diff --git a/src/App.jsx b/src/App.jsx
index 7f77291..4d62f31 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -11,85 +11,59 @@ import AddClient from "./Pages/AddClient";
import ManageClient from "./Pages/ManageClient";
import ClientPartnerMapping from "./Pages/ClientPartnerMapping";
import NewAdConfiguration from "./Pages/NewAdConfiguration";
-import Header from "./Components/Header";
import ManageFilesOrder from "./Pages/ManageFilesOrderPage";
import SettingsPage from "./Pages/SettingsPage";
import YouTubePlayer from "./Pages/YT";
+import Sidebar from "./Components/Sidebar";
+
+const ProtectedRoute = ({ children }) => {
+ const location = useLocation();
+ const storedLogin = localStorage.getItem("loggedIn");
+ let isLoggedIn = false;
+ if (storedLogin) {
+ try {
+ const parsed = JSON.parse(storedLogin);
+ const expired = Date.now() - parsed.timestamp > 1 * 60 * 60 * 1000;
+ isLoggedIn = parsed.value && !expired;
+ if (!isLoggedIn) localStorage.removeItem("loggedIn");
+ } catch {
+ localStorage.removeItem("loggedIn");
+ }
+ }
+ return isLoggedIn ? children : ;
+};
const Layout = () => {
const location = useLocation();
- const getLoginState = () => {
- const storedLogin = JSON.parse(localStorage.getItem('loggedIn'));
- if (storedLogin && storedLogin.value) {
- const expirationTime = 1 * 60 * 60 * 1000; // 1 hour expiration
- if (Date.now() - storedLogin.timestamp < expirationTime) {
- return true;
- } else {
- localStorage.removeItem('loggedIn'); // Expired, remove
- return false;
- }
- }
- return false;
- };
-
- const isLoggedIn = getLoginState();
-
- const hideHeaderRoutes = ["/", "/login"];
+ const hideSidebarRoutes = ["/", "/login"];
const isAdsPage = location.pathname.startsWith("/ads/");
-
-
-
- // App.js
- const ProtectedRoute = ({ children }) => {
- const location = useLocation();
- const storedLogin = localStorage.getItem('loggedIn');
- let isLoggedIn = false; // Default to false
-
- if (storedLogin) {
- try {
- const parsedLogin = JSON.parse(storedLogin);
- isLoggedIn = parsedLogin.value; // Access the 'value' property
- } catch (error) {
- console.error("Error parsing login data:", error);
- // Handle parsing error (e.g., clear localStorage)
- localStorage.removeItem('loggedIn');
- }
- }
-
- console.log(isLoggedIn); // Log the boolean value
-
- const isStaticFile = location.pathname.endsWith('.css') || location.pathname.endsWith('.js');
-
- if (isStaticFile) {
- return children; // Allow access to static files
- }
-
- return isLoggedIn ? children : ;
- };
+ const showSidebar = !hideSidebarRoutes.includes(location.pathname) && !isAdsPage;
return (
<>
- {!hideHeaderRoutes.includes(location.pathname) && !isAdsPage && }
-
- } />
- } />
- } />
- } />
+ {showSidebar && }
+
+
+ } />
+ } />
+ } />
+ } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
- } />
-
+ } />
+
+
>
);
};
@@ -102,4 +76,4 @@ function App() {
);
}
-export default App;
\ No newline at end of file
+export default App;
diff --git a/src/Components/AdminDashboardComponent.jsx b/src/Components/AdminDashboardComponent.jsx
index 24b2461..2408541 100644
--- a/src/Components/AdminDashboardComponent.jsx
+++ b/src/Components/AdminDashboardComponent.jsx
@@ -1,95 +1,89 @@
-import React from 'react'
import { motion } from "framer-motion";
+import { FaBuilding, FaUsers, FaTv, FaCog } from "react-icons/fa";
+import { useNavigate } from "react-router-dom";
-import { FaPlus, FaCircle } from "react-icons/fa";
-import { useNavigate } from 'react-router-dom';
+const cards = [
+ {
+ icon: FaBuilding,
+ label: "Partners",
+ description: "Manage restaurant partners",
+ path: "/partners",
+ gradient: "linear-gradient(135deg, #f97316, #f59e0b)",
+ glow: "rgba(249,115,22,0.3)",
+ bg: "rgba(249,115,22,0.08)",
+ border: "rgba(249,115,22,0.15)",
+ },
+ {
+ icon: FaUsers,
+ label: "Clients",
+ description: "Manage advertising clients",
+ path: "/clients",
+ gradient: "linear-gradient(135deg, #a855f7, #6366f1)",
+ glow: "rgba(168,85,247,0.3)",
+ bg: "rgba(168,85,247,0.08)",
+ border: "rgba(168,85,247,0.15)",
+ },
+ {
+ icon: FaTv,
+ label: "Ads Configuration",
+ description: "Configure ad campaigns",
+ path: "/Client-Partner-Mapping",
+ gradient: "linear-gradient(135deg, #06b6d4, #3b82f6)",
+ glow: "rgba(6,182,212,0.3)",
+ bg: "rgba(6,182,212,0.08)",
+ border: "rgba(6,182,212,0.15)",
+ },
+ {
+ icon: FaCog,
+ label: "General Settings",
+ description: "System configuration",
+ path: "/settings",
+ gradient: "linear-gradient(135deg, #10b981, #059669)",
+ glow: "rgba(16,185,129,0.3)",
+ bg: "rgba(16,185,129,0.08)",
+ border: "rgba(16,185,129,0.15)",
+ },
+];
export default function AdminDashboardComponent() {
- const navigate = useNavigate();
- return (
+ const navigate = useNavigate();
- <>
-
-
-
navigate("/Partners")}
- >
-
-
-
-
-
-
navigate("/Clients")}
- >
-
-
-
-
-
-
-
navigate("/Client-Partner-Mapping")}
- >
-
-
-
- {/*
*/}
-
Ads Configuration
-
-
-
-
navigate("/Settings")}
- >
-
-
-
- {/*
*/}
-
Configure General Settings
-
-
-
-
- >
-
- )
+ return (
+
+ {cards.map(({ icon: Icon, label, description, path, gradient, glow, bg, border }, i) => (
+
navigate(path)}
+ className="rounded-2xl p-6 cursor-pointer flex flex-col gap-4"
+ style={{ background: bg, border: `1px solid ${border}`, transition: "all 0.2s" }}
+ initial={{ opacity: 0, y: 20 }}
+ animate={{ opacity: 1, y: 0 }}
+ transition={{ delay: i * 0.08 }}
+ whileHover={{
+ scale: 1.03,
+ boxShadow: `0 16px 40px ${glow}`,
+ borderColor: border.replace("0.15", "0.4"),
+ }}
+ whileTap={{ scale: 0.98 }}
+ >
+
+
+
+
+
{label}
+
{description}
+
+
+ Open →
+
+
+ ))}
+
+ );
}
diff --git a/src/Components/ClientsPageCardComponent.jsx b/src/Components/ClientsPageCardComponent.jsx
index fd13643..4cf3719 100644
--- a/src/Components/ClientsPageCardComponent.jsx
+++ b/src/Components/ClientsPageCardComponent.jsx
@@ -1,87 +1,95 @@
-import React from 'react'
import { motion } from "framer-motion";
-
-import { FaPlus, FaCircle } from "react-icons/fa";
+import { FaPlus, FaTrash, FaUser } from "react-icons/fa";
import { api } from "../API/api";
-import { useNavigate } from 'react-router-dom';
-import { useLoading } from '../Context/LoadingContext';
+import { useNavigate } from "react-router-dom";
+import { useLoading } from "../Context/LoadingContext";
export default function ClientsCardComponent({ Data, setUpd }) {
- const navigate = useNavigate();
- const { setLoading } = useLoading();
+ const navigate = useNavigate();
+ const { setLoading } = useLoading();
+ const handleDelete = async (clientid) => {
+ try {
+ setLoading(true);
+ await api.delete(`/admin/delete-client/${clientid}`);
+ setUpd(clientid);
+ } catch (error) {
+ console.error("Error deleting client:", error);
+ }
+ setLoading(false);
+ };
- const handleDelete = async (clientid) => {
- console.log(clientid)
- try {
- setLoading(true);
- const response = await api.delete(`/admin/delete-client/${clientid}`);
- console.log(response);
- setUpd(clientid)
- } catch (error) {
- console.error("Error fetching files:", error);
- }
- setLoading(false)
- };
+ return (
+
+ {/* Add card */}
+
navigate("/add-client")}
+ >
+
+
+
+ Add New Client
+
+ {/* Client cards */}
+ {Data?.map((client, i) => (
+
navigate(`/manage-client/${client.transid}`)}
+ >
+
+
+
+
+
{client.name}
+
Client
+
-
- return (
-
- <>
-
-
-
navigate("/add-client")}
- >
-
-
- {Data?.map((client) => (
-
navigate(`/manage-client/${client.transid}`)}
- >
-
- {/* Red Delete Button at Top Right Corner */}
- {
- e.stopPropagation(); // Prevents triggering the card's onClick event
- if (confirm("Are you sure you want to delete this client?")) {
- handleDelete(client.transid);
- }
- }}
- className="absolute top-2 right-2 text-red-500 hover:text-red-700 text-xl"
- >
- 🗑️
-
-
-
-
-
-
{client.name}
-
-
-
- ))}
-
- >
-
- )
+ {
+ e.stopPropagation();
+ if (confirm("Delete this client?")) handleDelete(client.transid);
+ }}
+ onMouseEnter={e => { e.currentTarget.style.background = "rgba(239,68,68,0.28)"; }}
+ onMouseLeave={e => { e.currentTarget.style.background = "rgba(239,68,68,0.12)"; }}
+ >
+
+
+
+ ))}
+
+ );
}
diff --git a/src/Components/MappingComponent.jsx b/src/Components/MappingComponent.jsx
index 1897434..59cef77 100644
--- a/src/Components/MappingComponent.jsx
+++ b/src/Components/MappingComponent.jsx
@@ -1,87 +1,70 @@
-import React from 'react'
import { motion } from "framer-motion";
+import { FaPlus, FaTv } from "react-icons/fa";
+import { useNavigate } from "react-router-dom";
-import { FaPlus, FaCircle } from "react-icons/fa";
-import { api } from "../API/api";
-import { useNavigate } from 'react-router-dom';
-import { useLoading } from '../Context/LoadingContext';
+export default function MappingComponent({ Data }) {
+ const navigate = useNavigate();
-export default function MappingComponent({ Data, setUpd }) {
- const navigate = useNavigate();
- const { setLoading } = useLoading();
+ return (
+
+ {/* Add card */}
+
navigate("/new-ad-configuration")}
+ >
+
+
+
+
+ Add / Edit Ad Configuration
+
+
-
- const handleDelete = async (clientid) => {
- console.log(clientid)
- try {
- setLoading(true);
- const response = await api.delete(`/admin/delete-client/${clientid}`);
- console.log(response);
- setUpd(clientid)
- } catch (error) {
- console.error("Error fetching files:", error);
- }
- setLoading(false)
- };
-
-
-
- return (
-
- <>
-
-
-
navigate("/new-ad-configuration")}
- >
-
-
-
Add / Edit Ad Configuration
-
-
- {Data?.map((client) => (
-
navigate(`/ads-order-configuration/${client.transid}`)}
- >
-
-
- {/* {
- e.stopPropagation(); // Prevents triggering the card's onClick event
- if (confirm("Are you sure you want to delete this client?")) {
- handleDelete(client.transid);
- }
- }}
- className="absolute top-2 right-2 text-red-500 hover:text-red-700 text-xl"
- >
- 🗑️
- */}
-
-
-
-
-
{client.name}
-
-
-
- ))}
-
- >
-
- )
+ {/* Partner cards */}
+ {Data?.map((client, i) => (
+
navigate(`/ads-order-configuration/${client.transid}`)}
+ >
+
+
+
+
+
{client.name}
+
Configure ads →
+
+
+ ))}
+
+ );
}
diff --git a/src/Components/PartnersPageCardComponent.jsx b/src/Components/PartnersPageCardComponent.jsx
index 84a953f..2996e28 100644
--- a/src/Components/PartnersPageCardComponent.jsx
+++ b/src/Components/PartnersPageCardComponent.jsx
@@ -1,92 +1,110 @@
-import React from 'react'
import { motion } from "framer-motion";
-
-import { FaPlus, FaCircle } from "react-icons/fa";
+import { FaPlus, FaTrash, FaClock } from "react-icons/fa";
import { api } from "../API/api";
-import { useNavigate } from 'react-router-dom';
-import { useLoading } from '../Context/LoadingContext';
+import { useNavigate } from "react-router-dom";
+import { useLoading } from "../Context/LoadingContext";
export default function PartnersCardComponent({ Data, setUpd }) {
- const navigate = useNavigate();
- const { setLoading } = useLoading();
+ const navigate = useNavigate();
+ const { setLoading } = useLoading();
+ const handleDelete = async (partnerid) => {
+ try {
+ setLoading(true);
+ await api.delete(`/admin/delete-partner/${partnerid}`);
+ setUpd(partnerid);
+ } catch (error) {
+ console.error("Error deleting partner:", error);
+ }
+ setLoading(false);
+ };
- const handleDelete = async (partnerid) => {
- console.log(partnerid)
- try {
- setLoading(true);
- const response = await api.delete(`/admin/delete-partner/${partnerid}`);
- console.log(response);
- setUpd(partnerid)
- } catch (error) {
- console.error("Error fetching files:", error);
- }
- setLoading(false)
- };
+ return (
+
+ {/* Add card */}
+
navigate("/add-partner")}
+ >
+
+
+
+ Add New Partner
+
+ {/* Partner cards */}
+ {Data?.map((client, i) => (
+
navigate(`/manage-partner/${client.transid}`)}
+ >
+ {/* Thumbnail */}
+
+ {/* Info */}
+
+
{client.name}
+ {(client.open_time || client.close_time) && (
+
+
+ {client.open_time} – {client.close_time}
+
+ )}
+
-
- return (
-
- <>
-
-
-
navigate("/add-partner")}
- >
-
-
- {Data?.map((client) => (
-
navigate(`/manage-partner/${client.transid}`)}
- >
-
- {/* Red Delete Button at Top Right Corner */}
- {
- e.stopPropagation(); // Prevents triggering the card's onClick event
- if (confirm("Are you sure you want to delete this Partner?")) {
- handleDelete(client.transid);
- }
- }}
- className="absolute top-2 right-2 text-red-500 hover:text-red-700 text-xl"
- >
- 🗑️
-
-
-
-
-
{client.name}
-
🕒 {client.open_time} - {client.close_time}
-
- {/*
-
- {client.is_active ? "Active" : "Inactive"}
-
*/}
-
-
- ))}
-
- >
-
- )
+ {/* Delete */}
+ {
+ e.stopPropagation();
+ if (confirm("Delete this partner?")) handleDelete(client.transid);
+ }}
+ onMouseEnter={e => { e.currentTarget.style.background = "rgba(239,68,68,0.3)"; }}
+ onMouseLeave={e => { e.currentTarget.style.background = "rgba(239,68,68,0.15)"; }}
+ >
+
+
+
+ ))}
+
+ );
}
diff --git a/src/Components/Sidebar.jsx b/src/Components/Sidebar.jsx
new file mode 100644
index 0000000..7d257dc
--- /dev/null
+++ b/src/Components/Sidebar.jsx
@@ -0,0 +1,142 @@
+import { useState } from "react";
+import { Link, useLocation, useNavigate } from "react-router-dom";
+import { motion, AnimatePresence } from "framer-motion";
+import {
+ FaTachometerAlt, FaBuilding, FaUsers, FaTv, FaCog, FaSignOutAlt,
+} from "react-icons/fa";
+
+const navItems = [
+ { icon: FaTachometerAlt, label: "Dashboard", path: "/admin-dashboard" },
+ { icon: FaBuilding, label: "Partners", path: "/partners" },
+ { icon: FaUsers, label: "Clients", path: "/clients" },
+ { icon: FaTv, label: "Ads Config",path: "/Client-Partner-Mapping" },
+ { icon: FaCog, label: "Settings", path: "/settings" },
+];
+
+const NavItem = ({ icon: Icon, label, path, expanded }) => {
+ const location = useLocation();
+ const isActive =
+ location.pathname.toLowerCase() === path.toLowerCase() ||
+ location.pathname.toLowerCase().startsWith(path.toLowerCase() + "/");
+
+ return (
+ { if (!isActive) e.currentTarget.style.background = "rgba(255,255,255,0.05)"; }}
+ onMouseLeave={e => { if (!isActive) e.currentTarget.style.background = ""; }}
+ >
+ {isActive && (
+
+ )}
+
+
+ {expanded && (
+
+ {label}
+
+ )}
+
+
+ );
+};
+
+const Sidebar = () => {
+ const [expanded, setExpanded] = useState(false);
+ const navigate = useNavigate();
+
+ const handleLogout = () => {
+ localStorage.removeItem("loggedIn");
+ navigate("/login");
+ };
+
+ return (
+ setExpanded(true)}
+ onMouseLeave={() => setExpanded(false)}
+ animate={{ width: expanded ? 220 : 64 }}
+ transition={{ duration: 0.25, ease: "easeInOut" }}
+ className="fixed left-0 top-0 h-full z-50 flex flex-col overflow-hidden"
+ style={{
+ background: "linear-gradient(180deg, #100c24 0%, #160e30 100%)",
+ borderRight: "1px solid rgba(249,115,22,0.1)",
+ boxShadow: "4px 0 30px rgba(0,0,0,0.5)",
+ }}
+ >
+ {/* Logo row */}
+
+
+
+ {expanded && (
+
+ Dine 360 Ads
+ Admin Panel
+
+ )}
+
+
+
+ {/* Nav */}
+
+ {navItems.map((item) => (
+
+ ))}
+
+
+ {/* Logout */}
+
+
{ e.currentTarget.style.background = "rgba(248,113,113,0.08)"; }}
+ onMouseLeave={e => { e.currentTarget.style.background = ""; }}
+ >
+
+
+ {expanded && (
+
+ Logout
+
+ )}
+
+
+
+
+ );
+};
+
+export default Sidebar;
diff --git a/src/Pages/AddPartner.jsx b/src/Pages/AddPartner.jsx
index 1c2a20c..8f1f61e 100644
--- a/src/Pages/AddPartner.jsx
+++ b/src/Pages/AddPartner.jsx
@@ -1,310 +1,199 @@
import { useMemo, useState } from "react";
import { useNavigate } from "react-router-dom";
import { motion } from "framer-motion";
-import { FaArrowLeft } from "react-icons/fa";
+import { FaArrowLeft, FaBuilding } from "react-icons/fa";
import { api } from "../API/api";
import { useLoading } from "../Context/LoadingContext";
-import Select from 'react-select';
-import canada_cities from '../assets/canada_cities.json';
+import Select from "react-select";
+import canada_cities from "../assets/canada_cities.json";
+
+const selectStyles = {
+ control: (b) => ({
+ ...b,
+ background: "rgba(255,255,255,0.05)",
+ border: "1px solid rgba(255,255,255,0.1)",
+ borderRadius: "0.75rem",
+ padding: "4px",
+ boxShadow: "none",
+ "&:hover": { borderColor: "rgba(249,115,22,0.4)" },
+ }),
+ menu: (b) => ({
+ ...b,
+ background: "#1a1530",
+ border: "1px solid rgba(255,255,255,0.1)",
+ borderRadius: "0.75rem",
+ zIndex: 99,
+ }),
+ option: (b, s) => ({
+ ...b,
+ background: s.isSelected ? "rgba(249,115,22,0.2)" : s.isFocused ? "rgba(255,255,255,0.05)" : "transparent",
+ color: s.isSelected ? "#fb923c" : "#f1f5f9",
+ }),
+ singleValue: (b) => ({ ...b, color: "#f1f5f9" }),
+ input: (b) => ({ ...b, color: "#f1f5f9" }),
+ placeholder: (b) => ({ ...b, color: "#475569" }),
+};
const AddPartner = () => {
const [uploading, setUploading] = useState(false);
const [formData, setFormData] = useState({
- name: "",
- open_time: "",
- close_time: "",
- image: null,
- address: "",
- city: "",
- state: "",
- pincode: "",
- screens: "",
+ name: "", open_time: "", close_time: "", image: null,
+ address: "", city: "", state: "", pincode: "", screens: "",
});
+ const [selectedProvince, setSelectedProvince] = useState(null);
+ const [selectedCity, setSelectedCity] = useState(null);
const { setLoading } = useLoading();
const navigate = useNavigate();
- const handleChange = (e) => {
- const { name, value } = e.target;
- setFormData({ ...formData, [name]: value });
- };
+ const provinceOptions = useMemo(
+ () => Object.keys(canada_cities).map((p) => ({ value: p, label: p })),
+ []
+ );
+ const cityOptions = useMemo(
+ () => selectedProvince ? canada_cities[selectedProvince.value].map((c) => ({ value: c, label: c })) : [],
+ [selectedProvince]
+ );
+ const handleChange = (e) => setFormData({ ...formData, [e.target.name]: e.target.value });
+ const handleFileChange = (e) => setFormData({ ...formData, image: e.target.files[0] });
+ const handleProvinceChange = (o) => { setSelectedProvince(o); setSelectedCity(null); setFormData({ ...formData, state: o?.value || "" }); };
+ const handleCityChange = (o) => { setSelectedCity(o); setFormData({ ...formData, city: o?.value || "" }); };
- const handleFileUpload = async (file,id) => {
+ const handleFileUpload = async (file, id) => {
if (!file) return;
-
setUploading(true);
- const formData = new FormData();
- formData.append("file", file);
- formData.append("file_name", file.name);
- formData.append("client_id", id); // Attach client ID
-
+ const fd = new FormData();
+ fd.append("file", file);
+ fd.append("file_name", file.name);
+ fd.append("client_id", id);
try {
- console.log(formData)
- setLoading(true)
- await api.post("/files/update-partner-logo", formData, {
- headers: { "Content-Type": "multipart/form-data" },
- });
+ setLoading(true);
+ await api.post("/files/update-partner-logo", fd, { headers: { "Content-Type": "multipart/form-data" } });
navigate("/partners");
-
- // Refresh file list after each successful upload
- //fetchFiles();
- } catch (error) {
- console.error(`File upload failed for ${file.name}:`, error);
- // Optionally, you could add some user feedback here for individual file failures
+ } catch (e) {
+ console.error(e);
} finally {
- setUploading(false); // Keep this false, as we're handling individual uploads
- setLoading(false)
+ setUploading(false);
+ setLoading(false);
}
};
- const handleFileChange = async (e) => {
-
- setFormData({ ...formData, image: e.target.files[0] });
- };
const handleSubmit = async (e) => {
e.preventDefault();
try {
- console.log("iuytg")
setLoading(true);
- var data = await api.post("/admin/add-partner", formData);
- console.log(data)
- console.log("iuytg")
- await handleFileUpload(formData.image,data.id);
- navigate("/Partners");
- } catch (error) {
- console.error("Error adding Partner:", error);
+ const data = await api.post("/admin/add-partner", formData);
+ await handleFileUpload(formData.image, data.id);
+ navigate("/partners");
+ } catch (e) {
+ console.error(e);
}
setLoading(false);
};
- const [selectedProvince, setSelectedProvince] = useState(null);
- const [selectedCity, setSelectedCity] = useState(null);
-
- const provinceOptions = useMemo(
- () =>
- Object.keys(canada_cities).map((province) => ({
- value: province,
- label: province,
- })),
- [canada_cities]
- );
-
- const cityOptions = useMemo(
- () =>
- selectedProvince
- ? canada_cities[selectedProvince.value].map((city) => ({
- value: city,
- label: city,
- }))
- : [],
- [selectedProvince, canada_cities]
- );
-
- const handleProvinceChange = (selectedOption) => {
- setSelectedProvince(selectedOption);
- setSelectedCity(null);
- setFormData({ ...formData, state: selectedOption?.value || "" }); // Update state in formData
- };
-
- const handleCityChange = (selectedOption) => {
- setSelectedCity(selectedOption);
- setFormData({ ...formData, city: selectedOption?.value || "" }); // Update city in formData
- };
-
return (
-
-
navigate("/admin-dashboard")}
- >
- Back
-
-
-
➕ Add New Partner
-
-
+
Back to Partners
+
+
+
+
+
+
+
Add New Partner
+
Fill in the details to register a new partner
+
+
+
+
+
+
+ {/* Form */}
+
+
+
+ Basic Information
+
+
+
+
+
+
+ Location
+
+
+
+ Address
+
+
+
+ Province
+
+
+
+ City
+
+
+
+ Postal Code
+
+
+
+
+
+
+
+ {uploading ? "Saving..." : "Add Partner"}
+
+ navigate("/partners")}>Cancel
+
+
);
};
-export default AddPartner;
\ No newline at end of file
+export default AddPartner;
diff --git a/src/Pages/AdminDashboard.jsx b/src/Pages/AdminDashboard.jsx
index cacff3d..47caa74 100644
--- a/src/Pages/AdminDashboard.jsx
+++ b/src/Pages/AdminDashboard.jsx
@@ -1,55 +1,31 @@
-import { useEffect, useState } from "react";
-import { useNavigate } from "react-router-dom";
import { motion } from "framer-motion";
-import { FaPlus, FaCircle } from "react-icons/fa";
-import { api } from "../API/api";
-import { useLoading } from "../Context/LoadingContext";
-import HoveringCardComponent from "../Components/HoveringCardComponent";
import AdminDashboardComponent from "../Components/AdminDashboardComponent";
const AdminDashboard = () => {
- const [clients, setClients] = useState([]);
- const navigate = useNavigate();
- const { setLoading } = useLoading();
+ return (
+
+ {/* Header */}
+
+
+ Admin Dashboard
+
+ Welcome back — manage your Dine 360 Ads platform
+
- // useEffect(() => {
- // const fetchClients = async () => {
- // try {
- // setLoading(true);
- // const response = await api.get("/admin/clients");
+ {/* Divider */}
+
- // const updatedClients = response.map(client => ({
- // ...client,
- // is_active: client.status === "Offline" ? null : true,
- // }));
-
- // setClients(updatedClients);
-
-
- // } catch (error) {
- // console.error("Error fetching clients:", error);
- // } finally {
- // setLoading(false);
- // }
- // };
- // fetchClients();
- // }, []);
-
- return (
-
-
- 🏢 Admin Dashboard - Client List
-
-
-
-
- );
+
+
+ );
};
export default AdminDashboard;
diff --git a/src/Pages/ClientPartnerMapping.jsx b/src/Pages/ClientPartnerMapping.jsx
index 73b3cf2..7d9b620 100644
--- a/src/Pages/ClientPartnerMapping.jsx
+++ b/src/Pages/ClientPartnerMapping.jsx
@@ -1,53 +1,39 @@
-
import { useEffect, useState } from "react";
-import { useNavigate } from "react-router-dom";
import { motion } from "framer-motion";
-import { FaPlus, FaCircle } from "react-icons/fa";
import { api } from "../API/api";
import { useLoading } from "../Context/LoadingContext";
-import PartnersCardComponent from "../Components/PartnersPageCardComponent";
-import ClientsCardComponent from "../Components/ClientsPageCardComponent";
import MappingComponent from "../Components/MappingComponent";
const ClientPartnerMapping = () => {
- const [clients, setClients] = useState([]);
- const [upd, setUpd] = useState(0);
- const navigate = useNavigate();
- const { setLoading } = useLoading();
+ const [clients, setClients] = useState([]);
+ const [upd, setUpd] = useState(0);
+ const { setLoading } = useLoading();
- useEffect(() => {
- const fetchClients = async () => {
- try {
- setLoading(true);
- const response = await api.get("/admin/partners");
+ useEffect(() => {
+ const fetch = async () => {
+ try {
+ setLoading(true);
+ const response = await api.get("/admin/partners");
+ setClients(response);
+ } catch (e) {
+ console.error(e);
+ } finally {
+ setLoading(false);
+ }
+ };
+ fetch();
+ }, [upd]);
- setClients(response);
-
-
- } catch (error) {
- console.error("Error fetching clients:", error);
- } finally {
- setLoading(false);
- }
- };
- fetchClients();
- }, [upd]);
-
- return (
-
-
- Ads Configuration
-
-
-
-
-
- );
+ return (
+
+
+ Ads Configuration
+ Configure ad campaigns per partner and screen
+
+
+
+
+ );
};
-export default ClientPartnerMapping
-
+export default ClientPartnerMapping;
diff --git a/src/Pages/ClientsPage.jsx b/src/Pages/ClientsPage.jsx
index 95d8bf9..acb07f5 100644
--- a/src/Pages/ClientsPage.jsx
+++ b/src/Pages/ClientsPage.jsx
@@ -1,52 +1,39 @@
-
import { useEffect, useState } from "react";
-import { useNavigate } from "react-router-dom";
import { motion } from "framer-motion";
-import { FaPlus, FaCircle } from "react-icons/fa";
import { api } from "../API/api";
import { useLoading } from "../Context/LoadingContext";
-import PartnersCardComponent from "../Components/PartnersPageCardComponent";
import ClientsCardComponent from "../Components/ClientsPageCardComponent";
const ClientsPage = () => {
- const [clients, setClients] = useState([]);
- const [upd, setUpd] = useState(0);
- const navigate = useNavigate();
- const { setLoading } = useLoading();
+ const [clients, setClients] = useState([]);
+ const [upd, setUpd] = useState(0);
+ const { setLoading } = useLoading();
- useEffect(() => {
- const fetchClients = async () => {
- try {
- setLoading(true);
- const response = await api.get("/admin/clients");
+ useEffect(() => {
+ const fetch = async () => {
+ try {
+ setLoading(true);
+ const response = await api.get("/admin/clients");
+ setClients(response);
+ } catch (e) {
+ console.error(e);
+ } finally {
+ setLoading(false);
+ }
+ };
+ fetch();
+ }, [upd]);
- setClients(response);
-
-
- } catch (error) {
- console.error("Error fetching clients:", error);
- } finally {
- setLoading(false);
- }
- };
- fetchClients();
- }, [upd]);
-
- return (
-
-
- Clients List
-
-
-
-
-
- );
+ return (
+
+
+ Clients
+ Manage all advertising clients
+
+
+
+
+ );
};
-export default ClientsPage
-
+export default ClientsPage;
diff --git a/src/Pages/Login.jsx b/src/Pages/Login.jsx
index c1a98a3..62189aa 100644
--- a/src/Pages/Login.jsx
+++ b/src/Pages/Login.jsx
@@ -1,7 +1,7 @@
-import { useEffect, useState } from "react";
+import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { motion } from "framer-motion";
-import { FaLock, FaUser } from "react-icons/fa";
+import { FaLock, FaUser, FaFire } from "react-icons/fa";
const Login = () => {
const [username, setUsername] = useState("");
@@ -12,117 +12,175 @@ const Login = () => {
const handleLogin = (e) => {
e.preventDefault();
if (username === "admin" && password === "admin") {
- localStorage.setItem('loggedIn', JSON.stringify({
- timestamp: Date.now(),
- value: true,
- }));
+ localStorage.setItem("loggedIn", JSON.stringify({ timestamp: Date.now(), value: true }));
navigate("/admin-dashboard");
} else {
- setError("❌ Invalid credentials! Try again.");
+ setError("Invalid credentials. Please try again.");
}
};
- const [floors, setFloors] = useState([]);
-
- useEffect(() => {
- const a = "482beca79d9c005";
- const b = "b8778f51fcca82b";
- const authHeader = `token ${a}:${b}`;
-const url = "http://82.25.105.135:8004/api/resource/Dine360%20Room?fields=%5B%22*%22%5D&limit_page_length=100&filters=%5B%5B%22floor%22%2C%22%3D%22%2C%223%22%5D%5D";
-
- fetch(url, {
- headers: {
- Authorization: authHeader,
- },
- })
- .then((res) => res.json())
- .then((data) => {
- setFloors(data.data);
- console.log("regr", data.data)
- })
- .catch((err) => console.error(err));
- }, []);
-
return (
-
+
+ {/* Background orbs */}
+
+
+
+
+ {/* Card */}
- {/* Animated Heading */}
-
- 🔐 Admin Login
-
-
- {/* Input Fields */}
-
-
+ {/* Username */}
+
+
Username
+
+
+ setUsername(e.target.value)}
+ className="input-field"
+ style={{ paddingLeft: "2.5rem" }}
+ required
+ />
+
+
- {/* Floating Emojis Animation */}
-
- 🔑
-
+ {/* Password */}
+
+
Password
+
+
+ setPassword(e.target.value)}
+ className="input-field"
+ style={{ paddingLeft: "2.5rem" }}
+ required
+ />
+
+
-
- 🔥
+ {/* Error */}
+ {error && (
+
+ ⚠
+ {error}
+
+ )}
+
+ {/* Submit */}
+
+
+ Sign In
+
+
+
+
+ {/* Tagline */}
+
+ Dine 360 Ads · Digital Signage Platform
+
);
diff --git a/src/Pages/ManageFilesOrderPage.jsx b/src/Pages/ManageFilesOrderPage.jsx
index 777be4c..c57c4f3 100644
--- a/src/Pages/ManageFilesOrderPage.jsx
+++ b/src/Pages/ManageFilesOrderPage.jsx
@@ -1,253 +1,233 @@
-import { useEffect, useState, useRef, useMemo } from "react";
+import { useEffect, useState, useMemo } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { motion, AnimatePresence, Reorder } from "framer-motion";
-import { FaArrowUp, FaArrowDown } from "react-icons/fa";
+import { FaArrowUp, FaArrowDown, FaArrowLeft, FaSave, FaChevronDown, FaChevronRight } from "react-icons/fa";
import { api } from "../API/api";
import { useLoading } from "../Context/LoadingContext";
import ViewPartnerAdsConfiguration from "../Components/ViewPartnerAdsConfiguration";
const ManageFilesOrder = () => {
const { id } = useParams();
- const BACKEND = 'https://backend.dine360ads.com/';
+ const BACKEND = "https://backend.dine360ads.com/";
const [files, setFiles] = useState([]);
const [openScreens, setOpenScreens] = useState({});
const navigate = useNavigate();
const { setLoading } = useLoading();
- // Fetch and sort files
useEffect(() => {
(async () => {
try {
setLoading(true);
- const response = await api.get(`/admin/partner-ads?partnerid=${id}`);
- console.log(response)
- const sorted = response.sort((a, b) => {
- const nameA = a?.screenname || '';
- const nameB = b?.screenname || '';
- return nameA.localeCompare(nameB, undefined, { numeric: true });
- });
-
+ const res = await api.get(`/admin/partner-ads?partnerid=${id}`);
+ const sorted = res.sort((a, b) => (a?.screenname || "").localeCompare(b?.screenname || "", undefined, { numeric: true }));
setFiles(sorted);
- console.log("FILESSSSSSSSSSSSS<",sorted)
- } catch (error) {
- console.error("Error fetching files:", error);
- } finally {
- setLoading(false);
- }
+ } catch (e) { console.error(e); }
+ finally { setLoading(false); }
})();
}, [id, setLoading]);
- // Group files by screenname
const filesByScreen = useMemo(() =>
files.reduce((acc, file) => {
const key = file.screenname;
acc[key] = acc[key] || [];
acc[key].push(file);
return acc;
- }, {}),
- [files]
+ }, {}), [files]
);
- // Toggle collapse for screen
- const toggleScreen = (screen) => {
- setOpenScreens(prev => ({ ...prev, [screen]: !prev[screen] }));
- };
-
- // Merge reordered group back into files array
+ const toggleScreen = (s) => setOpenScreens(prev => ({ ...prev, [s]: !prev[s] }));
const handleGroupReorder = (screen, newGroup) => {
- const other = files.filter(f => f.screenname !== screen);
- setFiles([...other, ...newGroup]);
+ setFiles([...files.filter(f => f.screenname !== screen), ...newGroup]);
};
-
- // Save flattened order
const saveOrder = async () => {
try {
setLoading(true);
await api.post("/admin/reorder-partner-ads", files);
alert("Order saved successfully");
- } catch (error) {
- console.error("Error saving order:", error);
- alert("Error saving order. Check console for details.");
- } finally {
- setLoading(false);
- }
+ } catch (e) { console.error(e); alert("Error saving order."); }
+ finally { setLoading(false); }
};
- // Checkbox handlers update flat files state
- const handleToggleMainAd = (file, checked) => {
- setFiles(files.map(f =>
- f.file_path === file.file_path && file.screenid === f.screenid? { ...f, ismainad: checked ? 1 : 0 } : f
- ));
- };
- const handleToggleCarousel = (file, checked) => {
- setFiles(files.map(f =>
- f.file_path === file.file_path && file.screenid === f.screenid? { ...f, iscarousel: checked ? 1 : 0 } : f
- ));
- };
- const handleToggleInHouse = (file, checked) => {
- setFiles(files.map(f =>
- f.file_path === file.file_path && file.screenid === f.screenid? { ...f, isinhousead: checked ? 1 : 0 } : f
- ));
- };
+ const handleToggleMainAd = (file, checked) =>
+ setFiles(files.map(f => f.file_path === file.file_path && file.screenid === f.screenid ? { ...f, ismainad: checked ? 1 : 0 } : f));
+ const handleToggleCarousel = (file, checked) =>
+ setFiles(files.map(f => f.file_path === file.file_path && file.screenid === f.screenid ? { ...f, iscarousel: checked ? 1 : 0 } : f));
+ const handleToggleInHouse = (file, checked) =>
+ setFiles(files.map(f => f.file_path === file.file_path && file.screenid === f.screenid ? { ...f, isinhousead: checked ? 1 : 0 } : f));
- // Move up/down within a screen group
const groupMoveUp = (screen, index) => {
- const group = filesByScreen[screen];
+ const group = [...filesByScreen[screen]];
if (index <= 0) return;
- const newGroup = [...group];
- [newGroup[index - 1], newGroup[index]] = [newGroup[index], newGroup[index - 1]];
- handleGroupReorder(screen, newGroup);
+ [group[index - 1], group[index]] = [group[index], group[index - 1]];
+ handleGroupReorder(screen, group);
};
const groupMoveDown = (screen, index) => {
- const group = filesByScreen[screen];
+ const group = [...filesByScreen[screen]];
if (index >= group.length - 1) return;
- const newGroup = [...group];
- [newGroup[index], newGroup[index + 1]] = [newGroup[index + 1], newGroup[index]];
- handleGroupReorder(screen, newGroup);
+ [group[index], group[index + 1]] = [group[index + 1], group[index]];
+ handleGroupReorder(screen, group);
};
- return (
-
-
(
+
+
- 📂 Manage Ads Order
-
+
+
+ {label}
+
+ );
- {/* Collapsible groups by screen */}
-
+ return (
+
+
+ { e.currentTarget.style.color = "#f1f5f9"; }}
+ onMouseLeave={e => { e.currentTarget.style.color = "#64748b"; }}
+ onClick={() => navigate("/Client-Partner-Mapping")}
+ >
+ Back
+
+
+
+
Manage Ads Order
+
Drag to reorder, toggle ad types, save changes
+
+
+ Save Order
+
+
+
+
+
+
+
{Object.entries(filesByScreen).map(([screen, groupFiles]) => (
-
+
toggleScreen(screen)}
- className="w-full flex justify-between items-center bg-gray-700 px-4 py-2 rounded-lg"
+ className="w-full flex justify-between items-center px-5 py-4 transition-all"
+ style={{ background: openScreens[screen] ? "rgba(249,115,22,0.06)" : "transparent" }}
+ onMouseEnter={e => { if (!openScreens[screen]) e.currentTarget.style.background = "rgba(255,255,255,0.03)"; }}
+ onMouseLeave={e => { if (!openScreens[screen]) e.currentTarget.style.background = "transparent"; }}
>
- {screen}
- {openScreens[screen] ? '−' : '+'}
+
+
+ {screen}
+
+
+ {groupFiles.length} ad{groupFiles.length !== 1 ? "s" : ""}
+
+
+ {openScreens[screen]
+ ?
+ : }
{openScreens[screen] && (
- handleGroupReorder(screen, newOrder)}
- className="bg-gray-800 p-4 rounded-lg mt-2 space-y-2"
+
- {groupFiles.map((file, index) => {
- const fileName = file.file_path.split("_")[1];
- const fileUrl = BACKEND + file.file_path;
- const isImage = /\.(jpe?g|png|gif)$/i.test(fileUrl);
- const isVideo = /\.(mp4|webm|ogg|mkv)$/i.test(fileUrl);
+ handleGroupReorder(screen, newOrder)}
+ className="p-4 space-y-3"
+ >
+ {groupFiles.map((file, index) => {
+ const fileName = file.file_path.split("_")[1];
+ const fileUrl = BACKEND + file.file_path;
+ const isImage = /\.(jpe?g|png|gif)$/i.test(fileUrl);
+ const isVideo = /\.(mp4|webm|ogg|mkv)$/i.test(fileUrl);
- return (
-
-
- {isImage && (
- //
-
+ return (
+
+ {/* Preview */}
+
+ {isImage &&
}
+ {isVideo &&
}
+
- )}
- {isVideo && (
- //
- //
- //
- {fileName}
+
+ {/* Toggles */}
+
+ handleToggleMainAd(file, !file.ismainad)} />
+ handleToggleCarousel(file, !file.iscarousel)} />
+ handleToggleInHouse(file, !file.isinhousead)} />
+
+
+ {/* Up/Down */}
+
+ groupMoveUp(screen, index)}
+ disabled={index === 0}
+ className="w-7 h-7 rounded-lg flex items-center justify-center transition-all disabled:opacity-30"
+ style={{ background: "rgba(255,255,255,0.06)" }}
+ onMouseEnter={e => { if (index !== 0) e.currentTarget.style.background = "rgba(249,115,22,0.2)"; }}
+ onMouseLeave={e => { e.currentTarget.style.background = "rgba(255,255,255,0.06)"; }}
>
-
-
-
- )}
- {fileName}
-
-
-
- {/* Checkboxes */}
-
- handleToggleMainAd(file, e.target.checked)}
- />
- Main Ad
-
-
- handleToggleCarousel(file, e.target.checked)}
- />
- Carousel
-
-
- handleToggleInHouse(file, e.target.checked)}
- />
- In-House
-
-
- {/* Up/Down buttons */}
- groupMoveUp(screen, index)}
- disabled={index === 0}
- className="p-2 rounded-full bg-gray-700 disabled:opacity-50"
- >
-
-
- groupMoveDown(screen, index)}
- disabled={index === groupFiles.length - 1}
- className="p-2 rounded-full bg-gray-700 disabled:opacity-50"
- >
-
-
-
-
- );
- })}
-
+
+
+
groupMoveDown(screen, index)}
+ disabled={index === groupFiles.length - 1}
+ className="w-7 h-7 rounded-lg flex items-center justify-center transition-all disabled:opacity-30"
+ style={{ background: "rgba(255,255,255,0.06)" }}
+ onMouseEnter={e => { if (index !== groupFiles.length - 1) e.currentTarget.style.background = "rgba(249,115,22,0.2)"; }}
+ onMouseLeave={e => { e.currentTarget.style.background = "rgba(255,255,255,0.06)"; }}
+ >
+
+
+
+
+ );
+ })}
+
+
)}
))}
-
-
- Save Ads Order
-
-
- 📂 Ads Configurations
-
+ {/* Ads Configurations section */}
+
+
Ads Configurations
);
};
-export default ManageFilesOrder;
\ No newline at end of file
+export default ManageFilesOrder;
diff --git a/src/Pages/ManageFilesPage.jsx b/src/Pages/ManageFilesPage.jsx
index 03a4263..7cb3b14 100644
--- a/src/Pages/ManageFilesPage.jsx
+++ b/src/Pages/ManageFilesPage.jsx
@@ -1,158 +1,136 @@
import { useEffect, useState } from "react";
-import { useNavigate, useParams } from "react-router-dom";
import { motion } from "framer-motion";
-import { FaArrowLeft, FaUpload } from "react-icons/fa";
+import { FaUpload, FaTrash, FaFileImage, FaFileVideo } from "react-icons/fa";
import { api } from "../API/api";
import { useLoading } from "../Context/LoadingContext";
const ManageFiles = ({ id }) => {
- const BACKEND = 'https://backend.dine360ads.com/'
+ const BACKEND = "https://backend.dine360ads.com/";
const [files, setFiles] = useState([]);
const [upd, setupd] = useState(0);
const [uploading, setUploading] = useState(false);
- const navigate = useNavigate();
const { setLoading } = useLoading();
- // Fetch files when component loads
- useEffect(() => {
- fetchFiles();
- }, [id, upd]);
+
+ useEffect(() => { fetchFiles(); }, [id, upd]);
const fetchFiles = async () => {
try {
setLoading(true);
- const response = await api.get(`/client/files/${id}`);
- setFiles(response);
- } catch (error) {
- console.error("Error fetching files:", error);
- }
- setLoading(false)
+ const res = await api.get(`/client/files/${id}`);
+ setFiles(res);
+ } catch (e) { console.error(e); }
+ setLoading(false);
};
const deleteFiles = async (fileid) => {
- console.log(fileid)
try {
setLoading(true);
- const response = await api.delete(`/files/del/${fileid}`);
- console.log(response);
- setupd(upd + 1)
- } catch (error) {
- console.error("Error fetching files:", error);
- }
- setLoading(false)
+ await api.delete(`/files/del/${fileid}`);
+ setupd(upd + 1);
+ } catch (e) { console.error(e); }
+ setLoading(false);
};
const handleFileUpload = async (file) => {
if (!file) return;
-
setUploading(true);
- const formData = new FormData();
- formData.append("file", file);
- formData.append("file_name", file.name);
- formData.append("client_id", id); // Attach client ID
-
+ const fd = new FormData();
+ fd.append("file", file);
+ fd.append("file_name", file.name);
+ fd.append("client_id", id);
try {
- setLoading(true)
- await api.post("/files/upload", formData, {
- headers: { "Content-Type": "multipart/form-data" },
- });
-
- // Refresh file list after each successful upload
+ setLoading(true);
+ await api.post("/files/upload", fd, { headers: { "Content-Type": "multipart/form-data" } });
fetchFiles();
- } catch (error) {
- console.error(`File upload failed for ${file.name}:`, error);
- // Optionally, you could add some user feedback here for individual file failures
- } finally {
- setUploading(false); // Keep this false, as we're handling individual uploads
- setLoading(false)
- }
+ } catch (e) { console.error(e); }
+ finally { setUploading(false); setLoading(false); }
};
const handleMultipleUpload = async (e) => {
const filesToUpload = e.target.files;
- if (!filesToUpload || filesToUpload.length === 0) return;
-
+ if (!filesToUpload?.length) return;
setUploading(true);
- // Iterate through each selected file and call the upload function
for (let i = 0; i < filesToUpload.length; i++) {
await handleFileUpload(filesToUpload[i]);
}
- setUploading(false); // Set uploading to false after all files are processed
+ setUploading(false);
};
return (
-
- {/* Back Button */}
- {/*
navigate("/admin-dashboard")}
+
+
Media Files
+
+ {/* Upload zone */}
+
{ e.currentTarget.style.borderColor = "rgba(249,115,22,0.45)"; e.currentTarget.style.background = "rgba(249,115,22,0.08)"; }}
+ onMouseLeave={e => { e.currentTarget.style.borderColor = "rgba(249,115,22,0.2)"; e.currentTarget.style.background = "rgba(249,115,22,0.04)"; }}
>
- Back
- */}
+
+
+
+
+
{uploading ? "Uploading..." : "Click to upload files"}
+
Images and videos supported
+
+
+
- {/* Page Title */}
-
- 📂 Manage Client Files
-
+ {/* File list */}
+ {files.length === 0 ? (
+
No files uploaded yet.
+ ) : (
+
+ {files.map((file, index) => {
+ const fileName = file.file_name;
+ const fileUrl = BACKEND + file.file_path;
+ const isImage = /\.(jpe?g|png|gif)$/i.test(fileUrl);
+ const isVideo = /\.(mp4|webm|ogg|mkv)$/i.test(fileUrl);
- {/* Upload Button */}
-
-
-
- {uploading ? "Uploading..." : "Upload Files"}
-
-
-
-
- {files.length > 0 ? (
- files.map((file, index) => {
- const fileName = file.file_name
- const fileUrl = BACKEND+ file.file_path // Assuming this holds the correct file URL
- const isImage = fileUrl.match(/\.(jpeg|jpg|png|gif)$/i);
- const isVideo = fileUrl.match(/\.(mp4|webm|ogg|mkv)$/i);
-
- return (
-
- 📄 {fileName}
-
- {isImage && }
-
- {isVideo && (
-
-
- Your browser does not support the video tag.
-
+ return (
+
+
+ {isImage &&
}
+ {isVideo &&
}
+ {!isImage && !isVideo && (
+
+
+
)}
-
+
+
+
{fileName}
{
- if (confirm("Are you sure you want to delete this file?")) {
- deleteFiles(file.transid);
- }
- }}
- className="text-red-500 hover:text-red-700 text-sm font-bold"
+ className="w-7 h-7 rounded-lg flex items-center justify-center flex-shrink-0 transition-all"
+ style={{ background: "rgba(239,68,68,0.12)", border: "1px solid rgba(239,68,68,0.18)" }}
+ onClick={() => { if (confirm("Delete this file?")) deleteFiles(file.transid); }}
+ onMouseEnter={e => { e.currentTarget.style.background = "rgba(239,68,68,0.28)"; }}
+ onMouseLeave={e => { e.currentTarget.style.background = "rgba(239,68,68,0.12)"; }}
>
- Delete
+
-
- );
- })
- ) : (
-
No files uploaded yet.
- )}
-
-
+
+
+ );
+ })}
+
+ )}
);
};
-export default ManageFiles;
\ No newline at end of file
+export default ManageFiles;
diff --git a/src/Pages/ManagePartner.jsx b/src/Pages/ManagePartner.jsx
index a031da3..00a3923 100644
--- a/src/Pages/ManagePartner.jsx
+++ b/src/Pages/ManagePartner.jsx
@@ -1,59 +1,51 @@
import { useEffect, useState, useMemo } from "react";
import { useNavigate, useParams } from "react-router-dom";
-import { motion } from "framer-motion";
-import { FaArrowLeft, FaTrash, FaUpload } from "react-icons/fa";
+import { motion, AnimatePresence } from "framer-motion";
+import { FaArrowLeft, FaTrash, FaPlus, FaCircle, FaEdit, FaSave } from "react-icons/fa";
import { api } from "../API/api";
-import { FaPlus, FaCircle } from "react-icons/fa";
-import Select from 'react-select';
-import canada_cities from '../assets/canada_cities.json';
+import Select from "react-select";
+import canada_cities from "../assets/canada_cities.json";
import { useLoading } from "../Context/LoadingContext";
-import { FiPlus } from "react-icons/fi";
+
+const selectStyles = {
+ control: (b, s) => ({
+ ...b,
+ background: "rgba(255,255,255,0.05)",
+ border: `1px solid ${s.isFocused ? "rgba(249,115,22,0.5)" : "rgba(255,255,255,0.1)"}`,
+ borderRadius: "0.75rem",
+ padding: "4px",
+ boxShadow: s.isFocused ? "0 0 0 3px rgba(249,115,22,0.08)" : "none",
+ opacity: s.isDisabled ? 0.5 : 1,
+ "&:hover": { borderColor: "rgba(249,115,22,0.4)" },
+ }),
+ menu: (b) => ({ ...b, background: "#1a1530", border: "1px solid rgba(255,255,255,0.1)", borderRadius: "0.75rem", zIndex: 99 }),
+ option: (b, s) => ({
+ ...b,
+ background: s.isSelected ? "rgba(249,115,22,0.2)" : s.isFocused ? "rgba(255,255,255,0.05)" : "transparent",
+ color: s.isSelected ? "#fb923c" : "#f1f5f9",
+ }),
+ singleValue: (b) => ({ ...b, color: "#f1f5f9" }),
+ input: (b) => ({ ...b, color: "#f1f5f9" }),
+ placeholder: (b) => ({ ...b, color: "#475569" }),
+};
const ManagePartner = () => {
const { id } = useParams();
- const [files, setFiles] = useState([]);
- const [partners, setPartners] = useState([]);
const [screens, setScreens] = useState([]);
- const [uploading, setUploading] = useState(false);
- const navigate = useNavigate();
const [isEnabled, setIsEnabled] = useState(true);
const { setLoading } = useLoading();
+ const navigate = useNavigate();
const [formData, setFormData] = useState({
- transid: "",
- name: "",
- logo_url: "",
- open_time: "",
- close_time: "",
- address: "",
- city: "",
- state: "",
- pincode: "",
- screens: "",
- yt: "",
- scrolltext: ""
+ transid: "", name: "", logo_url: "", open_time: "", close_time: "",
+ address: "", city: "", state: "", pincode: "", screens: "", yt: "", scrolltext: "",
});
-
const [selectedProvince, setSelectedProvince] = useState(null);
const [selectedCity, setSelectedCity] = useState(null);
- const provinceOptions = useMemo(
- () =>
- Object.keys(canada_cities).map((province) => ({
- value: province,
- label: province,
- })),
- [canada_cities]
- );
-
+ const provinceOptions = useMemo(() => Object.keys(canada_cities).map((p) => ({ value: p, label: p })), []);
const cityOptions = useMemo(
- () =>
- selectedProvince
- ? canada_cities[selectedProvince.value].map((city) => ({
- value: city,
- label: city,
- }))
- : [],
- [selectedProvince, canada_cities]
+ () => selectedProvince ? canada_cities[selectedProvince.value].map((c) => ({ value: c, label: c })) : [],
+ [selectedProvince]
);
useEffect(() => {
@@ -61,566 +53,352 @@ const ManagePartner = () => {
fetchScreens();
}, [id]);
- const fetchFiles = async () => {
- try {
- const response = await api.get(`/client/files/${id}`);
- setFiles(response);
- } catch (error) {
- console.error("Error fetching files:", error);
- }
- };
-
const fetchPartners = async () => {
try {
-
- const response = await api.get(`/admin/get-partner/${id}`);
- setPartners(response);
- setFormData(response[0]);
- if (response[0]) {
- setSelectedProvince({ value: response[0].state, label: response[0].state });
- setSelectedCity({ value: response[0].city, label: response[0].city });
+ const res = await api.get(`/admin/get-partner/${id}`);
+ setFormData(res[0]);
+ if (res[0]) {
+ setSelectedProvince({ value: res[0].state, label: res[0].state });
+ setSelectedCity({ value: res[0].city, label: res[0].city });
}
- } catch (error) {
- console.error("Error fetching files:", error);
- }
+ } catch (e) { console.error(e); }
};
const fetchScreens = async () => {
try {
- const response = await api.get(`/admin/get-screen/${id}`);
- console.log(response)
- setScreens(response);
- console.log(response)
- } catch (error) {
- console.error("Error fetching files:", error);
- }
+ const res = await api.get(`/admin/get-screen/${id}`);
+ setScreens(res);
+ } catch (e) { console.error(e); }
};
const handleFileUpload = async (file) => {
if (!file) return;
-
- setUploading(true);
- const formData = new FormData();
- formData.append("file", file);
- formData.append("file_name", file.name);
- formData.append("client_id", id); // Attach client ID
-
+ const fd = new FormData();
+ fd.append("file", file);
+ fd.append("file_name", file.name);
+ fd.append("client_id", id);
try {
- setLoading(true)
- await api.post("/files/update-partner-logo", formData, {
- headers: { "Content-Type": "multipart/form-data" },
- });
- navigate("/partners");
-
- // Refresh file list after each successful upload
- fetchFiles();
- } catch (error) {
- console.error(`File upload failed for ${file.name}:`, error);
- // Optionally, you could add some user feedback here for individual file failures
- } finally {
- setUploading(false); // Keep this false, as we're handling individual uploads
- setLoading(false)
- }
+ setLoading(true);
+ await api.post("/files/update-partner-logo", fd, { headers: { "Content-Type": "multipart/form-data" } });
+ fetchPartners();
+ } catch (e) { console.error(e); }
+ finally { setLoading(false); }
};
+ const handleChange = (e) => setFormData({ ...formData, [e.target.name]: e.target.value });
const handleFileChange = async (e) => {
- console.log("oiouyt")
- await handleFileUpload(e.target.files[0]);
setFormData({ ...formData, image: e.target.files[0] });
+ await handleFileUpload(e.target.files[0]);
};
-
- const handleChange = (e) => {
- const { name, value } = e.target;
- setFormData({ ...formData, [name]: value });
- };
-
- const handleProvinceChange = (selectedOption) => {
- setSelectedProvince(selectedOption);
- setSelectedCity(null);
- setFormData({ ...formData, state: selectedOption?.value || "" });
- };
-
- const handleCityChange = (selectedOption) => {
- setSelectedCity(selectedOption);
- setFormData({ ...formData, city: selectedOption?.value || "" });
- };
-
- const handleEdit = () => {
- setIsEnabled(false);
- };
+ const handleProvinceChange = (o) => { setSelectedProvince(o); setSelectedCity(null); setFormData({ ...formData, state: o?.value || "" }); };
+ const handleCityChange = (o) => { setSelectedCity(o); setFormData({ ...formData, city: o?.value || "" }); };
const handleUpdate = async (e) => {
-
e.preventDefault();
try {
- console.log(formData)
-
await api.post("/admin/update-partner", formData);
+ setIsEnabled(true);
navigate("/partners");
-
- } catch (error) {
- console.error("Error updating Partner:", error);
- }
+ } catch (e) { console.error(e); }
};
const AddScreen = async () => {
-
try {
- console.log({ partnerid: id })
setLoading(true);
- var data = await api.post("/admin/add-screen", { partnerid: id });
- console.log(data)
-
+ await api.post("/admin/add-screen", { partnerid: id });
fetchScreens();
- } catch (error) {
- console.error("Error adding Screen:", error);
- }
- setLoading(false);
- }
-
-
- const DeleteScreen = async (id) => {
-
- try {
-
- setLoading(true);
- var data = await api.post("/admin/delete-screen", { screenid: id });
- console.log(data)
-
- fetchScreens();
- } catch (error) {
- console.error("Error adding Screen:", error);
- }
- setLoading(false);
- }
- const handleToggleYouTube = async (screenid, sts, idx) => {
- try {
- setLoading(true);
- // call your API – adjust the endpoint & payload as needed
- await api.post("/admin/update-screen-youtube", {
- screenid,
- sts: sts ? 1 : 0,
- });
-
- fetchScreens()
- } catch (err) {
- console.error("Error toggling YouTube:", err);
- } finally {
- setLoading(false);
- }
+ } catch (e) { console.error(e); }
+ finally { setLoading(false); }
};
+ const DeleteScreen = async (sid) => {
+ try {
+ setLoading(true);
+ await api.post("/admin/delete-screen", { screenid: sid });
+ fetchScreens();
+ } catch (e) { console.error(e); }
+ finally { setLoading(false); }
+ };
+ const handleToggleYouTube = async (screenid, sts) => {
+ try {
+ setLoading(true);
+ await api.post("/admin/update-screen-youtube", { screenid, sts: sts ? 1 : 0 });
+ fetchScreens();
+ } catch (e) { console.error(e); }
+ finally { setLoading(false); }
+ };
+
+ const statusColor = (s) => s === 1 ? "#4ade80" : s === 2 ? "#fb923c" : "#f87171";
+ const statusLabel = (s) => s === 1 ? "Active" : s === 2 ? "Idle" : "Inactive";
return (
-
- {/* Back button */}
-
navigate("/Partners")}
- >
- Back
-
-
+
{/* Header */}
-
- Manage Partners
-
+
+ { e.currentTarget.style.color = "#f1f5f9"; }}
+ onMouseLeave={e => { e.currentTarget.style.color = "#64748b"; }}
+ onClick={() => navigate("/partners")}
+ >
+ Back to Partners
+
+
+
+
{formData.name || "Manage Partner"}
+
Edit partner details and manage screens
+
+ {isEnabled ? (
+
setIsEnabled(false)}
+ className="btn-secondary flex items-center gap-2"
+ whileHover={{ scale: 1.03 }}
+ >
+ Edit Details
+
+ ) : (
+
+ Save Changes
+
+ )}
+
+
+
+
{/* Form */}
-
-
- {/* Partner Name */}
-
-
- {/* Edit / Update button */}
-
- {isEnabled ? (
-
- Edit
-
- ) : (
-
- Update
-
- )}
-
-
-
- {/* Screens Preview */}
- {formData.screens && (
-
-
-
-
-
- Add New Screen
-
+ {/* Basic info */}
+
- {screens.map((screen, index) => (
+ {/* Location */}
+
+
Location
+
+
+ Address
+
+
+
+ Province
+
+
+
+ City
+
+
+
+ Postal Code
+
+
+
+
-
- {/* Delete Button - Top Right of Card */}
-
{
- if (window.confirm("Are you sure you want to delete this screen?")) {
- DeleteScreen(screen.transid);
- }
- }}
- title="Delete this screen"
+ {/* Content */}
+
+
Content Settings
+
+
+ YouTube Links (comma separated)
+
+
+
+ Scroll Text
+
+
+
+
+
+
+ {/* Screens */}
+ {formData.screens && (
+
+
+
Screens
+
+ Add Screen
+
+
+
+
+ {screens.map((screen, index) => (
+
-
-
-
-
- Screen {index + 1} ({id.substring(0, 2) + 'S' + (index + 1)})
-
-
-
- {/* Preview Button */}
-
{
- const prefix = id.substring(0, 5);
- const screenName = `Screen${index + 1}`.replace(/\s+/g, '');
- window.open(`${window.location.origin}/ads/${prefix}/${screenName}`, '_blank');
- }}
- >
- Preview
-
-
- {/* Copy Link Button */}
-
{
- const prefix = id.substring(0, 5);
- const screenName = `Screen${index + 1}`.replace(/\s+/g, '');
- navigator.clipboard.writeText(`${window.location.origin}/ads/${prefix}/${screenName}`);
- alert('Link copied to clipboard!');
- }}
- >
- Copy Link
-
-
- {/* YouTube Toggle Switch */}
-
-
- handleToggleYouTube(screen.transid, screen.isyoutube !== 1, index)}
- className="sr-only peer"
- />
-
-
+
+
+
+ Screen {index + 1}
+
+ {id.substring(0, 2) + "S" + (index + 1)}
+
+
+
+
+ {statusLabel(screen.isactive)}
-
- {screen.isyoutube === 1 ? "YouTube ON" : "YouTube OFF"}
-
-
-
-
-
-
- {
- const newType = Number(e.target.value);
- try {
- console.log(screen.screentype)
- setLoading(true);
- await api.post("/admin/update-screen-type", {
- screenid: screen.transid,
- sts: newType,
- });
- fetchScreens();
- } catch (err) {
- console.error("Error updating screen type:", err);
- } finally {
- setLoading(false);
- }
- }}
- className="bg-gray-700 text-white rounded-lg w-full px-3 py-1 focus:outline-none focus:ring-2 focus:ring-blue-400"
+
+
{ if (window.confirm("Delete this screen?")) DeleteScreen(screen.transid); }}
+ onMouseEnter={e => { e.currentTarget.style.background = "rgba(239,68,68,0.28)"; }}
+ onMouseLeave={e => { e.currentTarget.style.background = "rgba(239,68,68,0.12)"; }}
>
- Screen Type 1 (Normal)
- {/* Screen Type 2 (InHouse) */}
- Screen Type 3 (In-house and Scroll Text)
-
-
+
+
+
+ {screen.status && (
+
+ {screen.status}
+ {screen.stsupdat && (
+
+ {new Date(screen.stsupdat).toLocaleDateString("en-GB")}{" "}
+ {new Date(screen.stsupdat).toLocaleTimeString()}
+
+ )}
+
+ )}
- {/* Status Indicator */}
-
-
-
- {screen.isactive === 1 ? "ACTIVE" : screen.isactive === 2 ? "IDLE" : "INACTIVE"}
-
+ {/* Actions */}
+
+
+ { e.currentTarget.style.background = "rgba(59,130,246,0.25)"; }}
+ onMouseLeave={e => { e.currentTarget.style.background = "rgba(59,130,246,0.15)"; }}
+ onClick={() => {
+ const prefix = id.substring(0, 5);
+ const sn = `Screen${index + 1}`;
+ window.open(`${window.location.origin}/ads/${prefix}/${sn}`, "_blank");
+ }}
+ >
+ Preview
+
+ { e.currentTarget.style.background = "rgba(16,185,129,0.25)"; }}
+ onMouseLeave={e => { e.currentTarget.style.background = "rgba(16,185,129,0.15)"; }}
+ onClick={() => {
+ const prefix = id.substring(0, 5);
+ const sn = `Screen${index + 1}`;
+ navigator.clipboard.writeText(`${window.location.origin}/ads/${prefix}/${sn}`);
+ alert("Link copied!");
+ }}
+ >
+ Copy Link
+
+
+
+ {/* YouTube toggle */}
+
+
YouTube
+
+ handleToggleYouTube(screen.transid, screen.isyoutube !== 1)}
+ className="sr-only peer"
+ />
+
+
+ {screen.isyoutube === 1 ? "ON" : "OFF"}
+
+
+
+
+ {/* Screen type */}
+
+ Screen Type
+ {
+ const newType = Number(e.target.value);
+ try {
+ setLoading(true);
+ await api.post("/admin/update-screen-type", { screenid: screen.transid, sts: newType });
+ fetchScreens();
+ } catch (err) { console.error(err); }
+ finally { setLoading(false); }
+ }}
+ className="input-field"
+ style={{ paddingTop: "0.5rem", paddingBottom: "0.5rem" }}
+ >
+ Type 1 — Normal
+ Type 3 — In-house + Scroll Text
+
+
-
-
-
-
- {screen.status}
-
- {screen.stsupdat && (
-
- {new Date(screen.stsupdat).toLocaleDateString('en-GB')}{' '}
- {new Date(screen.stsupdat).toLocaleTimeString()}
-
- )}
-
-
- ))}
-
-
+ ))}
+
+
)}
);
-
};
-export default ManagePartner;
\ No newline at end of file
+export default ManagePartner;
diff --git a/src/Pages/PartnersPage.jsx b/src/Pages/PartnersPage.jsx
index 18eca83..df5fe55 100644
--- a/src/Pages/PartnersPage.jsx
+++ b/src/Pages/PartnersPage.jsx
@@ -1,99 +1,43 @@
-
import { useEffect, useState } from "react";
-import { useNavigate } from "react-router-dom";
import { motion } from "framer-motion";
-import { FaPlus, FaCircle } from "react-icons/fa";
import { api } from "../API/api";
import { useLoading } from "../Context/LoadingContext";
import PartnersCardComponent from "../Components/PartnersPageCardComponent";
const PartnersPage = () => {
- const [clients, setClients] = useState([]);
- const [upd, setUpd] = useState(0);upd
- const navigate = useNavigate();
- const { setLoading } = useLoading();
+ const [clients, setClients] = useState([]);
+ const [upd, setUpd] = useState(0);
+ const { setLoading } = useLoading();
- useEffect(() => {
- const fetchClients = async () => {
- try {
- setLoading(true);
- const response = await api.get("/admin/partners");
+ useEffect(() => {
+ const fetch = async () => {
+ try {
+ setLoading(true);
+ const response = await api.get("/admin/partners");
+ setClients(response.map(c => ({ ...c, is_active: c.status !== "Offline" })));
+ } catch (e) {
+ console.error(e);
+ } finally {
+ setLoading(false);
+ }
+ };
+ fetch();
+ }, [upd]);
- const updatedClients = response.map(client => ({
- ...client,
- is_active: client.status === "Offline" ? null : true,
- }));
-
- setClients(updatedClients);
-
-
- } catch (error) {
- console.error("Error fetching clients:", error);
- } finally {
- setLoading(false);
- }
- };
- fetchClients();
- }, [upd]);
-
- return (
-
-
- Partners List
-
-
- {/*
-
navigate("/add-client")}
- >
-
-
-
-
- {clients?.map((client) => (
-
navigate(`/manage-client/${client.id}`)}
- >
-
-
-
-
{client.name}
-
🕒 {client.open_time} - {client.close_time}
-
-
-
- {client.is_active ? "Active" : "Inactive"}
-
-
-
- ))}
-
*/}
-
-
- );
+ return (
+
+
+ Partners
+ Manage all restaurant and venue partners
+
+
+
+
+ );
};
-export default PartnersPage
-
+export default PartnersPage;
diff --git a/src/Pages/SettingsPage.jsx b/src/Pages/SettingsPage.jsx
index b24e72a..5ced751 100644
--- a/src/Pages/SettingsPage.jsx
+++ b/src/Pages/SettingsPage.jsx
@@ -1,95 +1,89 @@
import { useEffect, useState } from "react";
-import { useNavigate } from "react-router-dom";
import { motion } from "framer-motion";
-import { FaArrowLeft } from "react-icons/fa";
+import { FaCog, FaSave } from "react-icons/fa";
import { api } from "../API/api";
import { useLoading } from "../Context/LoadingContext";
const SettingsPage = () => {
const { setLoading } = useLoading();
- const navigate = useNavigate();
const [settingsData, setSettingsData] = useState([]);
- useEffect(() => {
- fetchSettings();
- }, []);
+ useEffect(() => { fetchSettings(); }, []);
const fetchSettings = async () => {
try {
setLoading(true);
- const response = await api.get("/client/get-settings");
- setSettingsData(response);
- } catch (error) {
- console.error("Error fetching settings:", error);
- }
+ const res = await api.get("/client/get-settings");
+ setSettingsData(res);
+ } catch (e) { console.error(e); }
setLoading(false);
};
const handleUpdate = async (transid, newValue) => {
try {
setLoading(true);
- await api.post("/admin/update-settings", {
- transid: transid,
- value: newValue,
- });
- alert("General Settings Updated Successfully ")
+ await api.post("/admin/update-settings", { transid, value: newValue });
+ alert("Setting updated successfully");
fetchSettings();
- } catch (error) {
- console.error("Error updating setting:", error);
- }
+ } catch (e) { console.error(e); }
setLoading(false);
};
return (
-
-
navigate("/admin-dashboard")}
- >
- Back
-
+
+
+
+
+
+
+
+
General Settings
+
System-wide configuration values
+
+
+
-
- Settings
-
+
-
{/* Grid Layout */}
- {settingsData.map((item) => (
-
-
- {item.name}
-
+
+ {settingsData.map((item, i) => (
+
+ {item.name}
{
- const updatedSettings = settingsData.map((setting) =>
- setting.transid === item.transid
- ? { ...setting, value: e.target.value }
- : setting
- );
- setSettingsData(updatedSettings);
+ setSettingsData(settingsData.map((s) =>
+ s.transid === item.transid ? { ...s, value: e.target.value } : s
+ ));
}}
- className="block w-full bg-gray-700 p-2 rounded-lg mb-2"
+ className="input-field mb-3"
/>
handleUpdate(item.transid, item.value)}
- className="bg-blue-500 px-4 py-2 rounded-lg"
- whileHover={{ scale: 1.05 }}
+ className="btn-primary flex items-center gap-2 w-full justify-center"
+ style={{ padding: "0.55rem 1rem", fontSize: "0.8rem" }}
+ whileHover={{ scale: 1.02 }}
+ whileTap={{ scale: 0.97 }}
>
- Update
+ Update
-
+
))}
);
};
-export default SettingsPage;
\ No newline at end of file
+export default SettingsPage;
diff --git a/src/index.css b/src/index.css
index a461c50..59cb674 100644
--- a/src/index.css
+++ b/src/index.css
@@ -1 +1,136 @@
-@import "tailwindcss";
\ No newline at end of file
+@import "tailwindcss";
+
+body {
+ background: #0c0a1e;
+ color: #f1f5f9;
+ margin: 0;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+}
+
+.glass-card {
+ background: rgba(255, 255, 255, 0.04);
+ backdrop-filter: blur(20px);
+ -webkit-backdrop-filter: blur(20px);
+ border: 1px solid rgba(255, 255, 255, 0.08);
+}
+
+.glass-card-hover {
+ background: rgba(255, 255, 255, 0.04);
+ backdrop-filter: blur(20px);
+ -webkit-backdrop-filter: blur(20px);
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ transition: all 0.2s ease;
+}
+.glass-card-hover:hover {
+ background: rgba(255, 255, 255, 0.07);
+ border-color: rgba(249, 115, 22, 0.25);
+ box-shadow: 0 0 30px rgba(249, 115, 22, 0.08);
+}
+
+.gradient-text {
+ background: linear-gradient(135deg, #f97316, #f59e0b);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+}
+
+.btn-primary {
+ background: linear-gradient(135deg, #f97316, #f59e0b);
+ color: white;
+ border: none;
+ border-radius: 0.75rem;
+ padding: 0.65rem 1.5rem;
+ font-weight: 600;
+ font-size: 0.9rem;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ box-shadow: 0 4px 20px rgba(249, 115, 22, 0.3);
+}
+.btn-primary:hover {
+ transform: translateY(-1px);
+ box-shadow: 0 6px 25px rgba(249, 115, 22, 0.45);
+}
+
+.btn-secondary {
+ background: rgba(255,255,255,0.06);
+ color: #f1f5f9;
+ border: 1px solid rgba(255,255,255,0.1);
+ border-radius: 0.75rem;
+ padding: 0.65rem 1.5rem;
+ font-weight: 600;
+ font-size: 0.9rem;
+ cursor: pointer;
+ transition: all 0.2s ease;
+}
+.btn-secondary:hover {
+ background: rgba(255,255,255,0.1);
+ border-color: rgba(255,255,255,0.2);
+}
+
+.btn-danger {
+ background: rgba(239, 68, 68, 0.15);
+ color: #f87171;
+ border: 1px solid rgba(239,68,68,0.2);
+ border-radius: 0.75rem;
+ padding: 0.65rem 1.5rem;
+ font-weight: 600;
+ font-size: 0.9rem;
+ cursor: pointer;
+ transition: all 0.2s ease;
+}
+.btn-danger:hover {
+ background: rgba(239,68,68,0.25);
+}
+
+.input-field {
+ width: 100%;
+ background: rgba(255,255,255,0.05);
+ border: 1px solid rgba(255,255,255,0.1);
+ border-radius: 0.75rem;
+ padding: 0.75rem 1rem;
+ color: #f1f5f9;
+ font-size: 0.9rem;
+ outline: none;
+ transition: all 0.2s ease;
+}
+.input-field::placeholder { color: #475569; }
+.input-field:focus {
+ border-color: rgba(249,115,22,0.5);
+ box-shadow: 0 0 0 3px rgba(249,115,22,0.08);
+}
+.input-field:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+.label {
+ display: block;
+ font-size: 0.8rem;
+ font-weight: 600;
+ color: #94a3b8;
+ margin-bottom: 0.4rem;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+}
+
+.page-wrapper {
+ min-height: 100vh;
+ padding: 2rem;
+}
+
+.page-title {
+ font-size: 1.75rem;
+ font-weight: 800;
+ color: #f1f5f9;
+ margin-bottom: 0.25rem;
+}
+
+.page-subtitle {
+ font-size: 0.875rem;
+ color: #64748b;
+}
+
+::-webkit-scrollbar { width: 5px; }
+::-webkit-scrollbar-track { background: #0c0a1e; }
+::-webkit-scrollbar-thumb { background: rgba(249,115,22,0.25); border-radius: 3px; }
+::-webkit-scrollbar-thumb:hover { background: rgba(249,115,22,0.45); }