Initial commit — Dine360 Ads Frontend

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
MOHAN 2026-06-13 13:15:55 +05:30
commit a78b168b60
57 changed files with 13760 additions and 0 deletions

24
.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

6
.htaccess Normal file
View File

@ -0,0 +1,6 @@
<IfModule mod_rewrite.c>
Options -MultiViews
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.html [QSA,L]
</IfModule>

12
README.md Normal file
View File

@ -0,0 +1,12 @@
# React + Vite
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
## Expanding the ESLint configuration
If you are developing a production application, we recommend using TypeScript and enable type-aware lint rules. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.

33
eslint.config.js Normal file
View File

@ -0,0 +1,33 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
export default [
{ ignores: ['dist'] },
{
files: ['**/*.{js,jsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parserOptions: {
ecmaVersion: 'latest',
ecmaFeatures: { jsx: true },
sourceType: 'module',
},
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...js.configs.recommended.rules,
...reactHooks.configs.recommended.rules,
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
},
]

13
index.html Normal file
View File

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/logo.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Dine 360 Ads</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>

3816
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

40
package.json Normal file
View File

@ -0,0 +1,40 @@
{
"name": "frontend",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@heroicons/react": "^2.2.0",
"@tailwindcss/vite": "^4.0.14",
"autoprefixer": "^10.4.21",
"axios": "^1.8.3",
"framer-motion": "^12.9.4",
"lucide-react": "^0.484.0",
"postcss": "^8.5.3",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-icons": "^5.5.0",
"react-router-dom": "^7.3.0",
"react-select": "^5.10.1",
"react-toastify": "^11.0.5",
"sweetalert2": "^11.17.2",
"tailwindcss": "^4.0.14"
},
"devDependencies": {
"@eslint/js": "^9.21.0",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"@vitejs/plugin-react": "^4.3.4",
"eslint": "^9.21.0",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-react-refresh": "^0.4.19",
"globals": "^15.15.0",
"vite": "^6.2.0"
}
}

1765
public/canada_cities.json Normal file

File diff suppressed because it is too large Load Diff

BIN
public/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

53
src/API/api.js Normal file
View File

@ -0,0 +1,53 @@
import axios from "axios";
var API_BASE_URL = "https://ads.dine360ads.com/api/api";
//API_BASE_URL = "http://82.25.95.117:5000/api";
//API_BASE_URL = "http://localhost:5000/api"; // Update as needed
export const api = {
get: async (endpoint) => {
// Show loading
try {
const response = await axios.get(`${API_BASE_URL}${endpoint}`);
return response.data;
} catch (error) {
console.error("GET Error:", error);
throw error;
} finally {
// Hide loading
}
},
post: async (endpoint, data) => {
try {
const response = await axios.post(`${API_BASE_URL}${endpoint}`, data);
return response.data;
} catch (error) {
console.error("POST Error:", error);
throw error;
} finally {
}
},
put: async (endpoint, data) => {
try {
const response = await axios.put(`${API_BASE_URL}${endpoint}`, data);
return response.data;
} catch (error) {
console.error("PUT Error:", error);
throw error;
} finally {
}
},
delete: async (endpoint) => {
try {
const response = await axios.delete(`${API_BASE_URL}${endpoint}`);
return response.data;
} catch (error) {
console.error("DELETE Error:", error);
throw error;
} finally {
}
},
};

0
src/App.css Normal file
View File

105
src/App.jsx Normal file
View File

@ -0,0 +1,105 @@
import { BrowserRouter as Router, Routes, Route, useLocation, Navigate } from "react-router-dom";
import NotFound from "./Pages/NotFound";
import Login from "./Pages/Login";
import AdminDashboard from "./Pages/AdminDashboard";
import AdsPage from "./Pages/Ads";
import PartnersPage from "./Pages/PartnersPage";
import AddPartner from "./Pages/AddPartner";
import ManagePartner from "./Pages/ManagePartner";
import ClientsPage from "./Pages/ClientsPage";
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";
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 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 : <Navigate to="/login" />;
};
return (
<>
{!hideHeaderRoutes.includes(location.pathname) && !isAdsPage && <Header />}
<Routes>
<Route path="/" element={<Login />} />
<Route path="/login" element={<Login />} />
<Route path="/yt" element={<YouTubePlayer />} />
<Route path="/ads/:partnerid/:screenName" element={<AdsPage />} />
<Route path="/admin-dashboard" element={<ProtectedRoute><AdminDashboard /></ProtectedRoute>} />
<Route path="/partners" element={<ProtectedRoute><PartnersPage /></ProtectedRoute>} />
<Route path="/add-partner" element={<ProtectedRoute><AddPartner /></ProtectedRoute>} />
<Route path="/manage-partner/:id" element={<ProtectedRoute><ManagePartner /></ProtectedRoute>} />
<Route path="/clients" element={<ProtectedRoute><ClientsPage /></ProtectedRoute>} />
<Route path="/add-client" element={<ProtectedRoute><AddClient /></ProtectedRoute>} />
<Route path="/manage-client/:id" element={<ProtectedRoute><ManageClient /></ProtectedRoute>} />
<Route path="/Client-Partner-Mapping" element={<ProtectedRoute><ClientPartnerMapping /></ProtectedRoute>} />
<Route path="/new-ad-configuration" element={<ProtectedRoute><NewAdConfiguration /></ProtectedRoute>} />
<Route path="/ads-order-configuration/:id" element={<ProtectedRoute><ManageFilesOrder /></ProtectedRoute>} />
<Route path="/settings" element={<ProtectedRoute><SettingsPage /></ProtectedRoute>} />
<Route path="*" element={<NotFound />} />
</Routes>
</>
);
};
function App() {
return (
<Router>
<Layout />
</Router>
);
}
export default App;

View File

@ -0,0 +1,95 @@
import React from 'react'
import { motion } from "framer-motion";
import { FaPlus, FaCircle } from "react-icons/fa";
import { useNavigate } from 'react-router-dom';
export default function AdminDashboardComponent() {
const navigate = useNavigate();
return (
<>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
<motion.div
className="bg-gray-800 p-6 rounded-lg shadow-lg text-center cursor-pointer hover:bg-gray-700 transition flex items-center justify-center"
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
onClick={() => navigate("/Partners")}
>
<div
className="bg-cover bg-center h-40 rounded-lg filter blur-sm"
style={{
backgroundImage: `url('///')`,
}}
></div>
<div>
{/* <FaPlus className="text-5xl text-blue-500 mx-auto mb-4" /> */}
<p className="text-lg font-semibold">Partners</p>
</div>
</motion.div>
<motion.div
className="bg-gray-800 p-6 rounded-lg shadow-lg text-center cursor-pointer hover:bg-gray-700 transition flex items-center justify-center"
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
onClick={() => navigate("/Clients")}
>
<div
className="bg-cover bg-center h-40 rounded-lg filter blur-sm"
style={{
backgroundImage: `url('///')`,
}}
></div>
<div>
{/* <FaPlus className="text-5xl text-blue-500 mx-auto mb-4" /> */}
<p className="text-lg font-semibold">Clients</p>
</div>
</motion.div>
<motion.div
className="bg-gray-800 p-6 rounded-lg shadow-lg text-center cursor-pointer hover:bg-gray-700 transition flex items-center justify-center"
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
onClick={() => navigate("/Client-Partner-Mapping")}
>
<div
className="bg-cover bg-center h-40 rounded-lg filter blur-sm"
style={{
backgroundImage: `url('///')`,
}}
></div>
<div>
{/* <FaPlus className="text-5xl text-blue-500 mx-auto mb-4" /> */}
<p className="text-lg font-semibold">Ads Configuration</p>
</div>
</motion.div>
<motion.div
className="bg-gray-800 p-6 rounded-lg shadow-lg text-center cursor-pointer hover:bg-gray-700 transition flex items-center justify-center"
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
onClick={() => navigate("/Settings")}
>
<div
className="bg-cover bg-center h-40 rounded-lg filter blur-sm"
style={{
backgroundImage: `url('///')`,
}}
></div>
<div>
{/* <FaPlus className="text-5xl text-blue-500 mx-auto mb-4" /> */}
<p className="text-lg font-semibold">Configure General Settings</p>
</div>
</motion.div>
</div>
</>
)
}

View File

@ -0,0 +1,87 @@
import React from 'react'
import { motion } from "framer-motion";
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 ClientsCardComponent({ Data, setUpd }) {
const navigate = useNavigate();
const { setLoading } = useLoading();
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 (
<>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
<motion.div
className="bg-gray-800 p-6 rounded-lg shadow-lg text-center cursor-pointer hover:bg-gray-700 transition flex items-center justify-center"
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
onClick={() => navigate("/add-client")}
>
<div>
<FaPlus className="text-5xl text-blue-500 mx-auto mb-4" />
<p className="text-lg font-semibold">Add New Client</p>
</div>
</motion.div>
{Data?.map((client) => (
<motion.div
key={client.transid}
className="bg-gray-800 p-6 rounded-lg shadow-lg relative cursor-pointer hover:bg-gray-700 transition"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0 }}
whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.98 }}
onClick={() => navigate(`/manage-client/${client.transid}`)}
>
{/* Red Delete Button at Top Right Corner */}
<button
onClick={(e) => {
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"
>
🗑
</button>
<div
className="bg-cover bg-center h-40 rounded-lg filter blur-sm"
style={{
backgroundImage: `url('///')`,
}}
></div>
<div className="mt-4">
<h2 className="text-xl font-bold">{client.name}</h2>
</div>
</motion.div>
))}
</div>
</>
)
}

74
src/Components/Header.jsx Normal file
View File

@ -0,0 +1,74 @@
import { Link } from "react-router-dom";
const Header = () => {
return (
<header className="bg-gradient-to-r from-gray-900 via-gray-800 to-gray-700 text-white shadow-md sticky top-0 z-50">
<div className="container mx-auto flex items-center justify-between px-6 py-3">
{/* Logo and Title */}
<div className="flex items-center space-x-3">
<img src="/logo.png" alt="Logo" className="h-10 w-10 rounded-full" />
<h1 className="text-xl font-bold tracking-wide text-gray-100">
Dine 360 Ads
</h1>
</div>
{/* Navigation Menu */}
<nav>
<ul className="flex space-x-6">
<li>
<Link
to="/admin-dashboard"
className="text-gray-300 hover:text-white transition duration-300"
>
Dashboard
</Link>
</li>
<li>
<Link
to="/partners"
className="text-gray-300 hover:text-white transition duration-300"
>
Partners
</Link>
</li>
<li>
<Link
to="/clients"
className="text-gray-300 hover:text-white transition duration-300"
>
Clients
</Link>
</li>
<li>
<Link
to="Client-Partner-Mapping"
className="text-gray-300 hover:text-white transition duration-300"
>
Ads Configuration
</Link>
</li>
<li>
<Link
to="Settings"
className="text-gray-300 hover:text-white transition duration-300"
>
General Settings
</Link>
</li>
</ul>
</nav>
{/* Logout Button Styled as Link */}
<Link
to="/login"
className="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-lg transition duration-300 text-center"
>
Logout
</Link>
</div>
</header>
);
};
export default Header;

View File

@ -0,0 +1,57 @@
import React from 'react'
import { motion } from "framer-motion";
import { FaPlus, FaCircle } from "react-icons/fa";
import { api } from "../API/api";
export default function HoveringCardComponent({ Data }) {
return (
<>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
<motion.div
className="bg-gray-800 p-6 rounded-lg shadow-lg text-center cursor-pointer hover:bg-gray-700 transition flex items-center justify-center"
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
onClick={() => navigate("/add-client")}
>
<div>
<FaPlus className="text-5xl text-blue-500 mx-auto mb-4" />
<p className="text-lg font-semibold">Add New Client</p>
</div>
</motion.div>
{Data?.map((client) => (
<motion.div
key={client.id}
className="bg-gray-800 p-6 rounded-lg shadow-lg relative cursor-pointer hover:bg-gray-700 transition"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0 }}
whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.98 }}
onClick={() => navigate(`/manage-client/${client.id}`)}
>
<div
className="bg-cover bg-center h-40 rounded-lg filter blur-sm"
style={{
backgroundImage: `url('///')`,
}}
></div>
<div className="mt-4">
<h2 className="text-xl font-bold">{client.name}</h2>
<p className="text-gray-400">🕒 {client.open_time} - {client.close_time}</p>
<div className="mt-2 ml-1 flex items-center">
<FaCircle className={`mr-2 ${client.is_active ? "text-green-400" : "text-red-400"}`} />
<span> {client.is_active ? "Active" : "Inactive"}</span>
</div>
</div>
</motion.div>
))}
</div>
</>
)
}

View File

@ -0,0 +1,20 @@
import { motion } from "framer-motion";
const LoadingScreen = () => {
return (
<motion.div
className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
<motion.div
className="w-20 h-20 border-t-4 border-white border-solid rounded-full animate-spin"
animate={{ rotate: 360 }}
transition={{ repeat: Infinity, duration: 1 }}
/>
</motion.div>
);
};
export default LoadingScreen;

View File

@ -0,0 +1,87 @@
import React from 'react'
import { motion } from "framer-motion";
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, setUpd }) {
const navigate = useNavigate();
const { setLoading } = useLoading();
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 (
<>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
<motion.div
className="bg-gray-800 p-6 rounded-lg shadow-lg text-center cursor-pointer hover:bg-gray-700 transition flex items-center justify-center"
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
onClick={() => navigate("/new-ad-configuration")}
>
<div>
<FaPlus className="text-5xl text-blue-500 mx-auto mb-4" />
<p className="text-lg font-semibold">Add / Edit Ad Configuration</p>
</div>
</motion.div>
{Data?.map((client) => (
<motion.div
key={client.transid}
className="bg-gray-800 p-6 rounded-lg shadow-lg relative cursor-pointer hover:bg-gray-700 transition"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0 }}
whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.98 }}
onClick={() => navigate(`/ads-order-configuration/${client.transid}`)}
>
{/* <button
onClick={(e) => {
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"
>
🗑
</button> */}
<div
className="bg-cover bg-center h-40 rounded-lg filter blur-sm"
style={{
backgroundImage: `url('///')`,
}}
></div>
<div className="mt-4">
<h2 className="text-xl font-bold">{client.name}</h2>
</div>
</motion.div>
))}
</div>
</>
)
}

View File

@ -0,0 +1,74 @@
import React, { useEffect, useState } from "react";
import { motion } from "framer-motion";
import { useNavigate } from "react-router-dom";
import { useLoading } from "../Context/LoadingContext";
import NewMappingSubPartnersComponent from "./NewMappingSubPartnersComponent";
import { api } from "../API/api"
export default function NewMappingCardComponent({ Data, setUpd }) {
const navigate = useNavigate();
const { setLoading } = useLoading();
const [activeCard, setActiveCard] = useState(null); // Track active sub-card
const [partners, setPartners] = useState(null);
const toggleSubCard = (transid) => {
setActiveCard(activeCard === transid ? null : transid); // Toggle logic
};
useEffect(() => {
const fetchClients = async () => {
try {
setLoading(true);
const response = await api.get("/admin/partners");
setPartners(response);
} catch (error) {
console.error("Error fetching clients:", error);
} finally {
setLoading(false);
}
};
fetchClients();
}, []);
return (
<div className="grid grid-cols-1 sm:grid-cols-1 lg:grid-cols-1 gap-6">
{Data?.map((client,i) => (
<div key={client.transid} className="relative">
<motion.div
className="bg-gray-800 p-6 rounded-lg shadow-lg cursor-pointer hover:bg-gray-700 transition"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0 }}
whileHover={{ scale: 1.03 }}
// whileTap={{ scale: 0.98 }}
onClick={() => toggleSubCard(client.transid)}
>
<div
className="bg-cover bg-center h-40 rounded-lg filter blur-sm"
style={{ backgroundImage: "url('///')" }}
></div>
<div className="mt-4">
<h2 className="text-xl font-bold"> {client.name}</h2>
</div>
{activeCard === client.transid && (
<motion.div
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
transition={{ duration: 0.3 }}
className="mt-2"
>
<NewMappingSubPartnersComponent partners={partners} client={client} setUpd={setUpd} />
</motion.div>
)}
</motion.div>
</div>
))}
</div>
);
}

View File

