diff options
-rw-r--r-- | panel/package.json | 1 | ||||
-rw-r--r-- | panel/src/components/Navbar.css | 31 | ||||
-rw-r--r-- | panel/src/pages/ManageAds.css | 88 | ||||
-rw-r--r-- | panel/src/pages/ManageAds.jsx | 153 | ||||
-rw-r--r-- | panel/yarn.lock | 7 |
5 files changed, 186 insertions, 94 deletions
diff --git a/panel/package.json b/panel/package.json index 5943f3c..4aa0a2e 100644 --- a/panel/package.json +++ b/panel/package.json @@ -12,6 +12,7 @@ "dependencies": { "@emotion/react": "^11.13.0", "@emotion/styled": "^11.13.0", + "@mui/icons-material": "^5.16.6", "@mui/material": "^5.16.6", "autoprefixer": "^10.4.19", "bootstrap": "^5.3.3", diff --git a/panel/src/components/Navbar.css b/panel/src/components/Navbar.css index ee38707..76713fb 100644 --- a/panel/src/components/Navbar.css +++ b/panel/src/components/Navbar.css @@ -1,27 +1,44 @@ .navbar { - background-color: #343a40; - padding: 1rem; + background-color: #2196F3; + padding: 10px 20px; display: flex; justify-content: center; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } .navbar-list { list-style: none; - padding: 0; - margin: 0; display: flex; + margin: 0; + padding: 0; } .navbar-item { - margin: 0 1rem; + margin: 0 15px; } .navbar-item a { color: white; text-decoration: none; - font-weight: bold; + font-weight: 500; + transition: color 0.3s ease; } .navbar-item a:hover { - text-decoration: underline; + color: #FFEB3B; +} + +@media (max-width: 768px) { + .navbar { + flex-direction: column; + align-items: center; + } + + .navbar-list { + flex-direction: column; + } + + .navbar-item { + margin: 10px 0; + } } diff --git a/panel/src/pages/ManageAds.css b/panel/src/pages/ManageAds.css index c9f154e..f6d805e 100644 --- a/panel/src/pages/ManageAds.css +++ b/panel/src/pages/ManageAds.css @@ -26,59 +26,71 @@ color: #333; } -.ad-form { - display: flex; - flex-direction: column; - margin-bottom: 30px; +.manage-ads-list { + width: 100%; + margin-top: 20px; } -.ad-form .form-control { +.ad-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 15px; margin-bottom: 15px; - padding: 10px; - font-size: 16px; - border: 1px solid #ccc; - border-radius: 5px; + background-color: #f9fafc; + border-radius: 10px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); +} + +.ad-item h6 { + font-size: 18px; + color: #333; + margin: 0; +} + +.ad-item p { + font-size: 14px; + color: #666; + margin: 0; +} + +.ad-item img, +.ad-item video { + width: 100px; + height: auto; + border-radius: 10px; + margin-right: 15px; +} + +.ad-item-actions { + display: flex; + align-items: center; } -.ad-form button { - align-self: flex-start; - padding: 10px 20px; - background-color: #007bff; - color: white; +.ad-item-actions button { + background: none; border: none; - border-radius: 5px; + margin: 0 5px; cursor: pointer; } -.ad-list { - display: flex; - flex-wrap: wrap; - justify-content: space-between; +.ad-item-actions button svg { + fill: #333; } -.ad-card { - flex: 1; - padding: 20px; - margin: 10px; - background-color: #f9fafc; - border-radius: 10px; - text-align: center; - box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); +.add-ad-button { + margin-top: 20px; } -.ad-card img { - max-width: 100%; - border-radius: 5px; - margin-bottom: 15px; +.add-ad-dialog { + padding: 20px; } -.ad-card h5 { - font-size: 20px; - color: #333; - margin-bottom: 10px; +.add-ad-dialog input[type="file"] { + margin-top: 10px; + margin-bottom: 20px; } -.ad-card p { - font-size: 16px; - color: #666; +.add-ad-dialog button { + margin-top: 20px; } diff --git a/panel/src/pages/ManageAds.jsx b/panel/src/pages/ManageAds.jsx index 8999db3..9f6e28c 100644 --- a/panel/src/pages/ManageAds.jsx +++ b/panel/src/pages/ManageAds.jsx @@ -1,74 +1,129 @@ import React, { useState, useEffect } from 'react'; -import { db } from '../firebase'; -import { collection, addDoc, getDocs } from 'firebase/firestore'; -import './ManageAds.css'; +import { Container, Button, Typography, Box, TextField, Dialog, IconButton } from '@mui/material'; +import { collection, addDoc, getDocs, deleteDoc, doc, updateDoc } from 'firebase/firestore'; +import { db, storage } from '../firebase'; +import { ref, uploadBytes, getDownloadURL } from 'firebase/storage'; +import DeleteIcon from '@mui/icons-material/Delete'; +import EditIcon from '@mui/icons-material/Edit'; import Navbar from '../components/Navbar'; +import './ManageAds.css'; const ManageAds = () => { + const [open, setOpen] = useState(false); + const [adName, setAdName] = useState(''); + const [adDescription, setAdDescription] = useState(''); + const [file, setFile] = useState(null); const [ads, setAds] = useState([]); - const [newAd, setNewAd] = useState({ title: '', description: '', imageUrl: '' }); + const [editAdId, setEditAdId] = useState(null); useEffect(() => { + const fetchAds = async () => { + const querySnapshot = await getDocs(collection(db, 'ads')); + const adsList = querySnapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); + setAds(adsList); + }; + fetchAds(); }, []); - const fetchAds = async () => { + const handleAddAd = async () => { + if (!file) return; + + const storageRef = ref(storage, `ads/${file.name}`); + await uploadBytes(storageRef, file); + const fileURL = await getDownloadURL(storageRef); + + if (editAdId) { + const adRef = doc(db, 'ads', editAdId); + await updateDoc(adRef, { + name: adName, + description: adDescription, + fileURL, + }); + setEditAdId(null); + } else { + await addDoc(collection(db, 'ads'), { + name: adName, + description: adDescription, + fileURL, + }); + } + + setOpen(false); + setAdName(''); + setAdDescription(''); + setFile(null); + const querySnapshot = await getDocs(collection(db, 'ads')); - const adsData = querySnapshot.docs.map(doc => doc.data()); - setAds(adsData); + const adsList = querySnapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); + setAds(adsList); }; - const handleAddAd = async () => { - await addDoc(collection(db, 'ads'), newAd); - fetchAds(); + const handleDeleteAd = async (id) => { + await deleteDoc(doc(db, 'ads', id)); + setAds(ads.filter(ad => ad.id !== id)); + }; + + const handleEditAd = (ad) => { + setAdName(ad.name); + setAdDescription(ad.description); + setFile(null); + setEditAdId(ad.id); + setOpen(true); }; return ( - <div> + <> <Navbar /> - <div className="container mt-5"> - <h4>Manage Ads</h4> - <div className="mb-4"> - <div className="mb-3"> - <label>Title</label> - <input - type="text" - value={newAd.title} - onChange={(e) => setNewAd({ ...newAd, title: e.target.value })} - className="form-control" + <Container className="manage-ads"> + <Typography variant="h4" className="manage-ads-header">Manage Ads</Typography> + <Button variant="contained" className="add-ad-button" onClick={() => setOpen(true)}>Add Ad</Button> + <Dialog open={open} onClose={() => setOpen(false)}> + <Box className="add-ad-dialog"> + <TextField + label="Ad Name" + value={adName} + onChange={(e) => setAdName(e.target.value)} + fullWidth /> - </div> - <div className="mb-3"> - <label>Description</label> - <input - type="text" - value={newAd.description} - onChange={(e) => setNewAd({ ...newAd, description: e.target.value })} - className="form-control" + <TextField + label="Ad Description" + value={adDescription} + onChange={(e) => setAdDescription(e.target.value)} + fullWidth /> - </div> - <div className="mb-3"> - <label>Image URL</label> <input - type="text" - value={newAd.imageUrl} - onChange={(e) => setNewAd({ ...newAd, imageUrl: e.target.value })} - className="form-control" + type="file" + onChange={(e) => setFile(e.target.files[0])} /> - </div> - <button onClick={handleAddAd} className="btn btn-primary">Add Ad</button> - </div> - <div> - {ads.map((ad, index) => ( - <div key={index} className="mb-3"> - <h5>{ad.title}</h5> - <p>{ad.description}</p> - <img src={ad.imageUrl} alt={ad.title} className="img-fluid" /> - </div> + <Button onClick={handleAddAd}>Upload Ad</Button> + </Box> + </Dialog> + <Box className="manage-ads-list"> + {ads.map(ad => ( + <Box key={ad.id} className="ad-item"> + <Box className="ad-item-content"> + <Typography variant="h6">{ad.name}</Typography> + <Typography variant="body1">{ad.description}</Typography> + {ad.fileURL && ( + ad.fileURL.includes('image') ? + <img src={ad.fileURL} alt={ad.name} /> : + <video src={ad.fileURL} controls /> + )} + </Box> + <Box className="ad-item-actions"> + <IconButton onClick={() => handleEditAd(ad)}> + <EditIcon /> + </IconButton> + <IconButton onClick={() => handleDeleteAd(ad.id)}> + <DeleteIcon /> + </IconButton> + </Box> + </Box> ))} - </div> - </div> - </div> + </Box> + </Container> + </> ); }; diff --git a/panel/yarn.lock b/panel/yarn.lock index 3f21ffa..2d4ad03 100644 --- a/panel/yarn.lock +++ b/panel/yarn.lock @@ -927,6 +927,13 @@ resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.16.6.tgz#f029e12ffda8eb79838cc85897f03a628010037c" integrity sha512-kytg6LheUG42V8H/o/Ptz3olSO5kUXW9zF0ox18VnblX6bO2yif1FPItgc3ey1t5ansb1+gbe7SatntqusQupg== +"@mui/icons-material@^5.16.6": + version "5.16.6" + resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-5.16.6.tgz#7067ddba693bec22f77592cb9cd516e2f1c1fd6e" + integrity sha512-ceNGjoXheH9wbIFa1JHmSc9QVjJUvh18KvHrR4/FkJCSi9HXJ+9ee1kUhCOEFfuxNF8UB6WWVrIUOUgRd70t0A== + dependencies: + "@babel/runtime" "^7.23.9" + "@mui/material@^5.16.6": version "5.16.6" resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.16.6.tgz#c7d695f4a9a473052dc086e64471d0435b7e4a52" |