2025-07-02 15:44:00 +00:00
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
import { useEffect, useState } from "react";
|
|
|
|
|
import React, { ReactNode } from "react";
|
|
|
|
|
import { SidebarProvider } from "./sidebar-context";
|
|
|
|
|
import { Breadcrumbs } from "./breadcrumbs";
|
|
|
|
|
import { BurgerButtonIcon } from "../icons";
|
|
|
|
|
import { RetractingSidebar } from "../landing-page/retracting-sidedar";
|
2025-07-03 02:52:06 +00:00
|
|
|
import { motion, AnimatePresence } from "framer-motion";
|
2025-07-02 15:44:00 +00:00
|
|
|
|
|
|
|
|
export const AdminLayout = ({ children }: { children: ReactNode }) => {
|
|
|
|
|
const [isOpen, setIsOpen] = useState(true);
|
2025-07-03 02:52:06 +00:00
|
|
|
const [hasMounted, setHasMounted] = useState(false);
|
|
|
|
|
|
2025-07-02 15:44:00 +00:00
|
|
|
const updateSidebarData = (newData: boolean) => {
|
|
|
|
|
setIsOpen(newData);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Hooks
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
setHasMounted(true);
|
|
|
|
|
}, []);
|
|
|
|
|
|
2025-07-03 02:52:06 +00:00
|
|
|
// Render loading state until mounted
|
|
|
|
|
if (!hasMounted) {
|
|
|
|
|
return (
|
|
|
|
|
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-white to-slate-50 flex items-center justify-center">
|
|
|
|
|
<div className="animate-spin rounded-full h-32 w-32 border-b-2 border-blue-500"></div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-07-02 15:44:00 +00:00
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<SidebarProvider>
|
2025-07-03 02:52:06 +00:00
|
|
|
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-white to-slate-50">
|
|
|
|
|
<div className="flex h-screen overflow-hidden">
|
|
|
|
|
<RetractingSidebar
|
|
|
|
|
sidebarData={isOpen}
|
|
|
|
|
updateSidebarData={updateSidebarData}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<AnimatePresence mode="wait">
|
|
|
|
|
<motion.div
|
|
|
|
|
key="main-content"
|
|
|
|
|
className="flex-1 flex flex-col overflow-hidden"
|
|
|
|
|
initial={{ opacity: 0, x: 20 }}
|
|
|
|
|
animate={{ opacity: 1, x: 0 }}
|
|
|
|
|
transition={{ duration: 0.3 }}
|
2025-07-02 15:44:00 +00:00
|
|
|
>
|
2025-07-03 02:52:06 +00:00
|
|
|
{/* Header */}
|
|
|
|
|
<motion.header
|
|
|
|
|
className="bg-white/80 backdrop-blur-sm border-b border-slate-200/60 shadow-sm"
|
|
|
|
|
initial={{ y: -20, opacity: 0 }}
|
|
|
|
|
animate={{ y: 0, opacity: 1 }}
|
|
|
|
|
transition={{ delay: 0.2, duration: 0.3 }}
|
|
|
|
|
>
|
|
|
|
|
<div className="flex items-center justify-between px-6 py-4">
|
|
|
|
|
<div className="flex items-center space-x-4">
|
|
|
|
|
<button
|
|
|
|
|
className="md:hidden p-2 rounded-lg hover:bg-slate-100 transition-colors duration-200"
|
|
|
|
|
onClick={() => updateSidebarData(true)}
|
|
|
|
|
>
|
|
|
|
|
<BurgerButtonIcon />
|
|
|
|
|
</button>
|
|
|
|
|
<Breadcrumbs />
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Header Actions */}
|
|
|
|
|
<div className="flex items-center space-x-3">
|
|
|
|
|
{/* Notifications */}
|
|
|
|
|
<motion.button
|
|
|
|
|
whileHover={{ scale: 1.05 }}
|
|
|
|
|
whileTap={{ scale: 0.95 }}
|
|
|
|
|
className="p-2 rounded-lg hover:bg-slate-100 transition-colors duration-200 relative"
|
|
|
|
|
>
|
|
|
|
|
<div className="w-5 h-5 text-slate-600">
|
|
|
|
|
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 17h5l-5 5v-5z" />
|
|
|
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 7h6m0 10v-3m-3 3h.01M9 17h.01M9 14h.01M12 14h.01M15 11h.01M12 11h.01M9 11h.01M7 21h10a2 2 0 002-2V5a2 2 0 00-2-2H7a2 2 0 00-2 2v14a2 2 0 002 2z" />
|
|
|
|
|
</svg>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="absolute -top-1 -right-1 w-3 h-3 bg-red-500 rounded-full"></div>
|
|
|
|
|
</motion.button>
|
|
|
|
|
|
|
|
|
|
{/* Search */}
|
|
|
|
|
<motion.button
|
|
|
|
|
whileHover={{ scale: 1.05 }}
|
|
|
|
|
whileTap={{ scale: 0.95 }}
|
|
|
|
|
className="p-2 rounded-lg hover:bg-slate-100 transition-colors duration-200"
|
|
|
|
|
>
|
|
|
|
|
<div className="w-5 h-5 text-slate-600">
|
|
|
|
|
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
|
|
|
|
</svg>
|
|
|
|
|
</div>
|
|
|
|
|
</motion.button>
|
|
|
|
|
|
|
|
|
|
{/* Profile */}
|
|
|
|
|
<motion.div
|
|
|
|
|
whileHover={{ scale: 1.05 }}
|
|
|
|
|
whileTap={{ scale: 0.95 }}
|
|
|
|
|
className="flex items-center space-x-3 p-2 rounded-lg hover:bg-slate-100 transition-colors duration-200 cursor-pointer"
|
|
|
|
|
>
|
|
|
|
|
<div className="w-8 h-8 rounded-full bg-gradient-to-r from-blue-500 to-purple-500 flex items-center justify-center text-white text-sm font-semibold">
|
|
|
|
|
A
|
|
|
|
|
</div>
|
|
|
|
|
<div className="hidden md:block">
|
|
|
|
|
<p className="text-sm font-medium text-slate-800">Admin</p>
|
|
|
|
|
<p className="text-xs text-slate-500">admin@mikul.com</p>
|
|
|
|
|
</div>
|
|
|
|
|
</motion.div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</motion.header>
|
2025-07-02 15:44:00 +00:00
|
|
|
|
2025-07-03 02:52:06 +00:00
|
|
|
{/* Main Content */}
|
|
|
|
|
<motion.main
|
|
|
|
|
className="flex-1 overflow-auto"
|
|
|
|
|
initial={{ opacity: 0, y: 20 }}
|
|
|
|
|
animate={{ opacity: 1, y: 0 }}
|
|
|
|
|
transition={{ delay: 0.3, duration: 0.3 }}
|
|
|
|
|
>
|
|
|
|
|
<div className="h-full">
|
|
|
|
|
{children}
|
|
|
|
|
</div>
|
|
|
|
|
</motion.main>
|
|
|
|
|
</motion.div>
|
|
|
|
|
</AnimatePresence>
|
2025-07-02 15:44:00 +00:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</SidebarProvider>
|
|
|
|
|
);
|
|
|
|
|
};
|