@ -0,0 +1,177 @@
import { useEffect, useState } from "react";
import { motion } from "framer-motion";
import NewMappingSubScreensComponent from "./NewMappingSubScreensComponent";
import { useLoading } from '../Context/LoadingContext';
import { api } from "../API/api";
export default function NewMappingSubPartnersComponent({ client, partners, setUpd }) {
const [expandedPartners, setExpandedPartners] = useState({});
const { setLoading } = useLoading();
const BACKEND = 'https://ads.dine360ads.com/api//';
const [files, setFiles] = useState([]);
const [selectedPartners, setSelectedPartners] = useState([]);
const [searchTerm, setSearchTerm] = useState('');
const [showSelectedList, setShowSelectedList] = useState(false);
const [showButtonText, setShowButtonText] = useState("Show Selected Partners");
const [showButtonColor, setShowButtonColor] = useState("bg-blue-600");
const [cityFilter, setCityFilter] = useState('');
const [provinceFilter, setProvinceFilter] = useState('');
useEffect(() => {
fetchFiles();
}, []);
const fetchFiles = async () => {
try {
setLoading(true);
const response = await api.get(`/client/files/${client.transid}`);
setFiles(response);
} catch (error) {
console.error("Error fetching files:", error);
}
setLoading(false);
};
const handlePartnerSelection = (partnerId) => {
setSelectedPartners((prev) => {
if (prev.includes(partnerId)) {
return prev.filter((id) => id !== partnerId);
} else {
return [...prev, partnerId];
}
});
};
const handleShowSelected = () => {
setShowSelectedList(!showSelectedList);
setShowButtonText(showSelectedList ? "Show Selected Partners" : "Hide Selected Partners");
setShowButtonColor(showSelectedList ? "bg-blue-600" : "bg-green-600");
};
const filteredPartners = partners?.filter((partner) =>
partner.name.toLowerCase().includes(searchTerm.toLowerCase())
);
const sortedPartners = filteredPartners?.slice().sort((a, b) => {
const aSelected = selectedPartners.includes(a.transid);
const bSelected = selectedPartners.includes(b.transid);
if (aSelected && !bSelected) return -1;
if (!aSelected && bSelected) return 1;
return 0;
}).filter(partner => {
if (cityFilter && partner.city !== cityFilter) return false;
if (provinceFilter && partner.state !== provinceFilter) return false;
return true;
});
const uniqueCities = [...new Set(partners?.map(partner => partner.city).filter(Boolean))];
const uniqueProvinces = [...new Set(partners?.map(partner => partner.state).filter(Boolean))];
return (
<div className="bg-blue-900 p-4 rounded-lg shadow-md text-white" onClick={(e) => e.stopPropagation()}>
<p className="font-bold mb-3">Client Ads List</p>
<ul className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-4 p-4">
{files.length > 0 ? (
files.map((file, index) => {
const fileName = file.file_name;
const fileUrl = BACKEND + file.file_path;
const isImage = fileUrl.match(/\.(jpeg|jpg|png|gif)$/i);
const isVideo = fileUrl.match(/\.(mp4|webm|ogg|mkv)$/i);
return (
<li key={index} className="bg-gray-800 p-4 rounded-lg shadow-lg flex flex-col items-center relative">
<span className="text-gray-300 font-bold truncate w-full text-center">
{fileName}
</span>
{isImage && (<img src={fileUrl} alt={fileName} className="w-full h-40 object-cover rounded-lg mt-2" />)}
{isVideo && (<video controls className="w-full h-40 rounded-lg mt-2"><source src={fileUrl} type="video/mp4" />Your browser does not support the video tag.</video>)}
</li>
);
})
) : (
<p className="text-gray-500 col-span-full text-center">No Ads uploaded yet.</p>
)}
</ul>
<p className="font-bold mb-3">Available Partners</p>
<div className="grid grid-cols-1 md:grid-cols-3 gap-2 mb-4">
<input
type="text"
placeholder="Search Partners..."
value={searchTerm}
onChange={(e) => {
e.stopPropagation();
setSearchTerm(e.target.value);
}}
className="w-full bg-gray-700 p-2 rounded-lg text-white"
/>
<select
value={cityFilter}
onChange={(e) => setCityFilter(e.target.value)}
className="w-full bg-gray-700 p-2 rounded-lg text-white"
>
<option value="">All Cities</option>
{uniqueCities?.map(city => (
<option key={city} value={city}>{city}</option>
))}
</select>
<select
value={provinceFilter}
onChange={(e) => setProvinceFilter(e.target.value)}
className="w-full bg-gray-700 p-2 rounded-lg text-white"
>
<option value="">All Provinces</option>
{uniqueProvinces?.map(province => (
<option key={province} value={province}>{province}</option>
))}
</select>
</div>
<ul className="bg-gray-800 p-4 rounded-lg space-y-2">
{sortedPartners?.length > 0 ? (
sortedPartners?.map((partner, index) => (
<div key={index}>
<label
htmlFor={`partner-${partner.transid}`}
className="text-gray-300 flex items-start border-b border-gray-700 pb-2 space-y-2 cursor-pointer"
>
<input
type="checkbox"
id={`partner-${partner.transid}`}
checked={selectedPartners.includes(partner.transid)}
onChange={() => handlePartnerSelection(partner.transid)}
className="mr-2 mt-1"
/>
<span>{index + 1}. {partner.city} - {partner.name}</span>
</label>
</div>
))
) : (
<p className="text-gray-500">No Partners found.</p>
)}
</ul>
<button
onClick={handleShowSelected}
className={`mt-4 ${showButtonColor} hover:bg-blue-700 text-white px-6 py-2 rounded-lg font-bold block mx-auto`}
>
{showButtonText}
</button>
{showSelectedList && selectedPartners.length > 0 && (
<div className="mt-4">
<p className="font-bold mb-3">Sub Screens:</p>
{selectedPartners.map((partnerId) => {
const partner = partners.find((p) => p.transid === partnerId);
return (
<div key={partnerId} className="mb-4">
<NewMappingSubScreensComponent partner={partner} client={client} files={files} />
</div>
);
})}
</div>
)}
</div>
);
}

View File

@ -0,0 +1,96 @@
import { useEffect, useState } from "react";
import { motion } from "framer-motion";
import NewMappingSubScreensComponent from "./NewMappingSubScreensComponent"; // Ensure this exists
import { useLoading } from '../Context/LoadingContext';
import { api } from "../API/api";
export default function NewMappingSubPartnersComponent({ client, partners, setUpd }) {
const [expandedPartners, setExpandedPartners] = useState({});
const { setLoading } = useLoading();
const BACKEND = 'https://ads.dine360ads.com/api//';
const toggleSubScreens = (event, partnerId) => {
event.stopPropagation(); // Prevents event from closing parent card!
setExpandedPartners((prev) => ({
...prev,
[partnerId]: !prev[partnerId],
}));
};
const [files, setFiles] = useState([]);
useEffect(() => {
fetchFiles();
}, []);
// Fetch Client Files
const fetchFiles = async () => {
try {
setLoading(true);
const response = await api.get(`/client/files/${client.transid}`);
setFiles(response);
} catch (error) {
console.error("Error fetching files:", error);
}
setLoading(false);
};
return (
<div className="bg-blue-900 p-4 rounded-lg shadow-md text-white" onClick={(e) => e.stopPropagation()}>
<p className="font-bold mb-3">Client Ads List</p>
<ul className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-4 p-4">
{files.length > 0 ? (
files.map((file, index) => {
const fileName = file.file_name;
const fileUrl = BACKEND + file.file_path;
const isImage = fileUrl.match(/\.(jpeg|jpg|png|gif)$/i);
const isVideo = fileUrl.match(/\.(mp4|webm|ogg|mkv)$/i);
return (
<li key={index} className="bg-gray-800 p-4 rounded-lg shadow-lg flex flex-col items-center relative">
<span className="text-gray-300 font-bold truncate w-full text-center">
{fileName}
</span>
{isImage && (<img src={fileUrl} alt={fileName} className="w-full h-40 object-cover rounded-lg mt-2" />)}
{isVideo && (<video controls className="w-full h-40 rounded-lg mt-2"><source src={fileUrl} type="video/mp4" />Your browser does not support the video tag.</video>)}
</li>
);
})
) : (
<p className="text-gray-500 col-span-full text-center">No Ads uploaded yet.</p>
)}
</ul>
<p className="font-bold mb-3">Available Partners</p>
<ul className="bg-gray-800 p-4 rounded-lg space-y-2">
{partners?.length > 0 ? (
partners?.map((partner, index) => (
<div key={index}>
{/* Clickable Partner Name */}
<li
className="text-gray-300 flex flex-col border-b border-gray-700 pb-2 space-y-2 cursor-pointer hover:text-white transition"
onClick={(e) => toggleSubScreens(e, partner.transid)}
>
<span>{index + 1}. {partner.name}</span>
</li>
{/* Conditionally Show SubScreens */}
{expandedPartners[partner.transid] && (
<motion.div
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
transition={{ duration: 0.3 }}
className="mt-2 border-b border-gray-700 pb-2"
>
<NewMappingSubScreensComponent partner={partner} client={client} files={files} />
</motion.div>
)}
</div>
))
) : (
<p className="text-gray-500">No Partners are Registered Yet.</p>
)}
</ul>
</div>
);
}

View File

@ -0,0 +1,332 @@
import React, { useEffect, useState } from "react";
import { useLoading } from '../Context/LoadingContext';
import { api } from "../API/api";
import { useMemo } from "react";
export default function NewMappingSubScreensComponent({ partner, client, files }) {
const daysOfWeek = ["M", "T", "W", "Th", "F", "S", "Su"];
const { setLoading } = useLoading();
const [screens, setScreens] = useState([]);
const BACKEND = 'https://ads.dine360ads.com/api//';
const [selectedFiles, setSelectedFiles] = useState({});
const [screenSelections, setScreenSelections] = useState({});
const [FileDurations, setFileDurations] = useState({});
const [selectAllFiles, setSelectAllFiles] = useState({});
const [selectAllDays, setSelectAllDays] = useState({});
const [databaseData, setDatabaseData] = useState([]);
const [selectAllColumnFiles, setSelectAllColumnFiles] = useState(false);
const fetchScreens = async () => {
try {
setLoading(true);
const response = await api.get(`/admin/get-screen/${partner.transid}`);
setScreens(response);
} catch (error) {
console.error("Error fetching files:", error);
}
setLoading(false);
};
const fetchMappingsFromDB = async (clientId, partnerId) => {
try {
setLoading(true);
const response = await api.get(`/admin/client-partner-mappings?clientid=${clientId}&partnerid=${partnerId}`);
setDatabaseData(response);
} catch (error) {
console.error("Error fetching mappings:", error);
alert("Error fetching mappings from the database.");
} finally {
setLoading(false);
}
};
useEffect(() => {
if (client && partner) {
fetchMappingsFromDB(client.transid, partner.transid);
}
}, [client, partner]);
const handleFileSelection = (screenId, fileId) => {
setSelectedFiles(prev => ({
...prev,
[screenId]: {
...prev[screenId],
[fileId]: !prev[screenId]?.[fileId],
},
}));
};
const handleScreenCheckboxChange = (screenId, fileId, day) => {
setScreenSelections(prev => ({
...prev,
[screenId]: {
...prev[screenId],
[fileId]: {
...prev[screenId]?.[fileId],
[day]: !prev[screenId]?.[fileId]?.[day],
},
},
}));
};
const handleSelectAllFiles = (screenId, checked) => {
setSelectAllFiles(prev => ({ ...prev, [screenId]: checked }));
setSelectedFiles(prev => {
const screenFiles = files.reduce((acc, file) => ({ ...acc, [file.transid]: checked }), {});
return { ...prev, [screenId]: screenFiles };
});
};
const handleSelectAllDays = (screenId, fileId, checked) => {
setSelectAllDays(prev => ({ ...prev, [screenId]: { ...prev[screenId], [fileId]: checked } }));
setScreenSelections(prev => {
const screenDays = daysOfWeek.reduce((acc, day) => ({ ...acc, [day]: checked }), {});
return { ...prev, [screenId]: { ...prev[screenId], [fileId]: screenDays } };
});
};
const areAllFilesSelected = () =>
screens.length > 0 &&
screens.every(screen =>
files.every(file =>
selectedFiles[screen.transid]?.[file.transid] === true
)
);const allFilesSelected = useMemo(
() => areAllFilesSelected(),
[screens, files, selectedFiles]
);
const handleSelectAllColumnFiles = (checked) => {
setSelectAllColumnFiles(checked);
setSelectedFiles(prev => {
const updatedSelectedFiles = {};
screens.forEach(screen => {
updatedSelectedFiles[screen.transid] = {};
files.forEach(file => {
updatedSelectedFiles[screen.transid][file.transid] = checked;
});
});
return updatedSelectedFiles;
});
};
const handleDurationChange = (screenId, fileId, duration) => {
setFileDurations(prev => ({
...prev,
[screenId]: {
...prev[screenId],
[fileId]: duration,
},
}));
};
const handleSubmit = async (e) => {
e.preventDefault();
setLoading(true);
let finalData = [];
screens.forEach(screen => {
const filesSelectedForScreen = selectedFiles[screen.transid] || {};
Object.keys(filesSelectedForScreen).forEach(fileId => {
if (filesSelectedForScreen[fileId]) {
const file = files.find(f => f.transid === fileId);
if (!file) return;
const daysSelected = screenSelections[screen.transid]?.[fileId] || {};
const imageduration = FileDurations[screen.transid]?.[fileId] || 10;
finalData.push({
clientid: client.transid,
partnerid: partner.transid,
screenid: screen.transid,
fileid: file.transid,
file_path: file.file_path,
ismonday: daysSelected["M"] ? 1 : 0,
istuesday: daysSelected["T"] ? 1 : 0,
iswednesday: daysSelected["W"] ? 1 : 0,
isthursday: daysSelected["Th"] ? 1 : 0,
isfriday: daysSelected["F"] ? 1 : 0,
issaturday: daysSelected["S"] ? 1 : 0,
issunday: daysSelected["Su"] ? 1 : 0,
imageduration: imageduration
});
}
});
});
console.log("Final Data to Insert:", finalData);
try {
await api.post("/admin/add-client-partner-mapping", finalData);
alert("Client Partner Mapping data added successfully!");
fetchMappingsFromDB(client.transid, partner.transid);
} catch (error) {
console.error("Error adding client partner mapping:", error);
alert("Error adding client partner mapping. Please check console for details.");
}
setLoading(false);
};
useEffect(() => {
fetchScreens();
const initialSelectedFiles = {};
const initialScreenSelections = {};
const initialFileDurations = {};
databaseData?.forEach(item => {
if (!initialSelectedFiles[item.screenid]) {
initialSelectedFiles[item.screenid] = {};
}
initialSelectedFiles[item.screenid][item.fileid] = true;
if (!initialScreenSelections[item.screenid]) {
initialScreenSelections[item.screenid] = {};
}
if (!initialScreenSelections[item.screenid][item.fileid]) {
initialScreenSelections[item.screenid][item.fileid] = {};
}
initialScreenSelections[item.screenid][item.fileid]["M"] = item.ismonday === 1;
initialScreenSelections[item.screenid][item.fileid]["T"] = item.istuesday === 1;
initialScreenSelections[item.screenid][item.fileid]["W"] = item.iswednesday === 1;
initialScreenSelections[item.screenid][item.fileid]["Th"] = item.isthursday === 1;
initialScreenSelections[item.screenid][item.fileid]["F"] = item.isfriday === 1;
initialScreenSelections[item.screenid][item.fileid]["S"] = item.issaturday === 1;
initialScreenSelections[item.screenid][item.fileid]["Su"] = item.issunday === 1;
if (!initialFileDurations[item.screenid]) {
initialFileDurations[item.screenid] = {};
}
if (!initialFileDurations[item.screenid][item.fileid]) {
initialFileDurations[item.screenid][item.fileid] = {};
}
initialFileDurations[item.screenid][item.fileid] = item.imageduration;
});
setFileDurations(initialFileDurations)
console.log(initialFileDurations)
setSelectedFiles(initialSelectedFiles);
setScreenSelections(initialScreenSelections);
}, [databaseData]);
const areAllDaysChecked = (screenId, fileId) =>
daysOfWeek.every(day =>
screenSelections[screenId]?.[fileId]?.[day] === true
);
const handleDeleteClient = async (e) => {
e.preventDefault();
setLoading(true);
if (client && partner) {
fetchMappingsFromDB(client.transid, partner.transid);
} else {
alert("Client / Partner data Not Loaded Yet...");
return
}
try {
await api.delete(`/admin/delete-client-partner-mapping/${partner.transid}/${client.transid}`);
alert("Client has been removed from the partner successfully!");
} catch (error) {
console.error("Error removing client partner mapping:", error);
alert("Error removing client partner mapping. Please check console for details.");
}
setLoading(false);
};
return (
<div className="bg-gray-600 p-4 rounded-lg shadow-md text-white">
<p className="font-bold mb-3">{partner.name} - Assign Screens</p>
<div className="overflow-x-auto">
<table className="min-w-full border border-gray-500">
<thead>
<tr className="bg-gray-700">
<th className="p-2 border">Screen Name</th>
<th className="p-2 border">
<label className="flex items-center">
<input
type="checkbox"
className="mr-2"
//checked={selectAllColumnFiles}
checked={areAllFilesSelected()}
onChange={(e) => handleSelectAllColumnFiles(e.target.checked)}
/>
Ad Name
</label>
</th>
{daysOfWeek.map(day => (
<th key={day} className="p-2 border">{day}</th>
))}
</tr>
</thead>
<tbody>
{screens.map(screen => (
files.map(file => (
<tr key={`${screen.transid}-${file.transid}`} className="border">
<td className="p-2 border">{screen.name}</td>
<td className="p-2 border align-top">
<div className="flex justify-between items-start gap-2">
<div className="flex flex-col gap-2">
<label className="flex items-center">
<input
type="checkbox"
className="mr-2"
checked={selectedFiles[screen.transid]?.[file.transid] || false}
onChange={() => handleFileSelection(screen.transid, file.transid)}
/>
<span className="text-sm truncate">{file.file_name}</span>
</label>
<label className="flex items-center">
<input
type="checkbox"
className="mr-2"
//checked={selectAllDays[screen.transid]?.[file.transid] || false}
checked={areAllDaysChecked(screen.transid, file.transid)}
onChange={(e) => handleSelectAllDays(screen.transid, file.transid, e.target.checked)}
/>
<span className="text-sm">All Days</span>
</label>
</div>
<input
type="text"
value={FileDurations[screen.transid]?.[file.transid] || '10'}
onChange={(e) => handleDurationChange(screen.transid, file.transid, e.target.value)}
maxLength={3}
pattern="\d{1,3}"
inputMode="numeric"
placeholder="000"
title="Enter up to 3 digits only"
className="w-16 p-2 text-center text-sm border border-gray-300 rounded-md shadow-inner"
/>
</div>
</td>
{daysOfWeek.map(day => (
<td key={day} className="p-2 border text-center">
<input
type="checkbox"
checked={screenSelections[screen.transid]?.[file.transid]?.[day] || false}
onChange={() => handleScreenCheckboxChange(screen.transid, file.transid, day)}
/>
</td>
))}
</tr>
))
))}
</tbody>
</table>
</div>
<br />
<div>
{files.length > 0 && (
<button onClick={handleSubmit} className="mt-4 bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-lg font-bold block mx-auto">Save Ad Configuration</button>
)}
{files.length > 0 && (
<button onClick={handleDeleteClient} className="mt-4 bg-red-600 hover:bg-red-700 text-white px-6 py-2 rounded-lg font-bold block mx-auto">Delete Client Ad Configuration</button>
)}
</div>
</div>
);
}

