aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--panel/package.json1
-rw-r--r--panel/src/components/Navbar.css31
-rw-r--r--panel/src/pages/ManageAds.css88
-rw-r--r--panel/src/pages/ManageAds.jsx153
-rw-r--r--panel/yarn.lock7
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"