docv/app/dashboard/layout.tsx

271 lines
10 KiB
TypeScript

"use client"
import type React from "react"
import { useState, useEffect, useCallback } from "react"
import { useRouter, usePathname } from "next/navigation"
import Link from "next/link"
import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import {
Shield,
Settings,
Bell,
LogOut,
ChevronDown,
Home,
TestTube,
User,
Copy,
CheckCircle,
} from "@/lib/icons"
import UserStore from "@/lib/4nk/UserStore"
import EventBus from "@/lib/4nk/EventBus"
import AuthModal from "@/components/4nk/AuthModal"
import Iframe from "@/components/4nk/Iframe"
import { iframeUrl } from "../page"
import { FourNKProvider } from "@/lib/contexts/FourNKContext";
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
const [isConnected, setIsConnected] = useState(false)
const [userPairingId, setUserPairingId] = useState<string | null>(null)
const [isAuthenticated, setIsAuthenticated] = useState(false)
const [isAuthModalOpen, setIsAuthModalOpen] = useState(false)
const [show4nkAuthModal, setShow4nkAuthModal] = useState(false)
const [isLoading, setIsLoading] = useState(true)
const [isMockMode, setIsMockMode] = useState(true)
const [userInfo, setUserInfo] = useState<any>(null)
const [showLogoutConfirm, setShowLogoutConfirm] = useState(false)
const [isCopied, setIsCopied] = useState(false)
const router = useRouter()
useEffect(() => {
try {
const saved = typeof window !== 'undefined' ? localStorage.getItem('theme') : null
const dark = saved ? saved === 'dark' : (typeof window !== 'undefined' && window.matchMedia('(prefers-color-scheme: dark)').matches)
if (typeof document !== 'undefined') {
document.documentElement.classList.toggle('dark', !!dark)
}
} catch { }
}, [])
useEffect(() => {
const connected = UserStore.getInstance().isConnected();
setIsConnected(connected);
if (!connected) {
setShow4nkAuthModal(true);
}
}, []);
useEffect(() => {
const pairingId = UserStore.getInstance().getUserPairingId();
setUserPairingId(pairingId);
}, []);
useEffect(() => {
const checkAuthentication = async () => {
try {
const userStore = UserStore.getInstance()
const accessToken = userStore.getAccessToken()
if (accessToken) {
setIsAuthenticated(true)
const pairingId = userStore.getUserPairingId()
setUserInfo({
id: pairingId?.slice(0, 8) + "...",
name: "Utilisateur 4NK",
email: "user@4nk.io",
role: "Utilisateur",
company: "Organisation 4NK",
})
}
} catch (error) {
console.error("Error checking authentication:", error)
setIsAuthModalOpen(true)
} finally {
setIsLoading(false)
}
}
checkAuthentication()
}, [iframeUrl])
const handle4nkConnect = useCallback(() => {
setIsConnected(true);
setShow4nkAuthModal(false);
}, []);
const handle4nkClose = useCallback(() => {
if (!isConnected) return;
setShow4nkAuthModal(false);
}, [isConnected]);
const handleLogout = useCallback(() => {
UserStore.getInstance().disconnect();
setIsConnected(false);
setUserPairingId(null);
EventBus.getInstance().emit('CLEAR_CONSOLE');
setShowLogoutConfirm(true)
}, []);
const handleCopyToClipboard = useCallback(() => {
if (userPairingId) {
navigator.clipboard.writeText(userPairingId).then(() => {
setIsCopied(true);
setTimeout(() => setIsCopied(false), 2000);
}).catch(err => {
console.error('Erreur lors de la copie : ', err);
});
}
}, [userPairingId]);
return (
<div className="flex h-screen bg-gray-50 dark:bg-gray-900">
{/* Main content (prend tout l'écran) */}
<div className="flex-1 flex flex-col overflow-hidden">
{/* --- TOP BAR (MODIFIÉE) --- */}
<div className="bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 px-6 py-3 shadow-sm">
<div className="flex items-center justify-between">
{/* Partie Gauche: Logo */}
<div className="flex items-center space-x-2">
<Shield className="h-8 w-8 text-blue-600 dark:text-blue-400" />
<span className="text-xl font-bold text-gray-900 dark:text-gray-100">DocV</span>
{isMockMode && (
<Badge
variant="outline"
className="bg-green-50 dark:bg-green-800 text-green-700 dark:text-green-200 border-green-200 dark:border-green-700 text-xs"
>
<TestTube className="h-3 w-3 mr-1" />
Démo
</Badge>
)}
</div>
{/* Partie Droite: Icônes + Profil Utilisateur */}
<div className="flex items-center space-x-3">
{/* TODO: Icone de cloche pour une future lsite de notifications */}
<Button variant="ghost" size="sm" className="text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100">
<Bell className="h-5 w-5" />
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="flex items-center space-x-2 px-2 py-1 h-10">
<div className="w-8 h-8 bg-blue-100 dark:bg-blue-800 rounded-full flex items-center justify-center">
<span className="text-blue-600 dark:text-blue-400 font-medium text-sm">
{userInfo ? userInfo.name.charAt(0) : '?'}
</span>
</div>
<div className="hidden md:flex items-center">
<span className="text-sm font-medium text-gray-900 dark:text-gray-100">
{userInfo ? userInfo.name : 'Chargement...'}
</span>
<ChevronDown className="h-4 w-4 text-gray-400 ml-1" />
</div>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-56">
<DropdownMenuLabel>
<div className="flex items-center justify-between group">
<p className="text-sm font-medium truncate" title={userPairingId || ""}>
{userInfo?.id}
</p>
<Button
variant="ghost"
size="icon"
className="h-6 w-6"
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
handleCopyToClipboard();
}}
>
{isCopied ? (
<CheckCircle className="h-4 w-4 text-green-500" />
) : (
<Copy className="h-4 w-4 text-gray-500 group-hover:text-gray-900 dark:group-hover:text-gray-100" />
)}
</Button>
</div>
<p className="text-sm font-medium">{userInfo?.name}</p>
<p className="text-xs text-gray-500 dark:text-gray-400">{userInfo?.company}</p>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem>
<User className="h-4 w-4 mr-2" />
<span>Profil</span>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link href="/dashboard/settings">
<Settings className="h-4 w-4 mr-2" />
<span>Paramètres</span>
</Link>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={handleLogout} className="text-red-500 focus:text-red-500 focus:bg-red-50 dark:focus:bg-red-900/50">
<LogOut className="h-4 w-4 mr-2" />
<span>Déconnexion</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</div>
{/* Page content */}
<main className="flex-1 overflow-hidden bg-gray-900">
<FourNKProvider>
{children}
</FourNKProvider>
</main>
</div>
{/* --- Modal de déconnexion --- */}
{showLogoutConfirm && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50">
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 max-w-md w-full mx-4">
<div className="text-center">
<Shield className="h-12 w-12 mx-auto mb-4 text-blue-600 dark:text-blue-400" />
<h3 className="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-2">Déconnexion réussie</h3>
<p className="text-gray-600 dark:text-gray-300 mb-6">
Vous avez é déconnecté de votre espace sécurisé DocV.
</p>
<div className="space-y-3">
<Button onClick={() => router.push("/")} variant="outline" className="w-full">
<Home className="h-4 w-4 mr-2" />
Retourner à l'accueil
</Button>
</div>
<p className="text-xs text-gray-500 dark:text-gray-400 mt-4">
Vos données restent sécurisées par le chiffrement 4NK
</p>
</div>
</div>
</div>
)}
{/* --- Modals 4NK --- */}
{show4nkAuthModal && (
<AuthModal
isOpen={show4nkAuthModal}
onClose={handle4nkClose}
onConnect={handle4nkConnect}
iframeUrl={iframeUrl}
/>
)}
{isConnected && <Iframe iframeUrl={iframeUrl} />}
</div>
)
}