View File

@ -0,0 +1,304 @@
// Part 1: Component Logic and State Management
import React, { useEffect, useState } from "react";
import { useLoading } from '../../../Context/LoadingContext';
import { api } from "../../../API/api";
export default function NewMappingSubScreensComponent({ partner, client, files }) {
const daysOfWeek = ["M", "T", "W", "Th", "F", "S", "Su"];
const { setLoading } = useLoading();
const [screens, setScreens] = useState([]);
const BACKEND = 'https://ads.dine360ads.com/api//';
const [selectedFiles, setSelectedFiles] = useState({});
const [screenSelections, setScreenSelections] = useState({});
const [selectAllFiles, setSelectAllFiles] = useState({});
const [selectAllDays, setSelectAllDays] = useState({});
const [databaseData, setDatabaseData] = useState([]);
const fetchScreens = async () => {
try {
setLoading(true);
const response = await api.get(`/admin/get-screen/${partner.transid}`);
setScreens(response);
} catch (error) {
console.error("Error fetching files:", error);
}
setLoading(false);
};
const fetchMappingsFromDB = async (clientId, partnerId) => {
try {
setLoading(true);
const response = await api.get(`/admin/client-partner-mappings?clientid=${clientId}&partnerid=${partnerId}`); // Use the correct API endpoint
setDatabaseData(response); // Assuming you have a state variable called databaseData and a setter function called setDatabaseData
} catch (error) {
console.error("Error fetching mappings:", error);
alert("Error fetching mappings from the database.");
} finally {
setLoading(false);
}
};
useEffect(() => {
// Assuming client.transid and partner.transid are available in your component's props or state
if (client && partner) {
fetchMappingsFromDB(client.transid, partner.transid);
}
}, [client, partner]);
const handleFileSelection = (screenId, fileId) => {
setSelectedFiles(prev => ({
...prev,
[screenId]: {
...prev[screenId],
[fileId]: !prev[screenId]?.[fileId],
},
}));
};
const handleScreenCheckboxChange = (screenId, day) => {
setScreenSelections(prev => ({
...prev,
[screenId]: {
...prev[screenId],
[day]: !prev[screenId]?.[day],
},
}));
};
const handleSelectAllFiles = (screenId, checked) => {
setSelectAllFiles(prev => ({ ...prev, [screenId]: checked }));
setSelectedFiles(prev => {
const screenFiles = files.reduce((acc, file) => ({ ...acc, [file.transid]: checked }), {});
return { ...prev, [screenId]: screenFiles };
});
};
const handleSelectAllDays = (screenId, checked) => {
setSelectAllDays(prev => ({ ...prev, [screenId]: checked }));
setScreenSelections(prev => {
const screenDays = daysOfWeek.reduce((acc, day) => ({ ...acc, [day]: checked }), {});
return { ...prev, [screenId]: screenDays };
});
};
const handleSubmit = async (e) => {
e.preventDefault();
setLoading(true);
setLoading(true);
let finalData = [];
screens.forEach(screen => {
const filesSelectedForScreen = selectedFiles[screen.transid] || {};
const daysSelected = screenSelections[screen.transid] || {};
// Check if at least one file and one day are selected
const isFileSelected = Object.values(filesSelectedForScreen).some(Boolean);
const isDaySelected = Object.values(daysSelected).some(Boolean);
if (isFileSelected && isDaySelected) {
Object.keys(filesSelectedForScreen).forEach(fileId => {
if (filesSelectedForScreen[fileId]) {
const file = files.find(f => f.transid === fileId);
if (!file) return;
finalData.push({
clientid: client.transid,
partnerid: partner.transid,
screenid: screen.transid,
fileid: file.transid,
file_path: file.file_path,
ismonday: daysSelected["M"] ? 1 : 0,
istuesday: daysSelected["T"] ? 1 : 0,
iswednesday: daysSelected["W"] ? 1 : 0,
isthursday: daysSelected["Th"] ? 1 : 0,
isfriday: daysSelected["F"] ? 1 : 0,
issaturday: daysSelected["S"] ? 1 : 0,
issunday: daysSelected["Su"] ? 1 : 0,
});
}
});
}
//if nothing is selected for the screen, or only files or only days are selected then the screen is not added to the finalData.
});
console.log("Final Data to Insert:", finalData);
setLoading(true);
try {
await api.post("/admin/add-client-partner-mapping", finalData); // Use your API helper and the correct endpoint
alert("Client Partner Mapping data added successfully!");
fetchMappingsFromDB()
// Optionally, you might want to navigate or refresh data here.
// navigate("/mappings"); // Example navigation
} catch (error) {
console.error("Error adding client partner mapping:", error);
alert("Error adding client partner mapping. Please check console for details.");
}
setLoading(false);
setLoading(false);
};
useEffect(() => {
fetchScreens();
// Initialize state from databaseData
const initialSelectedFiles = {};
const initialScreenSelections = {};
databaseData?.forEach(item => {
if (!initialSelectedFiles[item.screenid]) {
initialSelectedFiles[item.screenid] = {};
}
if (!initialSelectedFiles[item.screenid][item.fileid]) {
initialSelectedFiles[item.screenid][item.fileid] = true;
}
if (!initialScreenSelections[item.screenid]) {
initialScreenSelections[item.screenid] = {};
}
initialScreenSelections[item.screenid]["M"] = item.ismonday === 1;
initialScreenSelections[item.screenid]["T"] = item.istuesday === 1;
initialScreenSelections[item.screenid]["W"] = item.iswednesday === 1;
initialScreenSelections[item.screenid]["Th"] = item.isthursday === 1;
initialScreenSelections[item.screenid]["F"] = item.isfriday === 1;
initialScreenSelections[item.screenid]["S"] = item.issaturday === 1;
initialScreenSelections[item.screenid]["Su"] = item.issunday === 1;
});
setSelectedFiles(initialSelectedFiles);
setScreenSelections(initialScreenSelections);
}, [databaseData]);
// Part 2: JSX Rendering
return (
<div className="bg-gray-600 p-4 rounded-lg shadow-md text-white">
<p className="font-bold mb-3">{partner.name} - Assign Screens</p>
<div className="overflow-x-auto">
<table className="min-w-full border border-gray-500">
<thead>
<tr className="bg-gray-700">
<th className="p-2 border">Screen Name</th>
<th className="p-2 border">Ads List</th>
{daysOfWeek.map(day => (
<th key={day} className="p-2 border">{day}</th>
))}
</tr>
</thead>
<tbody>
{screens.map(screen => (
<tr key={screen.transid} className="border">
<td className="p-2 border">
{screen.name}
<label className="flex items-center mt-2">
<br></br>
<input
type="checkbox"
className="mr-2"
checked={selectAllDays[screen.transid] || false}
onChange={(e) => handleSelectAllDays(screen.transid, e.target.checked)}
/>
All Days
</label>
</td>
<td className="p-2 border">
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-2">
<label className="flex items-center">
<input
type="checkbox"
className="mr-2"
checked={selectAllFiles[screen.transid] || false}
onChange={(e) => handleSelectAllFiles(screen.transid, e.target.checked)}
/>
Select All
</label>
{files.map(file => (
<label key={file.transid} className="flex items-center">
<input
type="checkbox"
className="mr-2"
checked={selectedFiles[screen.transid]?.[file.transid] || false}
onChange={() => handleFileSelection(screen.transid, file.transid)}
/>
<span className="text-sm truncate">{file.file_name}</span>
</label>
))}
</div>
</td>
{daysOfWeek.map(day => (
<td key={day} className="p-2 border text-center">
<input
type="checkbox"
checked={screenSelections[screen.transid]?.[day] || false}
onChange={() => handleScreenCheckboxChange(screen.transid, day)}
/>
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
<br />
<div>
<p className="font-bold mb-3">Client Ads List</p>
<ul className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-4 p-4">
{files.length > 0 ? (
files.map((file, index) => {
const fileName = file.file_name;
const fileUrl = BACKEND + file.file_path;
const isImage = fileUrl.match(/\.(jpeg|jpg|png|gif)$/i);
const isVideo = fileUrl.match(/\.(mp4|webm|ogg|mkv)$/i);
return (
<li
key={index}
className="bg-gray-800 p-4 rounded-lg shadow-lg flex flex-col items-center relative"
>
<span className="text-gray-300 font-bold truncate w-full text-center">
{fileName}
</span>
{isImage && (
<img
src={fileUrl}
alt={fileName}
className="w-full h-40 object-cover rounded-lg mt-2"
/>
)}
{isVideo && (
<video controls className="w-full h-40 rounded-lg mt-2">
<source
src={fileUrl} type="video/mp4" />
Your browser does not support the video tag.
</video>
)}
</li>
);
})
) : (
<p className="text-gray-500 col-span-full text-center">
No Ads uploaded yet.
</p>
)}
</ul>
{files.length > 0 && (
<button
onClick={handleSubmit}
className="mt-4 bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-lg font-bold block mx-auto"
>
Save Ad Configuration
</button>
)}
</div>
</div>
);
}

View File

@ -0,0 +1,193 @@
import React, { useEffect, useState } from "react";
import { useLoading } from '../Context/LoadingContext';
import { api } from "../API/api";
export default function NewMappingSubScreensComponent({ partner, client, files }) {
const daysOfWeek = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"];
const { setLoading } = useLoading();
const [screens, setScreens] = useState([]);
const fetchScreens = async () => {
try {
setLoading(true);
const response = await api.get(`/admin/get-screen/${partner.transid}`);
setScreens(response);
} catch (error) {
console.error("Error fetching files:", error);
}
setLoading(false)
};
const screens1 = [
{ id: 1, name: "Screen A" },
{ id: 2, name: "Screen B" },
{ id: 3, name: "Screen C" },
]; // Replace with actual screen names if dynamic
const BACKEND = 'https://ads.dine360ads.com/api//';
const [selectedFiles, setSelectedFiles] = useState([]);
const [screenSelections, setScreenSelections] = useState({}); // Stores selected checkboxes
// Handle file checkbox selection
const handleFileSelection = (fileId) => {
setSelectedFiles((prev) =>
prev.includes(fileId) ? prev.filter((id) => id !== fileId) : [...prev, fileId]
);
};
// Handle weekday checkbox selection for screens
const handleScreenCheckboxChange = (screenId, day) => {
setScreenSelections((prev) => ({
...prev,
[screenId]: {
...prev[screenId],
[day]: !prev[screenId]?.[day], // Toggle checkbox
},
}));
};
// Handle Submit - Collect data to insert into database
const handleSubmit = () => {
if (selectedFiles.length === 0) {
alert("No files selected!");
return;
}
let finalData = [];
selectedFiles.forEach((fileId) => {
const file = files.find((f) => f.transid === fileId);
if (!file) return;
screens.forEach((screen) => {
const daysSelected = screenSelections[screen.transid] || {};
finalData.push({
clientid: client.transid,
partnerid: partner.transid,
screenid: screen.transid,
fileid: file.transid,
file_path: file.file_path,
ismonday: daysSelected["Monday"] ? 1 : 0,
istuesday: daysSelected["Tuesday"] ? 1 : 0,
iswednesday: daysSelected["Wednesday"] ? 1 : 0,
isthursday: daysSelected["Thursday"] ? 1 : 0,
isfriday: daysSelected["Friday"] ? 1 : 0,
issaturday: daysSelected["Saturday"] ? 1 : 0,
issunday: daysSelected["Sunday"] ? 1 : 0,
});
});
});
console.log("Final Data to Insert:", finalData);
alert("Data formatted successfully! Check console for details.");
};
useEffect(() => {
fetchScreens()
}, []);
return (
<div className="bg-gray-600 p-4 rounded-lg shadow-md text-white">
<p className="font-bold mb-3">{partner.name} - Assign Screens</p>
<div className="overflow-x-auto">
<table className="min-w-full border border-gray-500">
<thead>
<tr className="bg-gray-700">
<th className="p-2 border">Screen Name</th>
<th className="p-2 border">Ads List</th>
{daysOfWeek.map((day) => (
<th key={day} className="p-2 border">{day}</th>
))}
</tr>
</thead>
<tbody>
{screens.map((screen) => (
<tr key={screen.transid} className="border">
<td className="p-2 border">{screen.name}</td>
<td className="p-2 border">{screen.adslist}</td>
{daysOfWeek.map((day) => (
<td key={day} className="p-2 border text-center">
<input
type="checkbox"
checked={screenSelections[screen.transid]?.[day] || false}
onChange={() => handleScreenCheckboxChange(screen.transid, day)}
/>
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
<br />
{/* File Selection */}
<div>
<p className="font-bold mb-3">Client Ads List</p>
<ul className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 p-4">
{files.length > 0 ? (
files.map((file, index) => {
const fileName = file.file_name;
const fileUrl = BACKEND + file.file_path;
const isImage = fileUrl.match(/\.(jpeg|jpg|png|gif)$/i);
const isVideo = fileUrl.match(/\.(mp4|webm|ogg|mkv)$/i);
return (
<li
key={index}
className="bg-gray-800 p-4 rounded-lg shadow-lg flex flex-col items-center relative"
>
{/* Checkbox at Top Left */}
<input
type="checkbox"
className="absolute top-5 left-3 w-5 h-5"
checked={selectedFiles.includes(file.transid)}
onChange={() => handleFileSelection(file.transid)}
/>
<span className="text-gray-300 font-bold truncate ml-10 pr-3 w-full text-right">
{fileName}
</span>
{isImage && (
<img
src={fileUrl}
alt={fileName}
className="w-full h-40 object-cover rounded-lg mt-2"
/>
)}
{isVideo && (
<video controls className="w-full h-40 rounded-lg mt-2">
<source src={fileUrl} type="video/mp4" />
Your browser does not support the video tag.
</video>
)}
</li>
);
})
) : (
<p className="text-gray-500 col-span-full text-center">
No Ads uploaded yet.
</p>
)}
</ul>
{files.length > 0 && (
<button
onClick={handleSubmit}
className="mt-4 bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-lg font-bold block mx-auto"
>
Save Ad Configuration
</button>
)}
</div>
</div>
);
}

View File

@ -0,0 +1,87 @@
import React from 'react'
import { motion } from "framer-motion";
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 ClientsCardComponent({ Data,setUpd }) {
const navigate = useNavigate();
const { setLoading } = useLoading();
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 (
<>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
<motion.div
className="bg-gray-800 p-6 rounded-lg shadow-lg text-center cursor-pointer hover:bg-gray-700 transition flex items-center justify-center"
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
onClick={() => navigate("/add-client")}
>
<div>
<FaPlus className="text-5xl text-blue-500 mx-auto mb-4" />
<p className="text-lg font-semibold">Add New Client</p>
</div>
</motion.div>
{Data?.map((client) => (
<motion.div
key={client.transid}
className="bg-gray-800 p-6 rounded-lg shadow-lg relative cursor-pointer hover:bg-gray-700 transition"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0 }}
whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.98 }}
onClick={() => navigate(`/manage-client/${client.transid}`)}
>
{/* Red Delete Button at Top Right Corner */}
<button
onClick={(e) => {
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"
>
🗑
</button>
<div
className="bg-cover bg-center h-40 rounded-lg filter blur-sm"
style={{
backgroundImage: `url('///')`,
}}
></div>
<div className="mt-4">
<h2 className="text-xl font-bold">{client.name}</h2>
</div>
</motion.div>
))}
</div>
</>
)
}

View File

@ -0,0 +1,92 @@
import React from 'react'
import { motion } from "framer-motion";
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 PartnersCardComponent({ Data, setUpd }) {
const navigate = useNavigate();
const { setLoading } = useLoading();
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 (
<>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
<motion.div
className="bg-gray-800 p-6 rounded-lg shadow-lg text-center cursor-pointer hover:bg-gray-700 transition flex items-center justify-center"
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
onClick={() => navigate("/add-partner")}
>
<div>
<FaPlus className="text-5xl text-blue-500 mx-auto mb-4" />
<p className="text-lg font-semibold">Add New Partner</p>
</div>
</motion.div>
{Data?.map((client) => (
<motion.div
key={client.transid}
className="bg-gray-800 p-6 rounded-lg shadow-lg relative cursor-pointer hover:bg-gray-700 transition"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0 }}
whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.98 }}
onClick={() => navigate(`/manage-partner/${client.transid}`)}
>
{/* Red Delete Button at Top Right Corner */}
<button
onClick={(e) => {
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"
>
🗑
</button>
<div
className="bg-cover bg-center h-40 rounded-lg "
style={{
backgroundImage: `url('https://ads.dine360ads.com/api/${client.logo_url || ""}')`,
}}
></div>
<div className="mt-4">
<h2 className="text-xl font-bold">{client.name}</h2>
<p className="text-gray-400">🕒 {client.open_time} - {client.close_time}</p>
{/* <div className="mt-2 ml-1 flex items-center">
<FaCircle className={`mr-2 ${client.is_active ? "text-green-400" : "text-red-400"}`} />
<span> {client.is_active ? "Active" : "Inactive"}</span>
</div> */}
</div>
</motion.div>
))}
</div>
</>
)
}

View File

@ -0,0 +1,131 @@
import React, { useEffect, useState } from "react";
import { useLoading } from '../Context/LoadingContext';
import { api } from "../API/api";
export default function ViewPartnerAdsConfiguration({ data }) {
const daysOfWeek = ["M", "T", "W", "Th", "F", "S", "Su"];
const { setLoading } = useLoading();
const [screens, setScreens] = useState([]);
const [selectedFiles, setSelectedFiles] = useState({});
const [screenSelections, setScreenSelections] = useState({});
const [selectAllFiles, setSelectAllFiles] = useState({});
const [selectAllDays, setSelectAllDays] = useState({});
const [selectAllColumnFiles, setSelectAllColumnFiles] = useState(false);
// Extract unique screens from the data prop
useEffect(() => {
if (data) {
console.log(data)
const uniqueScreens = [...new Map(data.map(item => [item.screenid, {
transid: item.screenid,
name: item.screenname || item.screenid // Use screenname if available, otherwise screenid
}])).values()];
console.log(uniqueScreens)
setScreens(uniqueScreens);
}
}, [data]);
useEffect(() => {
if (data) {
const initialSelectedFiles = {};
const initialScreenSelections = {};
data.forEach(item => {
if (!initialSelectedFiles[item.screenid]) {
initialSelectedFiles[item.screenid] = {};
}
initialSelectedFiles[item.screenid][item.fileid] = true;
if (!initialScreenSelections[item.screenid]) {
initialScreenSelections[item.screenid] = {};
}
if (!initialScreenSelections[item.screenid][item.fileid]) {
initialScreenSelections[item.screenid][item.fileid] = {};
}
initialScreenSelections[item.screenid][item.fileid]["M"] = item.ismonday === 1;
initialScreenSelections[item.screenid][item.fileid]["T"] = item.istuesday === 1;
initialScreenSelections[item.screenid][item.fileid]["W"] = item.iswednesday === 1;
initialScreenSelections[item.screenid][item.fileid]["Th"] = item.isthursday === 1;
initialScreenSelections[item.screenid][item.fileid]["F"] = item.isfriday === 1;
initialScreenSelections[item.screenid][item.fileid]["S"] = item.issaturday === 1;
initialScreenSelections[item.screenid][item.fileid]["Su"] = item.issunday === 1;
});
setSelectedFiles(initialSelectedFiles);
setScreenSelections(initialScreenSelections);
}
}, [data]);
return (
<div className="bg-gray-600 p-4 rounded-lg shadow-md text-white">
{/* // <p className="font-bold text-2xl mb-3"> Ad Configurations</p> */}
{screens.map(screen => {
console.log(screen)
const filesForScreen = data.filter(item => item.screenid === screen.transid);
const uniqueFilesForScreen = [...new Set(filesForScreen.map(item => ({
transid: item.fileid,
file_name: item.file_path.split('/').pop() // Extract filename from path
})))];
return (
<div key={screen.transid} className="mb-6">
<h3 className="text-lg font-semibold mb-2">{screen.name} </h3>
<div className="overflow-x-auto">
<table className="min-w-full border border-gray-500">
<thead>
<tr className="bg-gray-700">
<th className="p-2 border">Ad Name</th>
{daysOfWeek.map(day => (
<th key={day} className="p-2 border">{day}</th>
))}
</tr>
</thead>
<tbody>
{uniqueFilesForScreen.map(file => {
// Find the first occurrence of this file for this screen to get initial day states
const fileData = filesForScreen.find(item => item.fileid === file.transid);
if (!fileData) return null; // Should not happen if uniqueFilesForScreen is correct
return (
<tr key={`${screen.transid}-${file.transid}`} className="border">
<td className="p-2 border">
<label className="flex items-center">
{/* <input
type="checkbox"
className="mr-2"
checked={selectedFiles[screen.transid]?.[file.transid] || false}
onChange={() => null}
/> */}
<span className="text-sm truncate">[ {fileData.clientname} ] - {file.file_name.split("_")[1]}</span>
</label>
</td>
{daysOfWeek.map(day => (
<td key={day} className="p-2 border text-center">
<input
type="checkbox"
checked={screenSelections[screen.transid]?.[file.transid]?.[day] || (fileData[`is${day.toLowerCase()}`] === 1)}
onChange={() => null}
/>
</td>
))}
</tr>
);
})}
</tbody>
</table>
</div>
</div>
);
})}
<br />
</div>
);
}

View File

@ -0,0 +1,17 @@
import { createContext, useState, useContext } from "react";
import LoadingScreen from "../Components/LoadingScreen";
const LoadingContext = createContext();
export const LoadingProvider = ({ children }) => {
const [loading, setLoading] = useState(false);
return (
<LoadingContext.Provider value={{ loading, setLoading }}>
{loading && <LoadingScreen />} {/* Show loading screen when loading is true */}
{children}
</LoadingContext.Provider>
);
};
export const useLoading = () => useContext(LoadingContext);

129
src/Pages/AddClient.jsx Normal file
View File

@ -0,0 +1,129 @@
import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { motion } from "framer-motion";
import { FaArrowLeft } from "react-icons/fa";
import { api } from "../API/api";
import { useLoading } from "../Context/LoadingContext";
const AddClient = () => {
const [formData, setFormData] = useState({ name: "", emailid: "", phoneno: "", address: "" });
const { setLoading } = useLoading();
const navigate = useNavigate();
const handleChange = (e) => {
const { name, value } = e.target;
setFormData({ ...formData, [name]: value });
};
const handleFileChange = (e) => {
setFormData({ ...formData, image: e.target.files[0] });
};
const handleSubmit = async (e) => {
e.preventDefault();
setLoading(true);
try {
await api.post("/admin/add-client", formData);
navigate("/clients");
} catch (error) {
console.error("Error adding Partner:", error);
}
setLoading(false);
};
return (
<div className="min-h-screen bg-gray-900 text-white p-8">
<motion.button
className="mb-4 flex items-center text-gray-400 hover:text-white"
whileHover={{ scale: 1.1 }}
onClick={() => navigate("/clients")}
>
<FaArrowLeft className="mr-2" /> Back
</motion.button>
<motion.h1 className="text-3xl font-bold mb-6"> Add New Client</motion.h1>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="name" className="block text-sm font-medium text-gray-700">Client Name</label>
<input
type="text"
name="name"
id="name"
placeholder="Client Name"
onChange={handleChange}
className="block w-full bg-gray-700 p-2 rounded-lg mb-2"
required
minLength="1" // Example: Add minimum length validation
maxLength="255" //Example: add maximum length validation
/>
{/*
<label htmlFor="logo_url" className="block text-sm font-medium text-gray-700">Logo</label>
<input
type="file"
accept="image/*"
name="logo_url"
id="logo_url"
onChange={handleFileChange}
className="block w-full bg-gray-700 p-2 rounded-lg mb-2"
required
/> */}
<label htmlFor="emailid" className="block text-sm font-medium text-gray-700">E-Mail ID</label>
<input
type="email"
name="emailid"
id="emailid"
placeholder="E-Mail ID"
onChange={handleChange}
className="block w-full bg-gray-700 p-2 rounded-lg mb-2"
required
minLength="1" // Example: Add minimum length validation
maxLength="255" //Example: add maximum length validation
/>
<label htmlFor="phoneno" className="block text-sm font-medium text-gray-700">Phone Number</label>
<input
type="number"
name="phoneno"
id="phoneno"
placeholder="Phone Number"
onChange={handleChange}
className="block w-full bg-gray-700 p-2 rounded-lg mb-2"
required
minLength="1" // Example: Add minimum length validation
maxLength="255" //Example: add maximum length validation
/>
<label htmlFor="address" className="block text-sm font-medium text-gray-700">Address</label>
<input
type="text"
name="address"
id="address"
placeholder="Address"
onChange={handleChange}
className="block w-full bg-gray-700 p-2 rounded-lg mb-2"
required
/>
</div>
<motion.button
type="submit"
className="bg-blue-500 px-4 py-2 rounded-lg"
whileHover={{ scale: 1.05 }}
>
Submit
</motion.button>
</form>
</div>
);
};
export default AddClient

310
src/Pages/AddPartner.jsx Normal file
View File

@ -0,0 +1,310 @@
import { useMemo, useState } from "react";
import { useNavigate } from "react-router-dom";
import { motion } from "framer-motion";
import { FaArrowLeft } 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';
const AddPartner = () => {
const [uploading, setUploading] = useState(false);
const [formData, setFormData] = useState({
name: "",
open_time: "",
close_time: "",
image: null,
address: "",
city: "",
state: "",
pincode: "",
screens: "",
});
const { setLoading } = useLoading();
const navigate = useNavigate();
const handleChange = (e) => {
const { name, value } = e.target;
setFormData({ ...formData, [name]: value });
};
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
try {
console.log(formData)
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)
}
};
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);
}
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 (
<div className="min-h-screen bg-gray-900 text-white p-8">
<motion.button
className="mb-4 flex items-center text-gray-400 hover:text-white"
whileHover={{ scale: 1.1 }}
onClick={() => navigate("/admin-dashboard")}
>
<FaArrowLeft className="mr-2" /> Back
</motion.button>
<motion.h1 className="text-3xl font-bold mb-6"> Add New Partner</motion.h1>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="name" className="block text-sm font-medium text-gray-700">
Partner Name
</label>
<input
type="text"
name="name"
id="name"
placeholder="Partner Name"
onChange={handleChange}
className="block w-full bg-gray-700 p-2 rounded-lg mb-2"
required
minLength="1"
maxLength="255"
/>
<label htmlFor="logo_url" className="block text-sm font-medium text-gray-700">
Logo
</label>
<input
type="file"
accept="image/*"
name="logo_url"
id="logo_url"
onChange={handleFileChange}
className="block w-full bg-gray-700 p-2 rounded-lg mb-2"
/>
<label htmlFor="open_time" className="block text-sm font-medium text-gray-700">
Open Time
</label>
<input
type="time"
name="open_time"
id="open_time"
onChange={handleChange}
className="block w-full bg-gray-700 p-2 rounded-lg mb-2"
required
/>
<label htmlFor="close_time" className="block text-sm font-medium text-gray-700">
Close Time
</label>
<input
type="time"
name="close_time"
id="close_time"
onChange={handleChange}
className="block w-full bg-gray-700 p-2 rounded-lg mb-2"
required
/>
<label htmlFor="address" className="block text-sm font-medium text-gray-700">
Address
</label>
<input
type="text"
name="address"
id="address"
placeholder="Address"
onChange={handleChange}
className="block w-full bg-gray-700 p-2 rounded-lg mb-2"
required
/>
<label htmlFor="state" className="block text-sm font-medium text-gray-700">
Province
</label>
<Select
options={provinceOptions}
value={selectedProvince}
onChange={handleProvinceChange}
isSearchable
className="block w-full mb-2"
styles={{
control: (provided) => ({
...provided,
backgroundColor: '#374151',
color: 'white',
borderRadius: '0.5rem',
padding: '0.5rem',
border: 'none',
}),
option: (provided, state) => ({
...provided,
backgroundColor: state.isSelected ? '#1f2937' : '#374151',
color: 'white',
}),
singleValue: (provided) => ({
...provided,
color: 'white',
}),
input: (provided) => ({
...provided,
color: 'white',
}),
placeholder: (provided) => ({
...provided,
color: 'white',
}),
}}
placeholder="Select Province"
/>
<label htmlFor="city" className="block text-sm font-medium text-gray-700">
City
</label>
<Select
options={cityOptions}
value={selectedCity}
onChange={handleCityChange}
isSearchable
className="block w-full mb-2"
styles={{
control: (provided) => ({
...provided,
backgroundColor: '#374151',
color: 'white',
borderRadius: '0.5rem',
padding: '0.5rem',
border: 'none',
}),
option: (provided, state) => ({
...provided,
backgroundColor: state.isSelected ? '#1f2937' : '#374151',
color: 'white',
}),
singleValue: (provided) => ({
...provided,
color: 'white',
}),
input: (provided) => ({
...provided,
color: 'white',
}),
placeholder: (provided) => ({
...provided,
color: 'white',
}),
}}
placeholder="Select City"
isDisabled={!selectedProvince}
/>
<label htmlFor="pincode" className="block text-sm font-medium text-gray-700">
Pincode
</label>
<input
type="text"
name="pincode"
id="pincode"
placeholder="Pincode"
onChange={handleChange}
className="block w-full bg-gray-700 p-2 rounded-lg mb-2"
required
/>
<label htmlFor="screens" className="block text-sm font-medium text-gray-700">
No. of. Screens
</label>
<input
type="number"
name="screens"
id="screens"
placeholder="No. of. Screens"
onChange={handleChange}
className="block w-full bg-gray-700 p-2 rounded-lg mb-2"
required
min="1"
/>
</div>
<motion.button
type="submit"
className="bg-blue-500 px-4 py-2 rounded-lg"
whileHover={{ scale: 1.05 }}
>
Submit
</motion.button>
</form>
</div>
);
};
export default AddPartner;

View File

@ -0,0 +1,55 @@
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();
// useEffect(() => {
// const fetchClients = async () => {
// try {
// setLoading(true);
// const response = await api.get("/admin/clients");
// 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 (
<div className="min-h-screen bg-gray-900 text-white p-8">
<motion.h1
className="text-3xl font-bold text-center mb-8"
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
>
🏢 Admin Dashboard - Client List
</motion.h1>
<div >
<AdminDashboardComponent />
</div>
</div>
);
};
export default AdminDashboard;

131
src/Pages/Ads copy 2.jsx Normal file
View File

@ -0,0 +1,131 @@
import { useEffect, useState, useRef } from "react";
import { useParams } from "react-router-dom";
import { motion, AnimatePresence } from "framer-motion";
import { api } from "../API/api";
const AdsPage = () => {
const { partnerid, screenName } = useParams();
const [ads, setAds] = useState([]);
const [idx, setIdx] = useState(0);
const vidRef = useRef(null);
const [isVidPlay, setVidPlay] = useState(false);
const [adsLoaded, setAdsLoaded] = useState(false);
const [cycleCount, setCycleCount] = useState(0);
useEffect(() => {
(async () => {
try {
console.log("Fetching New Ads List");
alert("Fetching new ads list...");
setAds([]);
const fetchedAds = await api.get(`/client/ads/${partnerid}/${screenName}`);
console.log("Fetched Ads:", fetchedAds);
alert(`Fetched ${fetchedAds.length} ads`);
const sortedAds = fetchedAds.sort((a, b) => a.transid - b.transid);
setAds(sortedAds);
} catch (e) {
console.error("Ads error:", e);
alert("Error fetching ads: " + e.message);
}
setAdsLoaded(true);
})();
}, [partnerid, screenName, cycleCount]);
useEffect(() => {
if (!adsLoaded || !ads.length) return;
const ad = ads[idx];
alert(`Displaying ad ${idx + 1}/${ads.length}: ${ad.file_path}`);
const isVid = /\.(mp4|webm|ogg|mkv)$/i.test(ad.file_path);
let timer;
if (isVid) {
alert(`🟢 Preparing to play video: ${ad.file_path}`);
setVidPlay(true);
if (vidRef.current) {
vidRef.current.src = `https://ads.dine360ads.com/api/${ad.file_path}`;
vidRef.current.load();
vidRef.current
.play()
.then(() => {
alert(`✅ Video started playing: ${ad.file_path}`);
})
.catch((err) => {
alert(`❌ Video failed to play: ${err.message}`);
});
}
} else {
setVidPlay(false);
alert(`🖼️ Showing image: ${ad.file_path}`);
timer = setTimeout(() => {
if (idx === ads.length - 1) {
setCycleCount((prev) => prev + 1);
}
setIdx((p) => (p + 1) % ads.length);
}, 5000);
}
return () => clearTimeout(timer);
}, [ads, idx, adsLoaded]);
const vidEnd = () => {
alert("🔁 Video ended, moving to next ad...");
setVidPlay(false);
setIdx((p) => (p + 1) % ads.length);
};
const handleVideoError = () => {
alert("❗ Video playback error! Skipping to next ad.");
setVidPlay(false);
setIdx((p) => (p + 1) % ads.length);
};
const handleVideoPause = () => {
alert("⏸️ Video paused.");
};
const handleVideoPlay = () => {
alert("▶️ Video is playing.");
};
const handleVideoLoaded = () => {
alert("🔄 Video loaded successfully.");
};
return (
<div className="h-screen w-screen bg-black flex items-center justify-center overflow-hidden">
<AnimatePresence mode="wait">
{adsLoaded && ads.length > 0 && (
isVidPlay ? (
<video
key={ads[idx].file_path}
ref={vidRef}
className="w-full h-full object-contain absolute"
autoPlay
src={`https://ads.dine360ads.com/api//${ads[idx].file_path}`}
muted
onEnded={vidEnd}
onError={handleVideoError}
onPause={handleVideoPause}
onPlay={handleVideoPlay}
onLoadedData={handleVideoLoaded}
/>
) : (
<motion.img
key={ads[idx].file_path}
src={`https://ads.dine360ads.com/api/${ads[idx].file_path}`}
className="w-full h-full object-contain absolute"
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 1.1 }}
transition={{ duration: 1 }}
/>
)
)}
</AnimatePresence>
</div>
);
};
export default AdsPage;

