- New collapsible sidebar (64px → 220px on hover) replaces top header - Vibrant dark purple base (#0c0a1e) with glassmorphism cards - Orange/amber gradient brand color throughout - Redesigned Login page with floating orbs and glass card - New AdminDashboard with gradient icon cards - Partners/Clients/Mapping cards with modern hover effects - AddPartner/ManagePartner forms with section-based glass layout - ManageFilesPage with drag-upload zone and media grid - ManageFilesOrderPage with collapsible screen groups and toggle UI - SettingsPage with card-per-setting layout - Shared CSS design system (glass-card, btn-primary, input-field, etc.) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
137 lines
5.5 KiB
JavaScript
137 lines
5.5 KiB
JavaScript
import { useEffect, useState } from "react";
|
|
import { motion } from "framer-motion";
|
|
import { FaUpload, FaTrash, FaFileImage, FaFileVideo } from "react-icons/fa";
|
|
import { api } from "../API/api";
|
|
import { useLoading } from "../Context/LoadingContext";
|
|
|
|
const ManageFiles = ({ id }) => {
|
|
const BACKEND = "https://backend.dine360ads.com/";
|
|
const [files, setFiles] = useState([]);
|
|
const [upd, setupd] = useState(0);
|
|
const [uploading, setUploading] = useState(false);
|
|
const { setLoading } = useLoading();
|
|
|
|
useEffect(() => { fetchFiles(); }, [id, upd]);
|
|
|
|
const fetchFiles = async () => {
|
|
try {
|
|
setLoading(true);
|
|
const res = await api.get(`/client/files/${id}`);
|
|
setFiles(res);
|
|
} catch (e) { console.error(e); }
|
|
setLoading(false);
|
|
};
|
|
|
|
const deleteFiles = async (fileid) => {
|
|
try {
|
|
setLoading(true);
|
|
await api.delete(`/files/del/${fileid}`);
|
|
setupd(upd + 1);
|
|
} catch (e) { console.error(e); }
|
|
setLoading(false);
|
|
};
|
|
|
|
const handleFileUpload = async (file) => {
|
|
if (!file) return;
|
|
setUploading(true);
|
|
const fd = new FormData();
|
|
fd.append("file", file);
|
|
fd.append("file_name", file.name);
|
|
fd.append("client_id", id);
|
|
try {
|
|
setLoading(true);
|
|
await api.post("/files/upload", fd, { headers: { "Content-Type": "multipart/form-data" } });
|
|
fetchFiles();
|
|
} catch (e) { console.error(e); }
|
|
finally { setUploading(false); setLoading(false); }
|
|
};
|
|
|
|
const handleMultipleUpload = async (e) => {
|
|
const filesToUpload = e.target.files;
|
|
if (!filesToUpload?.length) return;
|
|
setUploading(true);
|
|
for (let i = 0; i < filesToUpload.length; i++) {
|
|
await handleFileUpload(filesToUpload[i]);
|
|
}
|
|
setUploading(false);
|
|
};
|
|
|
|
return (
|
|
<div>
|
|
<h2 className="text-white font-bold text-lg mb-4">Media Files</h2>
|
|
|
|
{/* Upload zone */}
|
|
<label
|
|
className="flex flex-col items-center justify-center gap-3 rounded-2xl p-8 mb-6 cursor-pointer transition-all"
|
|
style={{
|
|
background: "rgba(249,115,22,0.04)",
|
|
border: "2px dashed rgba(249,115,22,0.2)",
|
|
}}
|
|
onMouseEnter={e => { e.currentTarget.style.borderColor = "rgba(249,115,22,0.45)"; e.currentTarget.style.background = "rgba(249,115,22,0.08)"; }}
|
|
onMouseLeave={e => { e.currentTarget.style.borderColor = "rgba(249,115,22,0.2)"; e.currentTarget.style.background = "rgba(249,115,22,0.04)"; }}
|
|
>
|
|
<div
|
|
className="w-12 h-12 rounded-xl flex items-center justify-center"
|
|
style={{ background: "linear-gradient(135deg, #f97316, #f59e0b)", boxShadow: "0 4px 15px rgba(249,115,22,0.3)" }}
|
|
>
|
|
<FaUpload className="w-5 h-5 text-white" />
|
|
</div>
|
|
<div className="text-center">
|
|
<p className="text-white font-semibold text-sm">{uploading ? "Uploading..." : "Click to upload files"}</p>
|
|
<p className="text-xs mt-1" style={{ color: "#475569" }}>Images and videos supported</p>
|
|
</div>
|
|
<input type="file" onChange={handleMultipleUpload} className="hidden" multiple />
|
|
</label>
|
|
|
|
{/* File list */}
|
|
{files.length === 0 ? (
|
|
<p className="text-center py-8 text-sm" style={{ color: "#334155" }}>No files uploaded yet.</p>
|
|
) : (
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
{files.map((file, index) => {
|
|
const fileName = file.file_name;
|
|
const fileUrl = BACKEND + file.file_path;
|
|
const isImage = /\.(jpe?g|png|gif)$/i.test(fileUrl);
|
|
const isVideo = /\.(mp4|webm|ogg|mkv)$/i.test(fileUrl);
|
|
|
|
return (
|
|
<motion.div
|
|
key={index}
|
|
className="rounded-2xl overflow-hidden"
|
|
style={{ background: "rgba(255,255,255,0.04)", border: "1px solid rgba(255,255,255,0.08)" }}
|
|
initial={{ opacity: 0, scale: 0.97 }}
|
|
animate={{ opacity: 1, scale: 1 }}
|
|
transition={{ delay: index * 0.04 }}
|
|
>
|
|
<div className="relative h-36 bg-black flex items-center justify-center" style={{ background: "rgba(0,0,0,0.3)" }}>
|
|
{isImage && <img src={fileUrl} alt={fileName} className="h-full w-full object-cover" />}
|
|
{isVideo && <video className="h-full w-full object-cover" controls><source src={fileUrl} type="video/mp4" /></video>}
|
|
{!isImage && !isVideo && (
|
|
<div className="flex flex-col items-center gap-2 opacity-40">
|
|
<FaFileImage className="w-8 h-8 text-white" />
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div className="p-3 flex items-center justify-between gap-2">
|
|
<p className="text-xs text-white truncate flex-1">{fileName}</p>
|
|
<button
|
|
className="w-7 h-7 rounded-lg flex items-center justify-center flex-shrink-0 transition-all"
|
|
style={{ background: "rgba(239,68,68,0.12)", border: "1px solid rgba(239,68,68,0.18)" }}
|
|
onClick={() => { if (confirm("Delete this file?")) deleteFiles(file.transid); }}
|
|
onMouseEnter={e => { e.currentTarget.style.background = "rgba(239,68,68,0.28)"; }}
|
|
onMouseLeave={e => { e.currentTarget.style.background = "rgba(239,68,68,0.12)"; }}
|
|
>
|
|
<FaTrash className="w-3 h-3 text-red-400" />
|
|
</button>
|
|
</div>
|
|
</motion.div>
|
|
);
|
|
})}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default ManageFiles;
|