162
src/Pages/Ads.jsx Normal file
View File

@ -0,0 +1,162 @@
import { useEffect, useState, useRef } from "react";
import { useParams } from "react-router-dom";
import { motion, AnimatePresence } from "framer-motion";
import { api } from "../API/api";
const AdsPage = () => {
const { partnerid, screenName } = useParams();
const [ads, setAds] = useState([]);
const [idx, setIdx] = useState(0);
const vidRef = useRef(null);
const [isVidPlay, setVidPlay] = useState(false);
const [adsLoaded, setAdsLoaded] = useState(false);
const [cycleCount, setCycleCount] = useState(0);
const [delay, setDelay] = useState(1); // Default delay
// const [imageDuration, setImageDuration] = useState(5); // No longer a common default
useEffect(() => {
(async () => {
try {
console.log('Fetching New Files List');
setAds([]);
const fetchedAds = await api.get(`/client/ads/${partnerid}/${screenName}`);
const sortedAds = fetchedAds.sort((a, b) => a.transid - b.transid);
console.log(sortedAds);
setAds(sortedAds);
} catch (e) {
console.error("Ads error:", e);
}
setAdsLoaded(true);
})();
// Fetch settings (delay still needed for logging interval)
(async () => {
try {
const settings = await api.get("/client/get-settings");
if (settings && settings.length >= 2) {
setDelay(parseInt(settings[1].value, 10) || 1);
// setImageDuration is no longer needed here
}
} catch (e) {
console.error("Settings error:", e);
}
})();
}, [partnerid, screenName, cycleCount]);
useEffect(() => {
if (!adsLoaded || !ads.length) return;
const ad = ads[idx];
const isVid = /\.(mp4|webm|ogg|mkv)$/i.test(ad.file_path);
let timer;
if (isVid) {
setVidPlay(true);
vidRef.current?.play().catch(() => {});
} else {
setVidPlay(false);
// Use the image duration specific to the current ad
const currentImageDuration = ad.imageduration !== undefined ? parseInt(ad.imageduration, 10) : 5; // Default to 5 if not present
timer = setTimeout(() => {
if (idx === ads.length - 1) {
setCycleCount((prev) => prev + 1);
}
setIdx((p) => (p + 1) % ads.length);
}, currentImageDuration * 1000);
}
return () => clearTimeout(timer);
}, [ads, idx, adsLoaded]); // Removed imageDuration from dependency array
const vidEnd = () => {
setVidPlay(false);
setIdx((p) => (p + 1) % ads.length);
};
const logAd = async () => {
if (!adsLoaded || !ads.length) return console.log("Ads skip log.");
try {
if (ads[idx]) {
await api.post("/client/add-screen-log", {
screen_id: ads[0]?.screenid,
screen_name: screenName,
partner_id: ads[0]?.partnerid,
status: "active",
});
console.log("Logged");
} else console.log("Ad idx out.");
} catch (e) {
console.error("Log error:", e);
}
};
useEffect(() => {
if (!adsLoaded) return;
const logTimer = setInterval(() => {
if (!document.hidden) {
logAd();
} else {
console.log("Skipped logging: Tab is inactive");
}
}, 60 * delay * 1000);
return () => clearInterval(logTimer);
}, [adsLoaded, delay]);
useEffect(() => {
const handleVisibilityChange = async () => {
if (document.hidden) {
try {
console.log("User minimized or switched tab");
await api.post("/client/idle-screen-log", {
screen_id: ads[0]?.screenid,
screen_name: screenName,
partner_id: ads[0]?.partnerid,
status: "inactive",
});
} catch (e) {
console.error("Visibility Log error:", e);
}
} else {
console.log("User visible");
logAd();
}
};
document.addEventListener("visibilitychange", handleVisibilityChange);
return () => document.removeEventListener("visibilitychange", handleVisibilityChange);
}, [ads, adsLoaded, screenName]);
return (
<div className="h-screen w-screen bg-black flex items-center justify-center overflow-hidden">
<AnimatePresence mode="wait">
{adsLoaded && ads.length > 0 && (
isVidPlay ? (
<video
key={ads[idx].file_path}
ref={vidRef}
src={`https://ads.dine360ads.com/api//${ads[idx].file_path}`}
className="w-full h-full object-contain absolute"
autoPlay
muted
onEnded={vidEnd}
/>
) : (
<motion.img
key={ads[idx].file_path}
src={`https://ads.dine360ads.com/api//${ads[idx].file_path}`}
className="w-full h-full object-contain absolute"
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 1.1 }}
transition={{ duration: 1 }}
/>
)
)}
</AnimatePresence>
</div>
);
};
export default AdsPage;

View File

@ -0,0 +1,159 @@
import { useEffect, useState, useRef } from "react";
import { useParams } from "react-router-dom";
import { motion, AnimatePresence } from "framer-motion";
import { api } from "../API/api";
const AdsPage = () => {
const { partnerid, screenName } = useParams();
const [ads, setAds] = useState([]);
const [idx, setIdx] = useState(0);
const vidRef = useRef(null);
const [isVidPlay, setVidPlay] = useState(false);
const [adsLoaded, setAdsLoaded] = useState(false);
const [cycleCount, setCycleCount] = useState(0);
const [delay, setDelay] = useState(1); // Default delay
const [imageDuration, setImageDuration] = useState(5); // Default image duration
useEffect(() => {
(async () => {
try {
console.log('Fetching New Files List');
setAds([]);
const fetchedAds = await api.get(`/client/ads/${partnerid}/${screenName}`);
const sortedAds = fetchedAds.sort((a, b) => a.transid - b.transid);
console.log(sortedAds);
setAds(sortedAds);
} catch (e) {
console.error("Ads error:", e);
}
setAdsLoaded(true);
})();
// Fetch settings
(async () => {
try {
const settings = await api.get("/client/get-settings");
if (settings && settings.length >= 2) {
setDelay(parseInt(settings[1].value, 10) || 1);
setImageDuration(parseInt(settings[0].value, 10) || 5);
}
} catch (e) {
console.error("Settings error:", e);
}
})();
}, [partnerid, screenName, cycleCount]);
useEffect(() => {
if (!adsLoaded || !ads.length) return;
const ad = ads[idx];
const isVid = /\.(mp4|webm|ogg|mkv)$/i.test(ad.file_path);
let timer;
if (isVid) {
setVidPlay(true);
vidRef.current?.play().catch(() => {});
} else {
setVidPlay(false);
timer = setTimeout(() => {
if (idx === ads.length - 1) {
setCycleCount((prev) => prev + 1);
}
setIdx((p) => (p + 1) % ads.length);
}, imageDuration * 1000);
}
return () => clearTimeout(timer);
}, [ads, idx, adsLoaded, imageDuration]);
const vidEnd = () => {
setVidPlay(false);
setIdx((p) => (p + 1) % ads.length);
};
const logAd = async () => {
if (!adsLoaded || !ads.length) return console.log("Ads skip log.");
try {
if (ads[idx]) {
await api.post("/client/add-screen-log", {
screen_id: ads[0]?.screenid,
screen_name: screenName,
partner_id: ads[0]?.partnerid,
status: "active",
});
console.log("Logged");
} else console.log("Ad idx out.");
} catch (e) {
console.error("Log error:", e);
}
};
useEffect(() => {
if (!adsLoaded) return;
const logTimer = setInterval(() => {
if (!document.hidden) {
logAd();
} else {
console.log("Skipped logging: Tab is inactive");
}
}, 60 * delay * 1000);
return () => clearInterval(logTimer);
}, [adsLoaded, delay]);
useEffect(() => {
const handleVisibilityChange = async () => {
if (document.hidden) {
try {
console.log("User minimized or switched tab");
await api.post("/client/idle-screen-log", {
screen_id: ads[0]?.screenid,
screen_name: screenName,
partner_id: ads[0]?.partnerid,
status: "inactive",
});
} catch (e) {
console.error("Visibility Log error:", e);
}
} else {
console.log("User visible");
logAd();
}
};
document.addEventListener("visibilitychange", handleVisibilityChange);
return () => document.removeEventListener("visibilitychange", handleVisibilityChange);
}, [ads, adsLoaded, screenName]);
return (
<div className="h-screen w-screen bg-black flex items-center justify-center overflow-hidden">
<AnimatePresence mode="wait">
{adsLoaded && ads.length > 0 && (
isVidPlay ? (
<video
key={ads[idx].file_path}
ref={vidRef}
src={`https://ads.dine360ads.com/api//${ads[idx].file_path}`}
className="w-full h-full object-contain absolute"
autoPlay
muted
onEnded={vidEnd}
/>
) : (
<motion.img
key={ads[idx].file_path}
src={`https://ads.dine360ads.com/api//${ads[idx].file_path}`}
className="w-full h-full object-contain absolute"
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 1.1 }}
transition={{ duration: 1 }}
/>
)
)}
</AnimatePresence>
</div>
);
};
export default AdsPage;

240
src/Pages/Ads_log.jsx Normal file
View File

@ -0,0 +1,240 @@
import { useEffect, useState, useRef } from "react";
import { useParams } from "react-router-dom";
import { motion, AnimatePresence } from "framer-motion";
import { api } from "../API/api";
import Swal from "sweetalert2";
const AdsPage = () => {
const { partnerid, screenName } = useParams();
const [ads, setAds] = useState([]);
const [idx, setIdx] = useState(0);
const vidRef = useRef(null);
const [isVidPlay, setVidPlay] = useState(false);
const [adsLoaded, setAdsLoaded] = useState(false);
const [cycleCount, setCycleCount] = useState(0);
const [toastQueue, setToastQueue] = useState([]);
const [isToastActive, setIsToastActive] = useState(false);
const [delay, setDelay] = useState(1); // Default delay
const [imageDuration, setImageDuration] = useState(5); // Default image duration
const addLog = (message, icon = "info") => {
//setToastQueue((prev) => [...prev, { message, icon }]);
};
const showNextToast = () => {
if (toastQueue.length === 0 || isToastActive) return;
setIsToastActive(true);
const { message, icon } = toastQueue[0];
Swal.fire({
title: message,
icon: icon,
toast: true,
position: "top-end",
showConfirmButton: false,
timer: 2000,
timerProgressBar: true,
didClose: () => {
setToastQueue((prev) => prev.slice(1));
setIsToastActive(false);
},
});
};
useEffect(() => {
if (!isToastActive && toastQueue.length > 0) {
showNextToast();
}
}, [toastQueue, isToastActive]);
useEffect(() => {
(async () => {
try {
addLog("Fetching new ads list...");
setAds([]);
const fetchedAds = await api.get(`/client/ads/${partnerid}/${screenName}`);
addLog(`Fetched ${fetchedAds.length} ads`, "success");
const sortedAds = fetchedAds.sort((a, b) => a.transid - b.transid);
setAds(sortedAds);
} catch (e) {
console.error("Ads error:", e);
addLog("Error fetching ads: " + e.message, "error");
}
setAdsLoaded(true);
})();
(async () => {
try {
const settings = await api.get("/client/get-settings");
if (settings && settings.length >= 2) {
setDelay(parseInt(settings[1].value, 10) || 1);
setImageDuration(parseInt(settings[0].value, 10) || 5);
}
} catch (e) {
console.error("Settings error:", e);
}
})();
}, [partnerid, screenName, cycleCount]);
useEffect(() => {
if (!adsLoaded || ads.length === 0) return;
const ad = ads[idx];
addLog(`Displaying ad ${idx + 1}/${ads.length}`);
const isVid = /\.(mp4|webm|ogg|mkv)$/i.test(ad.file_path);
let timer;
if (isVid) {
addLog(`🟢 Preparing to play video: ${ad.file_path}`);
setVidPlay(true);
if (vidRef.current) {
vidRef.current.src = `https://ads.dine360ads.com/api/${ad.file_path}`;
vidRef.current.load();
vidRef.current
.play()
.then(() => addLog(`✅ Video started playing`, "success"))
.catch((err) => addLog(`❌ Video failed to play: ${err.message}`, "error"));
}
} else {
setVidPlay(false);
addLog(`🖼️ Showing image`);
timer = setTimeout(() => {
if (idx === ads.length - 1) {
setCycleCount((prev) => prev + 1);
}
setIdx((p) => (p + 1) % ads.length);
}, imageDuration * 1000);
}
return () => clearTimeout(timer);
}, [ads, idx, adsLoaded]);
const vidEnd = () => {
addLog("🔁 Video ended, moving to next ad...");
setVidPlay(false);
setIdx((p) => (p + 1) % ads.length);
};
const handleVideoError = () => {
addLog("❗ Video playback error! Skipping to next ad.", "error");
setVidPlay(false);
setIdx((p) => (p + 1) % ads.length);
};
const logAd = async () => {
if (!adsLoaded || !ads.length) return console.log("Ads skip log.");
try {
if (ads[idx]) {
await api.post("/client/add-screen-log", {
screen_id: ads[0]?.screenid,
screen_name: screenName,
partner_id: ads[0]?.partnerid,
status: "active",
});
console.log("Logged");
} else console.log("Ad idx out.");
} catch (e) {
console.error("Log error:", e);
}
};
useEffect(() => {
if (!adsLoaded) return;
const logTimer = setInterval(() => {
if (!document.hidden) {
logAd();
} else {
console.log("Skipped logging: Tab is inactive");
}
}, 60 * delay * 1000);
return () => clearInterval(logTimer);
}, [adsLoaded, delay]);
useEffect(() => {
const handleVisibilityChange = async () => {
if (document.hidden) {
try {
console.log("User minimized or switched tab");
await api.post("/client/idle-screen-log", {
screen_id: ads[0]?.screenid,
screen_name: screenName,
partner_id: ads[0]?.partnerid,
status: "inactive",
});
} catch (e) {
console.error("Visibility Log error:", e);
}
} else {
console.log("User visible");
logAd();
}
};
document.addEventListener("visibilitychange", handleVisibilityChange);
return () => document.removeEventListener("visibilitychange", handleVisibilityChange);
}, [ads, adsLoaded, screenName]);
return (
<div className="h-screen w-screen bg-black flex items-center justify-center overflow-hidden relative">
<AnimatePresence mode="wait">
{adsLoaded && ads.length > 0 && (
isVidPlay ? (
// <video
// key={ads[idx].file_path}
// ref={vidRef}
// className="w-full h-full object-contain absolute"
// autoPlay
// src={`https://ads.dine360ads.com/api/${ads[idx].file_path}`}
// muted
// onEnded={vidEnd}
// onError={handleVideoError}
// />
<video
key={ads[idx].file_path}
ref={vidRef}
className="w-full h-full object-contain absolute"
autoPlay
// controls
src={`https://ads.dine360ads.com/api/${ads[idx].file_path}`}
muted
onEnded={vidEnd}
onError={handleVideoError}
onPlay={() => addLog("✅ Video is playing")}
onPause={() => addLog("⏸️ Video is paused")}
onLoadedMetadata={() => addLog("📂 Video metadata loaded")}
/>
) : (
<motion.img
key={ads[idx].file_path}
src={`https://ads.dine360ads.com/api/${ads[idx].file_path}`}
className="w-full h-full object-contain absolute"
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 1.1 }}
transition={{ duration: 1 }}
/>
)
)}
</AnimatePresence>
</div>
);
};
export default AdsPage;

View File

@ -0,0 +1,53 @@
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();
useEffect(() => {
const fetchClients = async () => {
try {
setLoading(true);
const response = await api.get("/admin/partners");
setClients(response);
} catch (error) {
console.error("Error fetching clients:", error);
} finally {
setLoading(false);
}
};
fetchClients();
}, [upd]);
return (
<div className="min-h-screen bg-gray-900 text-white p-8">
<motion.h1
className="text-3xl font-bold text-center mb-8"
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
>
Ads Configuration
</motion.h1>
<MappingComponent Data={clients} setUpd={setUpd} />
</div>
);
};
export default ClientPartnerMapping

52
src/Pages/ClientsPage.jsx Normal file
View File

@ -0,0 +1,52 @@
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();
useEffect(() => {
const fetchClients = async () => {
try {
setLoading(true);
const response = await api.get("/admin/clients");
setClients(response);
} catch (error) {
console.error("Error fetching clients:", error);
} finally {
setLoading(false);
}
};
fetchClients();
}, [upd]);
return (
<div className="min-h-screen bg-gray-900 text-white p-8">
<motion.h1
className="text-3xl font-bold text-center mb-8"
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
>
Clients List
</motion.h1>
<ClientsCardComponent Data={clients} setUpd={setUpd} />
</div>
);
};
export default ClientsPage

131
src/Pages/Login copy.jsx Normal file
View File

@ -0,0 +1,131 @@
import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { motion } from "framer-motion";
import { FaLock, FaUser } from "react-icons/fa";
const Login = () => {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
const navigate = useNavigate();
const handleLogin = (e) => {
e.preventDefault();
if (username === "admin" && password === "admin") {
localStorage.setItem('loggedIn', JSON.stringify({
timestamp: Date.now(),
value: true,
}));
navigate("/admin-dashboard");
} else {
setError("❌ Invalid credentials! 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 (
<div className="h-screen flex items-center justify-center bg-gray-900 text-white">
<motion.div
className="bg-gray-800 p-8 rounded-lg shadow-xl w-96"
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ duration: 0.5 }}
>
{/* Animated Heading */}
<motion.h2
className="text-2xl font-bold text-center mb-6"
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3 }}
>
🔐 Admin Login
</motion.h2>
{/* Input Fields */}
<form onSubmit={handleLogin} className="space-y-4">
<div className="flex items-center border-b-2 border-gray-600 py-2">
<FaUser className="text-gray-400 mr-2" />
<input
type="text"
placeholder="Username"
className="w-full bg-transparent border-none focus:outline-none text-lg"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
</div>
<div className="flex items-center border-b-2 border-gray-600 py-2">
<FaLock className="text-gray-400 mr-2" />
<input
type="password"
placeholder="Password"
className="w-full bg-transparent border-none focus:outline-none text-lg"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</div>
{/* Error Message Animation */}
{error && (
<motion.p
className="text-red-400 text-center"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
>
{error}
</motion.p>
)}
{/* Login Button */}
<motion.button
type="submit"
className="w-full bg-blue-600 py-2 mt-4 rounded-lg text-lg font-semibold hover:bg-blue-700 transition duration-300"
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
🚀 Login
</motion.button>
</form>
</motion.div>
{/* Floating Emojis Animation */}
<motion.div
className="absolute top-10 left-10 text-5xl"
animate={{ y: [0, -15, 0] }}
transition={{ repeat: Infinity, duration: 2 }}
>
🔑
</motion.div>
<motion.div
className="absolute bottom-10 right-10 text-5xl"
animate={{ rotate: [0, 20, -20, 0] }}
transition={{ repeat: Infinity, duration: 2 }}
>
🔥
</motion.div>
</div>
);
};
export default Login;

131
src/Pages/Login.jsx Normal file
View File

@ -0,0 +1,131 @@
import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { motion } from "framer-motion";
import { FaLock, FaUser } from "react-icons/fa";
const Login = () => {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
const navigate = useNavigate();
const handleLogin = (e) => {
e.preventDefault();
if (username === "admin" && password === "admin") {
localStorage.setItem('loggedIn', JSON.stringify({
timestamp: Date.now(),
value: true,
}));
navigate("/admin-dashboard");
} else {
setError("❌ Invalid credentials! 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 (
<div className="h-screen flex items-center justify-center bg-gray-900 text-white">
<motion.div
className="bg-gray-800 p-8 rounded-lg shadow-xl w-96"
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ duration: 0.5 }}
>
{/* Animated Heading */}
<motion.h2
className="text-2xl font-bold text-center mb-6"
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3 }}
>
🔐 Admin Login
</motion.h2>
{/* Input Fields */}
<form onSubmit={handleLogin} className="space-y-4">
<div className="flex items-center border-b-2 border-gray-600 py-2">
<FaUser className="text-gray-400 mr-2" />
<input
type="text"
placeholder="Username"
className="w-full bg-transparent border-none focus:outline-none text-lg"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
</div>
<div className="flex items-center border-b-2 border-gray-600 py-2">
<FaLock className="text-gray-400 mr-2" />
<input
type="password"
placeholder="Password"
className="w-full bg-transparent border-none focus:outline-none text-lg"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</div>
{/* Error Message Animation */}
{error && (
<motion.p
className="text-red-400 text-center"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
>
{error}
</motion.p>
)}
{/* Login Button */}
<motion.button
type="submit"
className="w-full bg-blue-600 py-2 mt-4 rounded-lg text-lg font-semibold hover:bg-blue-700 transition duration-300"
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
🚀 Login
</motion.button>
</form>
</motion.div>
{/* Floating Emojis Animation */}
<motion.div
className="absolute top-10 left-10 text-5xl"
animate={{ y: [0, -15, 0] }}
transition={{ repeat: Infinity, duration: 2 }}
>
🔑
</motion.div>
<motion.div
className="absolute bottom-10 right-10 text-5xl"
animate={{ rotate: [0, 20, -20, 0] }}
transition={{ repeat: Infinity, duration: 2 }}
>
🔥
</motion.div>
</div>
);
};
export default Login;

202
src/Pages/ManageClient.jsx Normal file
View File

@ -0,0 +1,202 @@
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 { api } from "../API/api";
import ManageFiles from "./ManageFilesPage";
import { useLoading } from "../Context/LoadingContext";
const ManageClient = () => {
const { setLoading } = useLoading();
const { id } = useParams();
const [files, setFiles] = useState([]);
const [clients, setclients] = useState([]);
const [screens, setScreens] = useState([]);
const [uploading, setUploading] = useState(false);
const navigate = useNavigate();
const [isEnabled, setIsEnabled] = useState(true);
const [formData, setFormData] = useState({
transid: "",
name: "",
emailid: "",
phoneno: "",
address: "",
});
// Fetch files when component loads
useEffect(() => {
fetchClients();
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 fetchClients = async () => {
try {
setLoading(true);
const response = await api.get(`/admin/get-client/${id}`);
setclients(response);
setFormData(response[0]);
} catch (error) {
console.error("Error fetching files:", error);
}
setLoading(false)
};
const fetchScreens = async () => {
try {
setLoading(true);
const response = await api.get(`/admin/get-screen/${id}`);
setScreens(response);
} catch (error) {
console.error("Error fetching files:", error);
}
setLoading(false)
};
const handleUpdate = async (e) => {
e.preventDefault();
try {
setLoading(true);
await api.post("/admin/update-client", formData);
navigate("/clients");
} catch (error) {
console.error("Error updating Client:", error);
}
setLoading(false)
};
const handleChange = (e) => {
const { name, value } = e.target;
setFormData({ ...formData, [name]: value });
};
const handleEdit = () => {
setIsEnabled(false);
};
return (
<div className="min-h-screen bg-gray-900 text-white p-8">
{/* Back Button */}
<motion.button
className="mb-4 flex items-center text-gray-400 hover:text-white"
whileHover={{ scale: 1.1 }}
onClick={() => navigate("/admin-dashboard")}
>
<FaArrowLeft className="mr-2" /> Back
</motion.button>
{/* Page Title */}
<motion.h1
className="text-3xl font-bold mb-6"
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
>
Manage Client
</motion.h1>
<div className="space-y-4">
<div>
<label htmlFor="name" className="block text-sm font-medium text-gray-700">
Client Name
</label>
<input
type="text"
name="name"
id="name"
placeholder="Partner Name"
onChange={handleChange}
value={formData.name || ""}
className="block w-full bg-gray-700 p-2 rounded-lg mb-2"
required
// disabled={true}
/>
<label htmlFor="emailid" className="block text-sm font-medium text-gray-700">E-Mail ID</label>
<input
type="email"
name="emailid"
id="emailid"
placeholder="E-Mail ID"
onChange={handleChange}
className="block w-full bg-gray-700 p-2 rounded-lg mb-2"
required
minLength="1" // Example: Add minimum length validation
maxLength="255" //Example: add maximum length validation
disabled={isEnabled}
value={formData.emailid}
/>
<label htmlFor="phoneno" className="block text-sm font-medium text-gray-700">Phone Number</label>
<input
type="number"
name="phoneno"
id="phoneno"
placeholder="Phone Number"
onChange={handleChange}
className="block w-full bg-gray-700 p-2 rounded-lg mb-2"
required
minLength="1" // Example: Add minimum length validation
maxLength="255" //Example: add maximum length validation
disabled={isEnabled}
value={formData.phoneno}
/>
<label htmlFor="address" className="block text-sm font-medium text-gray-700">Address</label>
<input
type="text"
name="address"
id="address"
placeholder="Address"
onChange={handleChange}
className="block w-full bg-gray-700 p-2 rounded-lg mb-2"
required
value={formData.address}
disabled={isEnabled}
/>
</div>
{isEnabled ? (
<motion.button
type="button"
className="bg-green-500 px-4 py-2 rounded-lg"
whileHover={{ scale: 1.05 }}
onClick={handleEdit}
>
Edit
</motion.button>
) : (
<motion.button
type="submit"
className="bg-blue-500 px-4 py-2 rounded-lg"
whileHover={{ scale: 1.05 }}
onClick={handleUpdate}
>
Update
</motion.button>
)}
</div>
<div>
</div>
<div>
<ManageFiles id={id} />
</div>
</div>
);
};
export default ManageClient;

View File

@ -0,0 +1,253 @@
import { useEffect, useState, useRef, 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 { api } from "../API/api";
import { useLoading } from "../Context/LoadingContext";
import ViewPartnerAdsConfiguration from "../Components/ViewPartnerAdsConfiguration";
const ManageFilesOrder = () => {
const { id } = useParams();
const BACKEND = 'https://ads.dine360ads.com/api//';
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 });
});
setFiles(sorted);
console.log("FILESSSSSSSSSSSSS<",sorted)
} catch (error) {
console.error("Error fetching files:", error);
} 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]
);
// Toggle collapse for screen
const toggleScreen = (screen) => {
setOpenScreens(prev => ({ ...prev, [screen]: !prev[screen] }));
};
// Merge reordered group back into files array
const handleGroupReorder = (screen, newGroup) => {
const other = files.filter(f => f.screenname !== screen);
setFiles([...other, ...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);
}
};
// 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
));
};
// Move up/down within a screen group
const groupMoveUp = (screen, index) => {
const group = filesByScreen[screen];
if (index <= 0) return;
const newGroup = [...group];
[newGroup[index - 1], newGroup[index]] = [newGroup[index], newGroup[index - 1]];
handleGroupReorder(screen, newGroup);
};
const groupMoveDown = (screen, index) => {
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);
};
return (
<div className="min-h-screen bg-gray-900 text-white p-8">
<motion.h1
className="text-3xl font-bold mb-6"
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
>
📂 Manage Ads Order
</motion.h1>
{/* Collapsible groups by screen */}
<div className="space-y-6">
{Object.entries(filesByScreen).map(([screen, groupFiles]) => (
<div key={screen}>
<button
onClick={() => toggleScreen(screen)}
className="w-full flex justify-between items-center bg-gray-700 px-4 py-2 rounded-lg"
>
<span className="text-lg font-medium">{screen}</span>
<span className="text-2xl">{openScreens[screen] ? '' : '+'}</span>
</button>
<AnimatePresence>
{openScreens[screen] && (
<Reorder.Group
axis="y"
values={groupFiles}
onReorder={(newOrder) => 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);
return (
<Reorder.Item
key={file.id || file.file_path}
value={file}
layout
className="flex items-center justify-between border-b border-gray-700 pb-2"
>
<div className="flex items-center space-x-4">
{isImage && (
// <img src={fileUrl} alt={fileName} className="w-20 h-20 object-cover rounded-lg" />
<img
src={fileUrl}
alt={fileName}
className="h-40 w-auto object-cover rounded-lg"
/>
)}
{isVideo && (
// <video controls className="w-20 h-20 rounded-lg">
// <source src={fileUrl} type="video/mp4" />
// </video>
<video
controls
className="h-40 w-auto rounded-lg"
>
<source src={fileUrl} type="video/mp4" />
</video>
)}
<span className="text-gray-300">{fileName}</span>
</div>
<div className="flex items-center space-x-4">
{/* Checkboxes */}
<label className="flex items-center space-x-1">
<input
type="checkbox"
checked={file.ismainad}
onChange={e => handleToggleMainAd(file, e.target.checked)}
/>
<span>Main Ad</span>
</label>
<label className="flex items-center space-x-1">
<input
type="checkbox"
checked={file.iscarousel}
onChange={e => handleToggleCarousel(file, e.target.checked)}
/>
<span>Carousel</span>
</label>
<label className="flex items-center space-x-1">
<input
type="checkbox"
checked={file.isinhousead}
onChange={e => handleToggleInHouse(file, e.target.checked)}
/>
<span>In-House</span>
</label>
{/* Up/Down buttons */}
<motion.button
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
onClick={() => groupMoveUp(screen, index)}
disabled={index === 0}
className="p-2 rounded-full bg-gray-700 disabled:opacity-50"
>
<FaArrowUp className="text-gray-400" />
</motion.button>
<motion.button
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
onClick={() => groupMoveDown(screen, index)}
disabled={index === groupFiles.length - 1}
className="p-2 rounded-full bg-gray-700 disabled:opacity-50"
>
<FaArrowDown className="text-gray-400" />
</motion.button>
</div>
</Reorder.Item>
);
})}
</Reorder.Group>
)}
</AnimatePresence>
</div>
))}
<button
onClick={saveOrder}
className="mt-4 bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-lg font-bold mx-auto block"
>
Save Ads Order
</button>
</div>
<motion.h1
className="text-3xl font-bold mb-6 mt-12"
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
>
📂 Ads Configurations
</motion.h1>
<ViewPartnerAdsConfiguration data={files} />
</div>
);
};
export default ManageFilesOrder;

View File

@ -0,0 +1,213 @@
import { useEffect, useState, useRef } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { motion, AnimatePresence, Reorder } from "framer-motion";
import { FaArrowUp, FaArrowDown } 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://ads.dine360ads.com/api//';
const [files, setFiles] = useState([]);
const [upd, setupd] = useState(0);
const [uploading, setUploading] = useState(false);
const navigate = useNavigate();
const { setLoading } = useLoading();
const fileRefs = useRef([]);
useEffect(() => {
fetchFiles();
}, [id, upd]);
const fetchFiles = async () => {
try {
setLoading(true);
const response = await api.get(`/admin/partner-ads?partnerid=${id}`);
console.log(response)
const res = response.sort((a, b) => a.screenname.localeCompare(b.screenname, undefined, { numeric: true }));
setFiles(res);
} catch (error) {
console.error("Error fetching files:", error);
}
setLoading(false);
};
const ReorderAds = async (e) => {
setLoading(true);
console.log("Final Data to Insert:", files);
try {
await api.post("/admin/reorder-partner-ads", files);
alert("Client Partner Mapping data added successfully!");
} catch (error) {
console.error("Error adding client partner mapping:", error);
alert("Error adding client partner mapping. Please check console for details.");
}
setLoading(false);
};
const moveFileUp = (index) => {
if (index > 0) {
const newFiles = [...files];
[newFiles[index], newFiles[index - 1]] = [newFiles[index - 1], newFiles[index]];
setFiles(newFiles);
}
};
const moveFileDown = (index) => {
if (index < files.length - 1) {
const newFiles = [...files];
[newFiles[index], newFiles[index + 1]] = [newFiles[index + 1], newFiles[index]];
setFiles(newFiles);
}
};
const handleToggleCarousel = (index, checked) => {
// old: no-op
setFiles(files.map((f, i) =>
i === index
? { ...f, iscarousel: checked ? 1 : 0 }
: f
));
};
const handleToggleInHouse = (index, checked) => {
// old: no-op
setFiles(files.map((f, i) =>
i === index
? { ...f, isinhousead: checked ? 1 : 0 }
: f
));
};
const handleToggleMainAd = (index, checked) => {
// old: no-op
setFiles(files.map((f, i) =>
i === index
? { ...f, ismainad: checked ? 1 : 0 }
: f
));
};
return (
<div className="min-h-screen bg-gray-900 text-white p-8">
<motion.h1
className="text-3xl font-bold mb-6"
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
>
📂 Manage Ads Order
</motion.h1>
<div className="space-y-4">
<Reorder.Group axis="y" values={files} onReorder={setFiles} className="bg-gray-800 p-4 rounded-lg space-y-2">
<AnimatePresence>
{files.length > 0 ? (
files.map((file, index) => {
const fileName = file.file_path.split("_")[1];
const fileUrl = BACKEND + file.file_path;
const isImage = fileUrl.match(/\.(jpeg|jpg|png|gif)$/i);
const isVideo = fileUrl.match(/\.(mp4|webm|ogg|mkv)$/i);
return (
<Reorder.Item key={index} value={file} layout className="text-gray-300 flex items-center justify-between border-b border-gray-700 pb-2 space-x-4">
<div className="flex items-center space-x-4">
{isImage && <img src={fileUrl} alt={fileName} className="w-32 h-32 object-cover rounded-lg" />}
{isVideo && (
<video controls className="w-32 h-32 rounded-lg">
<source src={fileUrl} type="video/mp4" />
Your browser does not support the video tag.
</video>
)}
<span> {file.screenname} - 📄 {fileName}</span>
</div>
<div className="flex space-x-2">
<div className="flex space-x-2 mr-10">
<input
className="mr-2"
type="checkbox"
checked={file.ismainad}
onChange={e => handleToggleMainAd(index, e.target.checked)}
/>
Set Ad as Main Ad
</div><br></br>
<div className="flex space-x-2 mr-10">
<input
className="mr-2"
type="checkbox"
checked={file.iscarousel}
onChange={e => handleToggleCarousel(index, e.target.checked)}
/>
Set Ad in Image Carousel
</div>
<br>
</br>
<div className="flex space-x-2 mr-30">
<input
type="checkbox"
className="mr-2"
checked={file.isinhousead}
onChange={e => handleToggleInHouse(index, e.target.checked)}
/>
Set Ad as In-House Ad
</div>
<motion.button
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
onClick={() => moveFileUp(index)}
disabled={index === 0}
className="p-2 rounded-full bg-gray-700 disabled:opacity-50"
>
<FaArrowUp className="text-gray-400 hover:text-white" />
</motion.button>
<motion.button
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
onClick={() => moveFileDown(index)}
disabled={index === files.length - 1}
className="p-2 rounded-full bg-gray-700 disabled:opacity-50"
>
<FaArrowDown className="text-gray-400 hover:text-white" />
</motion.button>
</div>
</Reorder.Item>
);
})
) : (
<p className="text-gray-500">No files uploaded yet.</p>
)}
</AnimatePresence>
</Reorder.Group>
</div>
<button onClick={ReorderAds} className="mt-4 bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-lg font-bold block mx-auto">Save Ads Order</button>
<br></br>
<motion.h1
className="text-3xl font-bold mb-6"
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
>
📂 Ads Configurations
</motion.h1>
<div>
<ViewPartnerAdsConfiguration data={files} />
</div>
</div>
);
};
export default ManageFilesOrder;

View File

@ -0,0 +1,158 @@
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 { api } from "../API/api";
import { useLoading } from "../Context/LoadingContext";
const ManageFiles = ({ id }) => {
const BACKEND = 'https://ads.dine360ads.com/api//'
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]);
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 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)
};
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
try {
setLoading(true)
await api.post("/files/upload", formData, {
headers: { "Content-Type": "multipart/form-data" },
});
// 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)
}
};
const handleMultipleUpload = async (e) => {
const filesToUpload = e.target.files;
if (!filesToUpload || filesToUpload.length === 0) 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
};
return (
<div className="min-h-screen bg-gray-900 text-white p-8">
{/* Back Button */}
{/* <motion.button
className="mb-4 flex items-center text-gray-400 hover:text-white"
whileHover={{ scale: 1.1 }}
onClick={() => navigate("/admin-dashboard")}
>
<FaArrowLeft className="mr-2" /> Back
</motion.button> */}
{/* Page Title */}
<motion.h1
className="text-3xl font-bold mb-6"
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
>
📂 Manage Client Files
</motion.h1>
{/* Upload Button */}
<div className="space-y-4">
<label className="bg-gray-700 px-4 py-2 rounded-lg cursor-pointer flex items-center gap-2 hover:bg-gray-600">
<FaUpload />
<span>{uploading ? "Uploading..." : "Upload Files"}</span>
<input
type="file"
onChange={handleMultipleUpload}
className="hidden"
multiple // Add the 'multiple' attribute here
/>
</label>
<ul className="bg-gray-800 p-4 rounded-lg space-y-2">
{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 (
<li key={index} className="text-gray-300 flex flex-col border-b border-gray-700 pb-2 space-y-2">
<span>📄 {fileName}</span>
{isImage && <img src={fileUrl} alt={fileName} className="w-32 h-32 object-cover rounded-lg" />}
{isVideo && (
<video controls className="w-32 h-32 rounded-lg">
<source src={fileUrl} type="video/mp4" />
Your browser does not support the video tag.
</video>
)}
<button
onClick={() => {
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"
>
Delete
</button>
</li>
);
})
) : (
<p className="text-gray-500">No files uploaded yet.</p>
)}
</ul>
</div>
</div>
);
};
export default ManageFiles;

View File

@ -0,0 +1,145 @@
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 { api } from "../API/api";
import { useLoading } from "../Context/LoadingContext";
const ManageFiles = ({ id }) => {
const BACKEND = 'https://ads.dine360ads.com/api//'
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]);
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 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)
};
const handleUpload = async (e) => {
const file = e.target.files[0];
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
try {
setLoading(true)
await api.post("/files/upload", formData, {
headers: { "Content-Type": "multipart/form-data" },
});
// Refresh file list after upload
fetchFiles();
} catch (error) {
console.error("File upload failed:", error);
} finally {
setUploading(false);
setLoading(false)
}
};
return (
<div className="min-h-screen bg-gray-900 text-white p-8">
{/* Back Button */}
{/* <motion.button
className="mb-4 flex items-center text-gray-400 hover:text-white"
whileHover={{ scale: 1.1 }}
onClick={() => navigate("/admin-dashboard")}
>
<FaArrowLeft className="mr-2" /> Back
</motion.button> */}
{/* Page Title */}
<motion.h1
className="text-3xl font-bold mb-6"
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
>
📂 Manage Client Files
</motion.h1>
{/* Upload Button */}
<div className="space-y-4">
<label className="bg-gray-700 px-4 py-2 rounded-lg cursor-pointer flex items-center gap-2 hover:bg-gray-600">
<FaUpload />
<span>{uploading ? "Uploading..." : "Upload File"}</span>
<input type="file" onChange={handleUpload} className="hidden" />
</label>
<ul className="bg-gray-800 p-4 rounded-lg space-y-2">
{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 (
<li key={index} className="text-gray-300 flex flex-col border-b border-gray-700 pb-2 space-y-2">
<span>📄 {fileName}</span>
{isImage && <img src={fileUrl} alt={fileName} className="w-32 h-32 object-cover rounded-lg" />}
{isVideo && (
<video controls className="w-32 h-32 rounded-lg">
<source src={fileUrl} type="video/mp4" />
Your browser does not support the video tag.
</video>
)}
<button
onClick={() => {
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"
>
Delete
</button>
</li>
);
})
) : (
<p className="text-gray-500">No files uploaded yet.</p>
)}
</ul>
</div>
</div>
);
};
export default ManageFiles;

View File

@ -0,0 +1,663 @@
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 { 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 { useLoading } from "../Context/LoadingContext";
import { FiPlus } from "react-icons/fi";
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 [formData, setFormData] = useState({
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 cityOptions = useMemo(
() =>
selectedProvince
? canada_cities[selectedProvince.value].map((city) => ({
value: city,
label: city,
}))
: [],
[selectedProvince, canada_cities]
);
useEffect(() => {
fetchPartners();
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 });
}
} catch (error) {
console.error("Error fetching files:", error);
}
};
const fetchScreens = async () => {
try {
const response = await api.get(`/admin/get-screen/${id}`);
setScreens(response);
console.log(response)
} catch (error) {
console.error("Error fetching files:", error);
}
};
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
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)
}
};
const handleFileChange = async (e) => {
console.log("oiouyt")
await handleFileUpload(e.target.files[0]);
setFormData({ ...formData, image: 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 handleUpdate = async (e) => {
e.preventDefault();
try {
console.log(formData)
await api.post("/admin/update-partner", formData);
navigate("/partners");
} catch (error) {
console.error("Error updating Partner:", error);
}
};
const AddScreen = async () => {
try {
console.log({ partnerid: id })
setLoading(true);
var data = await api.post("/admin/add-screen", { partnerid: id });
console.log(data)
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,
});
// update local state so the UI flips instantly
// setScreens((prev) =>
// prev.map((s, i) =>
// i === idx ? { ...s, youtubeEnabled: enable ? 1 : 0 } : s
// )
// );
fetchScreens()
} catch (err) {
console.error("Error toggling YouTube:", err);
} finally {
setLoading(false);
}
};
return (
<div className="min-h-screen bg-gray-900 text-white p-8">
{/* Back button */}
<motion.button
className="mb-4 flex items-center text-gray-400 hover:text-white"
whileHover={{ scale: 1.1 }}
onClick={() => navigate("/Partners")}
>
<FaArrowLeft className="mr-2" /> Back
</motion.button>
{/* Header */}
<motion.h1
className="text-3xl font-bold mb-6"
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
>
Manage Partners
</motion.h1>
{/* Form */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* Partner Name */}
<div>
<label htmlFor="name" className="block text-sm font-medium text-gray-300">
Partner Name
</label>
<input
type="text"
name="name"
id="name"
value={formData.name || ""}
onChange={handleChange}
disabled={isEnabled}
className="mt-1 block w-full bg-gray-700 p-2 rounded-lg"
required
/>
</div>
{/* Logo */}
{/* Open Time */}
<div>
<label htmlFor="open_time" className="block text-sm font-medium text-gray-300">
Open Time
</label>
<input
type="time"
name="open_time"
id="open_time"
value={formData.open_time || ""}
onChange={handleChange}
disabled={isEnabled}
className="mt-1 block w-full bg-gray-700 p-2 rounded-lg"
required
/>
</div>
{/* Close Time */}
<div>
<label htmlFor="close_time" className="block text-sm font-medium text-gray-300">
Close Time
</label>
<input
type="time"
name="close_time"
id="close_time"
value={formData.close_time || ""}
onChange={handleChange}
disabled={isEnabled}
className="mt-1 block w-full bg-gray-700 p-2 rounded-lg"
required
/>
</div>
{/* Address */}
<div>
<label htmlFor="address" className="block text-sm font-medium text-gray-300">
Address
</label>
<input
type="text"
name="address"
id="address"
value={formData.address || ""}
onChange={handleChange}
disabled={isEnabled}
className="mt-1 block w-full bg-gray-700 p-2 rounded-lg"
required
/>
</div>
{/* Province */}
<div>
<label htmlFor="state" className="block text-sm font-medium text-gray-300">
Province
</label>
<Select
options={provinceOptions}
value={selectedProvince}
onChange={handleProvinceChange}
isDisabled={isEnabled}
className="mt-1"
styles={{ /* your existing styles */ }}
placeholder="Select Province"
/>
</div>
{/* City */}
<div>
<label htmlFor="city" className="block text-sm font-medium text-gray-300">
City
</label>
<Select
options={cityOptions}
value={selectedCity}
onChange={handleCityChange}
isDisabled={isEnabled || !selectedProvince}
className="mt-1"
styles={{ /* your existing styles */ }}
placeholder="Select City"
/>
</div>
{/* Pincode */}
<div>
<label htmlFor="pincode" className="block text-sm font-medium text-gray-300">
Pincode
</label>
<input
type="text"
name="pincode"
id="pincode"
value={formData.pincode || ""}
onChange={handleChange}
disabled={isEnabled}
className="mt-1 block w-full bg-gray-700 p-2 rounded-lg"
required
/>
</div>
{/* No. of Screens */}
<div>
<label htmlFor="screens" className="block text-sm font-medium text-gray-300">
No. of Screens
</label>
<input
type="number"
name="screens"
id="screens"
value={formData.screens || ""}
onChange={handleChange}
disabled={isEnabled}
className="mt-1 block w-full bg-gray-700 p-2 rounded-lg"
required
min="1"
/>
</div>
<div>
<label htmlFor="yt" className="block text-sm font-medium text-gray-700">
Youtube Links
</label>
<textarea
name="yt"
id="yt"
placeholder="Enter YouTube links, separated by commas"
onChange={handleChange}
value={formData.yt || ""}
className="block w-full bg-gray-700 p-2 rounded-lg mb-2"
rows={4}
required
disabled={isEnabled}
/>
</div>
<div>
<label htmlFor="scrolltext" className="block text-sm font-medium text-gray-700">
Scroll Text
</label>
<textarea
type="text"
name="scrolltext"
id="scrolltext"
placeholder="Scroll Text"
onChange={handleChange}
value={formData.scrolltext || ""}
className="block w-full bg-gray-700 p-2 rounded-lg mb-2"
required
disabled={isEnabled}
></textarea>
</div>
<div>
<label htmlFor="logo_url" className="block text-sm font-medium text-gray-300">
Logo
</label>
<img
src={`https://ads.dine360ads.com/api/${formData.logo_url || ""}`}
alt="Logo"
className="mt-1 w-[60%] rounded-lg"
/>
<input
type="file"
name="logo_url"
id="logo_url"
accept="image/*"
onChange={handleFileChange}
disabled={isEnabled}
className="mt-2 block w-full bg-gray-700 p-2 rounded-lg"
/>
</div>
</div>
{/* Edit / Update button */}
<div className="mt-6">
{isEnabled ? (
<motion.button
type="button"
onClick={handleEdit}
className="bg-green-500 px-4 py-2 rounded-lg"
whileHover={{ scale: 1.05 }}
>
Edit
</motion.button>
) : (
<motion.button
type="button"
className="bg-blue-500 px-4 py-2 rounded-lg"
whileHover={{ scale: 1.05 }}
onClick={handleUpdate}
>
Update
</motion.button>
)}
</div>
{/* Screens Preview */}
{formData.screens && (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mt-8">
<div className="bg-gray-800 p-6 rounded-xl shadow-xl flex justify-center items-center ">
<div className="">
<motion.button
type="button"
onClick={AddScreen}
className={`
inline-flex items-center
bg-gradient-to-r from-blue-500 to-teal-400
hover:from-blue-600 hover:to-teal-500
text-white font-semibold
px-6 py-3 rounded-lg
shadow-lg focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-300
transition-transform
`}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
<FiPlus className="w-5 h-5 mr-2" />
Add New Screen
</motion.button>
</div>
</div>
{screens.map((screen, index) => (
// <div key={index} className="bg-gray-800 p-4 rounded-lg shadow-md">
// <h3 className="text-lg font-semibold mb-2">
// Screen {index + 1} ({id.substring(0, 2) + 'S' + (index + 1)})
// </h3>
// <div className="flex space-x-2">
// <motion.button
// type="button"
// className="bg-blue-500 px-4 py-2 rounded-lg"
// whileHover={{ scale: 1.05 }}
// onClick={() => {
// const prefix = id.substring(0, 5);
// const screenName = `Screen${index + 1}`.replace(/\s+/g, '');
// window.open(`${window.location.origin}/ads/${prefix}/${screenName}`, '_blank');
// }}
// >
// Preview
// </motion.button>
// <motion.button
// type="button"
// className="bg-green-500 px-4 py-2 rounded-lg"
// whileHover={{ scale: 1.05 }}
// onClick={() => {
// 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
// </motion.button>
// <motion.button
// type="button"
// className={`px-2 py-2 rounded-lg ${screen.isyoutube === 1 ? "bg-amber-400" : "bg-fuchsia-500"
// }`}
// whileHover={{ scale: 1.05 }}
// onClick={() => {
// console.log(screen)
// handleToggleYouTube(
// screen.transid,
// screen.isyoutube !== 1,
// index
// )
// }
// }
// >
// {screen.isyoutube === 1
// ? "Disable YouTube"
// : "Enable YouTube"}
// </motion.button>
// <div className="flex items-center ml-auto">
// <FaCircle
// className={`mr-2 ${screen.isactive === 1
// ? "text-green-400"
// : screen.isactive === 2
// ? "text-orange-400"
// : "text-red-400"
// }`}
// />
// <span className="uppercase">
// {screen.isactive === 1 ? "ACTIVE" : screen.isactive === 2 ? "IDLE" : "INACTIVE"}
// </span>
// </div>
// <br></br>
// </div>
// <div>
// <br>
// </br>
// {screen.status}
// <br></br>
// {screen.stsupdat && (
// <div>
// {new Date(screen.stsupdat).toLocaleDateString('en-GB')} {new Date(screen.stsupdat).toLocaleTimeString()}
// </div>
// )}
// </div>
// </div>
<div key={index} className="bg-gray-800 p-4 rounded-lg shadow-md relative">
<h3 className="text-lg font-semibold mb-2">
Screen {index + 1} ({id.substring(0, 2) + 'S' + (index + 1)})
</h3>
<div className="flex space-x-2 items-center">
<motion.button
type="button"
className="bg-blue-500 px-4 py-2 rounded-lg"
whileHover={{ scale: 1.05 }}
onClick={() => {
const prefix = id.substring(0, 5);
const screenName = `Screen${index + 1}`.replace(/\s+/g, '');
window.open(`${window.location.origin}/ads/${prefix}/${screenName}`, '_blank');
}}
>
Preview
</motion.button>
<motion.button
type="button"
className="bg-green-500 px-4 py-2 rounded-lg"
whileHover={{ scale: 1.05 }}
onClick={() => {
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
</motion.button>
<motion.button
type="button"
className={`px-2 py-2 rounded-lg ${screen.isyoutube === 1 ? "bg-amber-400" : "bg-fuchsia-500"
}`}
whileHover={{ scale: 1.05 }}
onClick={() => {
console.log(screen);
handleToggleYouTube(screen.transid, screen.isyoutube !== 1, index);
}}
>
{screen.isyoutube === 1 ? "Disable YouTube" : "Enable YouTube"}
</motion.button>
<div className="flex items-center ml-auto">
<FaCircle
className={`mr-2 ${screen.isactive === 1
? "text-green-400"
: screen.isactive === 2
? "text-orange-400"
: "text-red-400"
}`}
/>
<span className="uppercase">
{screen.isactive === 1 ? "ACTIVE" : screen.isactive === 2 ? "IDLE" : "INACTIVE"}
</span>
</div>
{/* Delete button at the top right */}
<motion.button
type="button"
className="absolute top-2 right-2 text-red-500 hover:text-red-700"
whileHover={{ scale: 1.1 }}
onClick={() => {
// Add your delete functionality here, e.g., open a confirmation modal
console.log('Delete clicked for screen:', screen.transid);
DeleteScreen(screen.transid)
}}
>
<FaTrash /> {/* Using FontAwesome for the trash icon */}
</motion.button>
</div>
<br />
<div>
<br />
{screen.status}
<br />
{screen.stsupdat && (
<div>
{new Date(screen.stsupdat).toLocaleDateString('en-GB')}{' '}
{new Date(screen.stsupdat).toLocaleTimeString()}
</div>
)}
</div>
</div>
))}
</div>
)}
</div>
);
};
export default ManagePartner;

626
src/Pages/ManagePartner.jsx Normal file
View File

@ -0,0 +1,626 @@
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 { 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 { useLoading } from "../Context/LoadingContext";
import { FiPlus } from "react-icons/fi";
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 [formData, setFormData] = useState({
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 cityOptions = useMemo(
() =>
selectedProvince
? canada_cities[selectedProvince.value].map((city) => ({
value: city,
label: city,
}))
: [],
[selectedProvince, canada_cities]
);
useEffect(() => {
fetchPartners();
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 });
}
} catch (error) {
console.error("Error fetching files:", error);
}
};
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 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
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)
}
};
const handleFileChange = async (e) => {
console.log("oiouyt")
await handleFileUpload(e.target.files[0]);
setFormData({ ...formData, image: 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 handleUpdate = async (e) => {
e.preventDefault();
try {
console.log(formData)
await api.post("/admin/update-partner", formData);
navigate("/partners");
} catch (error) {
console.error("Error updating Partner:", error);
}
};
const AddScreen = async () => {
try {
console.log({ partnerid: id })
setLoading(true);
var data = await api.post("/admin/add-screen", { partnerid: id });
console.log(data)
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);
}
};
return (
<div className="min-h-screen bg-gray-900 text-white p-8">
{/* Back button */}
<motion.button
className="mb-4 flex items-center text-gray-400 hover:text-white"
whileHover={{ scale: 1.1 }}
onClick={() => navigate("/Partners")}
>
<FaArrowLeft className="mr-2" /> Back
</motion.button>
{/* Header */}
<motion.h1
className="text-3xl font-bold mb-6"
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
>
Manage Partners
</motion.h1>
{/* Form */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* Partner Name */}
<div>
<label htmlFor="name" className="block text-sm font-medium text-gray-300">
Partner Name
</label>
<input
type="text"
name="name"
id="name"
value={formData.name || ""}
onChange={handleChange}
disabled={isEnabled}
className="mt-1 block w-full bg-gray-700 p-2 rounded-lg"
required
/>
</div>
{/* Logo */}
{/* Open Time */}
<div>
<label htmlFor="open_time" className="block text-sm font-medium text-gray-300">
Open Time
</label>
<input
type="time"
name="open_time"
id="open_time"
value={formData.open_time || ""}
onChange={handleChange}
disabled={isEnabled}
className="mt-1 block w-full bg-gray-700 p-2 rounded-lg"
required
/>
</div>
{/* Close Time */}
<div>
<label htmlFor="close_time" className="block text-sm font-medium text-gray-300">
Close Time
</label>
<input
type="time"
name="close_time"
id="close_time"
value={formData.close_time || ""}
onChange={handleChange}
disabled={isEnabled}
className="mt-1 block w-full bg-gray-700 p-2 rounded-lg"
required
/>
</div>
{/* Address */}
<div>
<label htmlFor="address" className="block text-sm font-medium text-gray-300">
Address
</label>
<input
type="text"
name="address"
id="address"
value={formData.address || ""}
onChange={handleChange}
disabled={isEnabled}
className="mt-1 block w-full bg-gray-700 p-2 rounded-lg"
required
/>
</div>
{/* Province */}
<div>
<label htmlFor="state" className="block text-sm font-medium text-gray-300">
Province
</label>
<Select
options={provinceOptions}
value={selectedProvince}
onChange={handleProvinceChange}
isDisabled={isEnabled}
className="mt-1"
styles={{ /* your existing styles */ }}
placeholder="Select Province"
/>
</div>
{/* City */}
<div>
<label htmlFor="city" className="block text-sm font-medium text-gray-300">
City
</label>
<Select
options={cityOptions}
value={selectedCity}
onChange={handleCityChange}
isDisabled={isEnabled || !selectedProvince}
className="mt-1"
styles={{ /* your existing styles */ }}
placeholder="Select City"
/>
</div>
{/* Pincode */}
<div>
<label htmlFor="pincode" className="block text-sm font-medium text-gray-300">
Pincode
</label>
<input
type="text"
name="pincode"
id="pincode"
value={formData.pincode || ""}
onChange={handleChange}
disabled={isEnabled}
className="mt-1 block w-full bg-gray-700 p-2 rounded-lg"
required
/>
</div>
{/* No. of Screens */}
<div>
<label htmlFor="screens" className="block text-sm font-medium text-gray-300">
No. of Screens
</label>
<input
type="number"
name="screens"
id="screens"
value={formData.screens || ""}
onChange={handleChange}
disabled={isEnabled}
className="mt-1 block w-full bg-gray-700 p-2 rounded-lg"
required
min="1"
/>
</div>
<div>
<label htmlFor="yt" className="block text-sm font-medium text-gray-700">
Youtube Links
</label>
<textarea
name="yt"
id="yt"
placeholder="Enter YouTube links, separated by commas"
onChange={handleChange}
value={formData.yt || ""}
className="block w-full bg-gray-700 p-2 rounded-lg mb-2"
rows={4}
required
disabled={isEnabled}
/>
</div>
<div>
<label htmlFor="scrolltext" className="block text-sm font-medium text-gray-700">
Scroll Text
</label>
<textarea
type="text"
name="scrolltext"
id="scrolltext"
placeholder="Scroll Text"
onChange={handleChange}
value={formData.scrolltext || ""}
className="block w-full bg-gray-700 p-2 rounded-lg mb-2"
required
disabled={isEnabled}
></textarea>
</div>
<div>
<label htmlFor="logo_url" className="block text-sm font-medium text-gray-300">
Logo
</label>
<img
src={`https://ads.dine360ads.com/api/${formData.logo_url || ""}`}
alt="Logo"
className="mt-1 w-[60%] rounded-lg"
/>
<input
type="file"
name="logo_url"
id="logo_url"
accept="image/*"
onChange={handleFileChange}
disabled={isEnabled}
className="mt-2 block w-full bg-gray-700 p-2 rounded-lg"
/>
</div>
</div>
{/* Edit / Update button */}
<div className="mt-6">
{isEnabled ? (
<motion.button
type="button"
onClick={handleEdit}
className="bg-green-500 px-4 py-2 rounded-lg"
whileHover={{ scale: 1.05 }}
>
Edit
</motion.button>
) : (
<motion.button
type="button"
className="bg-blue-500 px-4 py-2 rounded-lg"
whileHover={{ scale: 1.05 }}
onClick={handleUpdate}
>
Update
</motion.button>
)}
</div>
{/* Screens Preview */}
{formData.screens && (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mt-8">
<div className="bg-gray-800 p-6 rounded-xl shadow-xl flex justify-center items-center ">
<div className="">
<motion.button
type="button"
onClick={AddScreen}
className={`
inline-flex items-center
bg-gradient-to-r from-blue-500 to-teal-400
hover:from-blue-600 hover:to-teal-500
text-white font-semibold
px-6 py-3 rounded-lg
shadow-lg focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-300
transition-transform
`}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
<FiPlus className="w-5 h-5 mr-2" />
Add New Screen
</motion.button>
</div>
</div>
{screens.map((screen, index) => (
<div key={index} className="bg-gray-800 p-4 rounded-lg shadow-md relative">
{/* Delete Button - Top Right of Card */}
<motion.button
type="button"
className="absolute top-3 right-3 text-red-500 hover:text-red-700 bg-white bg-opacity-10 rounded-full p-2 transition z-10"
whileHover={{ scale: 1.2, rotate: 15 }}
onClick={() => {
if (window.confirm("Are you sure you want to delete this screen?")) {
DeleteScreen(screen.transid);
}
}}
title="Delete this screen"
>
<FaTrash />
</motion.button>
<h3 className="text-lg font-semibold mb-2">
Screen {index + 1} ({id.substring(0, 2) + 'S' + (index + 1)})
</h3>
<div className="flex flex-wrap items-center gap-3 relative">
{/* Preview Button */}
<motion.button
type="button"
className="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded-lg shadow transition"
whileHover={{ scale: 1.07 }}
onClick={() => {
const prefix = id.substring(0, 5);
const screenName = `Screen${index + 1}`.replace(/\s+/g, '');
window.open(`${window.location.origin}/ads/${prefix}/${screenName}`, '_blank');
}}
>
Preview
</motion.button>
{/* Copy Link Button */}
<motion.button
type="button"
className="bg-green-600 hover:bg-green-700 px-4 py-2 rounded-lg shadow transition"
whileHover={{ scale: 1.07 }}
onClick={() => {
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
</motion.button>
{/* YouTube Toggle Switch */}
<motion.div
className="flex items-center gap-2"
whileHover={{ scale: 1.05 }}
title={screen.isyoutube === 1 ? "YouTube is enabled for this screen" : "YouTube is disabled for this screen"}
>
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
checked={screen.isyoutube === 1}
onChange={() => handleToggleYouTube(screen.transid, screen.isyoutube !== 1, index)}
className="sr-only peer"
/>
<div className={`w-14 h-7 bg-gray-400 rounded-full peer peer-checked:bg-amber-400 peer-focus:ring-2 peer-focus:ring-amber-300 transition-all duration-300`}>
<div
className={`absolute left-0 top-0 w-7 h-7 rounded-full bg-white shadow-md transform transition-all duration-300 ${screen.isyoutube === 1 ? 'translate-x-7' : ''}`}
></div>
</div>
<span className={`ml-4 text-sm font-medium ${screen.isyoutube === 1 ? 'text-amber-300' : 'text-fuchsia-300'}`}>
{screen.isyoutube === 1 ? "YouTube ON" : "YouTube OFF"}
</span>
</label>
</motion.div>
<motion.div
className="ml-2"
whileHover={{ scale: 1.05 }}
title="Change Screen Type"
>
<select
value={screen.screentype || 1}
onChange={async (e) => {
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"
>
<option value={1}>Screen Type 1 (Normal)</option>
{/* <option value={2}>Screen Type 2 (InHouse)</option> */}
<option value={3}>Screen Type 3 (In-house and Scroll Text)</option>
</select>
</motion.div>
{/* Status Indicator */}
<div className="flex items-center ml-auto">
<FaCircle
className={`mr-2 ${screen.isactive === 1
? "text-green-400"
: screen.isactive === 2
? "text-orange-400"
: "text-red-400"
}`}
/>
<span className="uppercase font-semibold tracking-wide">
{screen.isactive === 1 ? "ACTIVE" : screen.isactive === 2 ? "IDLE" : "INACTIVE"}
</span>
</div>
</div>
<br />
<div>
<br />
{screen.status}
<br />
{screen.stsupdat && (
<div>
{new Date(screen.stsupdat).toLocaleDateString('en-GB')}{' '}
{new Date(screen.stsupdat).toLocaleTimeString()}
</div>
)}
</div>
</div>
))}
</div>
)}
</div>
);
};
export default ManagePartner;

View File

@ -0,0 +1,53 @@
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 NewMappingCardComponent from "../Components/NewMappingCardComponent copy";
const NewAdConfiguration = () => {
const [clients, setClients] = useState([]);
const [upd, setUpd] = useState(0);
const navigate = useNavigate();
const { setLoading } = useLoading();
useEffect(() => {
const fetchClients = async () => {
try {
setLoading(true);
const response = await api.get("/admin/clients");
setClients(response);
} catch (error) {
console.error("Error fetching clients:", error);
} finally {
setLoading(false);
}
};
fetchClients();
}, [upd]);
return (
<div className="min-h-screen bg-gray-900 text-white p-8">
<motion.h1
className="text-3xl font-bold text-center mb-8"
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
>
Clients List
</motion.h1>
<NewMappingCardComponent Data={clients} setUpd={setUpd} />
</div>
);
};
export default NewAdConfiguration

57
src/Pages/NotFound.jsx Normal file
View File

@ -0,0 +1,57 @@
import { motion } from "framer-motion";
import { Link } from "react-router-dom";
const NotFound = () => {
return (
<div className="h-screen flex flex-col items-center justify-center bg-gray-900 text-white">
{/* Animated 404 Text */}
<motion.h1
className="text-9xl font-bold text-red-500"
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ duration: 0.5, ease: "easeOut" }}
>
404
</motion.h1>
{/* Error Message */}
<motion.p
className="text-xl mt-4 text-gray-300"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3 }}
>
Oops! The page you are looking for does not exist.
</motion.p>
{/* Animated Button */}
<motion.div
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ delay: 0.6, duration: 0.3 }}
className="mt-6"
>
<Link
to="/"
className="px-6 py-3 bg-blue-500 rounded-full text-lg font-semibold hover:bg-blue-700 transition duration-300"
>
Go Back Home
</Link>
</motion.div>
{/* Floating Animation */}
<motion.div
className="absolute top-10 left-10 w-24 h-24 bg-blue-500 rounded-full opacity-30"
animate={{ y: [0, -20, 0] }}
transition={{ repeat: Infinity, duration: 3, ease: "easeInOut" }}
/>
<motion.div
className="absolute bottom-20 right-20 w-32 h-32 bg-red-500 rounded-full opacity-30"
animate={{ x: [0, -20, 0] }}
transition={{ repeat: Infinity, duration: 3, ease: "easeInOut" }}
/>
</div>
);
};
export default NotFound;

View File

@ -0,0 +1,99 @@
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();
useEffect(() => {
const fetchClients = async () => {
try {
setLoading(true);
const response = await api.get("/admin/partners");
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 (
<div className="min-h-screen bg-gray-900 text-white p-8">
<motion.h1
className="text-3xl font-bold text-center mb-8"
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
>
Partners List
</motion.h1>
{/* <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
<motion.div
className="bg-gray-800 p-6 rounded-lg shadow-lg text-center cursor-pointer hover:bg-gray-700 transition flex items-center justify-center"
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
onClick={() => navigate("/add-client")}
>
<div>
<FaPlus className="text-5xl text-blue-500 mx-auto mb-4" />
<p className="text-lg font-semibold">Add New Client</p>
</div>
</motion.div>
{clients?.map((client) => (
<motion.div
key={client.id}
className="bg-gray-800 p-6 rounded-lg shadow-lg relative cursor-pointer hover:bg-gray-700 transition"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2 }}
whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.98 }}
onClick={() => navigate(`/manage-client/${client.id}`)}
>
<div
className="bg-cover bg-center h-40 rounded-lg filter blur-sm"
style={{
backgroundImage: `url('///')`,
}}
></div>
<div className="mt-4">
<h2 className="text-xl font-bold">{client.name}</h2>
<p className="text-gray-400">🕒 {client.open_time} - {client.close_time}</p>
<div className="mt-2 ml-1 flex items-center">
<FaCircle className={`mr-2 ${client.is_active ? "text-green-400" : "text-red-400"}`} />
<span> {client.is_active ? "Active" : "Inactive"}</span>
</div>
</div>
</motion.div>
))}
</div> */}
<PartnersCardComponent Data={clients} setUpd={setUpd} />
</div>
);
};
export default PartnersPage

View File

@ -0,0 +1,95 @@
import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { motion } from "framer-motion";
import { FaArrowLeft } 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();
}, []);
const fetchSettings = async () => {
try {
setLoading(true);
const response = await api.get("/client/get-settings");
setSettingsData(response);
} catch (error) {
console.error("Error fetching settings:", error);
}
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 ")
fetchSettings();
} catch (error) {
console.error("Error updating setting:", error);
}
setLoading(false);
};
return (
<div className="min-h-screen bg-gray-900 text-white p-8">
<motion.button
className="mb-4 flex items-center text-gray-400 hover:text-white"
whileHover={{ scale: 1.1 }}
onClick={() => navigate("/admin-dashboard")}
>
<FaArrowLeft className="mr-2" /> Back
</motion.button>
<motion.h1
className="text-3xl font-bold mb-6"
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
>
Settings
</motion.h1>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4"> {/* Grid Layout */}
{settingsData.map((item) => (
<div key={item.transid} className="item">
<label className="block text-sm font-medium text-gray-700">
{item.name}
</label>
<input
type="text"
value={item.value}
placeholder= {item.name}
onChange={(e) => {
const updatedSettings = settingsData.map((setting) =>
setting.transid === item.transid
? { ...setting, value: e.target.value }
: setting
);
setSettingsData(updatedSettings);
}}
className="block w-full bg-gray-700 p-2 rounded-lg mb-2"
/>
<motion.button
onClick={() => handleUpdate(item.transid, item.value)}
className="bg-blue-500 px-4 py-2 rounded-lg"
whileHover={{ scale: 1.05 }}
>
Update
</motion.button>
</div>
))}
</div>
</div>
);
};
export default SettingsPage;

67
src/Pages/YT.jsx Normal file
View File

@ -0,0 +1,67 @@
import React, { useEffect, useRef } from 'react';
const YouTubePlayer = () => {
const playerRef = useRef(null);
const youtubeUrl = 'https://www.youtube.com/watch?v=MWm-ooierpw';
const getVideoId = (url) => {
try {
const parsedUrl = new URL(url);
if (parsedUrl.hostname === 'youtu.be') {
return parsedUrl.pathname.slice(1);
} else if (parsedUrl.hostname.includes('youtube.com')) {
return parsedUrl.searchParams.get('v');
}
} catch (err) {
console.error(err);
}
return null;
};
const videoId = getVideoId(youtubeUrl);
useEffect(() => {
const loadYouTubeAPI = () => {
const tag = document.createElement('script');
tag.src = 'https://www.youtube.com/iframe_api';
const firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
};
loadYouTubeAPI();
window.onYouTubeIframeAPIReady = () => {
playerRef.current = new window.YT.Player('ytplayer', {
height: '450',
width: '800',
videoId,
playerVars: {
autoplay: 1,
mute: 1,
},
events: {
onReady: (event) => {
event.target.playVideo();
// Try to unmute after 5 seconds
setTimeout(() => {
try {
event.target.unMute();
} catch (err) {
console.log('Auto-unmute failed:', err);
}
}, 5000); // 5000ms = 5 seconds
},
},
});
};
}, [videoId]);
return (
<div className="flex items-center justify-center h-screen bg-black">
<div id="ytplayer"></div>
</div>
);
};
export default YouTubePlayer;

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

1
src/assets/react.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

1
src/index.css Normal file
View File

@ -0,0 +1 @@
@import "tailwindcss";

11
src/main.jsx Normal file
View File

@ -0,0 +1,11 @@
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { LoadingProvider } from "./Context/LoadingContext";
import './index.css'
ReactDOM.createRoot(document.getElementById("root")).render(
<LoadingProvider>
<App />
</LoadingProvider>
);

8
vite.config.js Normal file
View File

@ -0,0 +1,8 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
// https://vite.dev/config/
export default defineConfig({
plugins: [react(),tailwindcss(),],
})