Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 15 additions & 15 deletions web3nova-revamp/components/Desk/ApplicantModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,23 +44,23 @@ export default function ApplicantModal({ matric, onClose }) {
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={onClose}
className="fixed inset-0 bg-black/70 backdrop-blur-sm z-50 flex items-center justify-center p-4"
className="fixed inset-0 bg-black/70 backdrop-blur-sm z-50 flex items-center justify-center p-3 sm:p-4"
>
<motion.div
initial={{ opacity: 0, y: 12, scale: 0.98 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: 12, scale: 0.98 }}
onClick={(e) => e.stopPropagation()}
className="bg-zinc-950 border border-zinc-800 rounded-2xl max-w-2xl w-full max-h-[85vh] overflow-auto"
className="bg-zinc-950 border border-zinc-800 rounded-2xl w-full sm:max-w-2xl max-h-[90vh] sm:max-h-[85vh] overflow-auto"
>
<div className="flex items-center justify-between px-6 py-4 border-b border-zinc-900 sticky top-0 bg-zinc-950">
<h2 className="font-semibold">Applicant Details</h2>
<div className="flex items-center justify-between px-4 sm:px-6 py-4 border-b border-zinc-900 sticky top-0 bg-zinc-950">
<h2 className="font-semibold text-sm sm:text-base">Applicant Details</h2>
<button onClick={onClose} className="text-zinc-400 hover:text-white">
<X size={18} />
</button>
</div>

<div className="p-6">
<div className="p-4 sm:p-6">
{error && (
<div className="text-sm text-red-400 bg-red-950/30 border border-red-900/50 rounded-lg px-3 py-2">
{error}
Expand All @@ -75,17 +75,17 @@ export default function ApplicantModal({ matric, onClose }) {

{data && (
<div className="space-y-5">
<div className="flex items-center gap-4">
<div className="flex flex-col sm:flex-row sm:items-center gap-4">
<div className="w-20 h-20 rounded-full bg-zinc-900 overflow-hidden flex items-center justify-center shrink-0">
{data.photo_url ? (
<img src={data.photo_url} alt="" className="w-full h-full object-cover" />
) : (
<User size={32} className="text-zinc-600" />
)}
</div>
<div>
<div className="text-lg font-semibold">{data.full_name}</div>
<div className="text-xs font-mono text-zinc-500">{data.Matriculation_Number}</div>
<div className="flex-1">
<div className="text-base sm:text-lg font-semibold">{data.full_name}</div>
<div className="text-xs font-mono text-zinc-500 truncate">{data.Matriculation_Number}</div>
<div className="mt-1">
{data.status === "admitted" ? (
<span className="text-green-400 text-xs bg-green-950/40 px-2 py-0.5 rounded">admitted</span>
Expand All @@ -98,7 +98,7 @@ export default function ApplicantModal({ matric, onClose }) {
</div>
</div>

<div className="grid grid-cols-1 md:grid-cols-2 gap-3 text-sm">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2 sm:gap-3 text-xs sm:text-sm">
<Field icon={Mail} label="Email" value={data.email} />
<Field icon={Phone} label="Phone" value={data.phone_number} />
<Field icon={Users} label="Parent contact" value={data.Parent_contact} />
Expand All @@ -116,23 +116,23 @@ export default function ApplicantModal({ matric, onClose }) {
<Fingerprint size={12} /> Passkey
</div>
{!confirmReset ? (
<div className="flex items-center justify-between gap-3 bg-zinc-900/50 border border-zinc-900 rounded-lg px-3 py-3">
<p className="text-sm text-zinc-400">
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 bg-zinc-900/50 border border-zinc-900 rounded-lg px-3 py-3">
<p className="text-xs sm:text-sm text-zinc-400">
Reset this intern's passkey so they can re-register a new device at <span className="font-mono text-zinc-300">/recovery</span>.
</p>
<button
onClick={() => setConfirmReset(true)}
className="shrink-0 inline-flex items-center gap-1 bg-amber-900/40 hover:bg-amber-900/70 border border-amber-900 text-amber-200 px-3 py-1.5 rounded text-xs"
className="w-full sm:w-auto shrink-0 inline-flex items-center justify-center gap-1 bg-amber-900/40 hover:bg-amber-900/70 border border-amber-900 text-amber-200 px-3 py-1.5 rounded text-xs"
>
<ShieldAlert size={12} /> Reset passkey
</button>
</div>
) : (
<div className="bg-amber-950/30 border border-amber-900/50 rounded-lg p-3">
<p className="text-sm text-amber-200 mb-3">
<p className="text-xs sm:text-sm text-amber-200 mb-3">
Clear passkey for <span className="font-medium">{data.full_name}</span>? They'll have to set up a new one via /recovery.
</p>
<div className="flex justify-end gap-2">
<div className="flex flex-col-reverse sm:flex-row justify-end gap-2">
<button
onClick={() => setConfirmReset(false)}
disabled={resetting}
Expand Down
179 changes: 112 additions & 67 deletions web3nova-revamp/components/Desk/DeskLayout.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useEffect, useState } from "react";
import { useRouter } from "next/router";
import Link from "next/link";
import { Users, UserCheck, Clock, LogOut, Wifi, RefreshCw } from "lucide-react";
import { Users, UserCheck, Clock, LogOut, Wifi, RefreshCw, Menu, X } from "lucide-react";
import { getToken, clearToken, deskFetch } from "@/lib/deskApi";

const NAV = [
Expand All @@ -10,12 +10,85 @@ const NAV = [
{ href: "/desk/attendance", label: "Attendance", icon: Clock },
];

function Sidebar({ router, hub, syncing, syncMsg, syncHubIp, logout, onClose }) {
return (
<aside className="w-full bg-zinc-950 flex flex-col h-full">
<div className="px-5 py-6 border-b border-zinc-900 flex items-center justify-between">
<div>
<div className="text-lg font-semibold">Desk</div>
<div className="text-xs text-zinc-500">Web3Nova</div>
</div>
{onClose && (
<button type="button" onClick={onClose} aria-label="Close navigation" className="text-zinc-400 hover:text-white md:hidden">
<X size={18} />
</button>
)}
</div>

<nav className="flex-1 py-4">
{NAV.map(({ href, label, icon: Icon }) => {
const active = router.pathname === href;
return (
<Link
key={href}
href={href}
onClick={onClose}
className={`flex items-center gap-3 px-5 py-2.5 text-sm ${
active
? "bg-zinc-900 text-white border-l-2 border-white"
: "text-zinc-400 hover:text-white hover:bg-zinc-900/50"
}`}
>
<Icon size={16} />
{label}
</Link>
);
})}
</nav>

<div className="border-t border-zinc-900 px-5 py-4 space-y-2">
<div className="flex items-center gap-2 text-xs text-zinc-500">
<Wifi size={12} />
<span className="font-mono truncate">{hub?.current_ip || "not set"}</span>
</div>
{hub?.updated_at && (
<div className="text-[10px] text-zinc-600">
updated {new Date(hub.updated_at).toLocaleString()}
</div>
)}
<button
onClick={syncHubIp}
disabled={syncing}
className="w-full flex items-center justify-center gap-2 text-xs bg-zinc-900 hover:bg-zinc-800 disabled:opacity-50 text-zinc-300 py-2 rounded border border-zinc-800"
>
<RefreshCw size={12} className={syncing ? "animate-spin" : ""} />
{syncing ? "syncing..." : "Sync hub IP"}
</button>
{syncMsg && (
<div className={`text-[10px] ${syncMsg.type === "ok" ? "text-emerald-400" : "text-red-400"}`}>
{syncMsg.text}
</div>
)}
</div>

<button
onClick={logout}
className="flex items-center gap-3 px-5 py-3 text-sm text-zinc-400 hover:text-red-400 border-t border-zinc-900"
>
<LogOut size={16} />
Sign out
</button>
</aside>
);
}

export default function DeskLayout({ children, title }) {
const router = useRouter();
const [ready, setReady] = useState(false);
const [hub, setHub] = useState(null);
const [syncing, setSyncing] = useState(false);
const [syncMsg, setSyncMsg] = useState(null);
const [drawerOpen, setDrawerOpen] = useState(false);

useEffect(() => {
if (!getToken()) {
Expand All @@ -26,6 +99,9 @@ export default function DeskLayout({ children, title }) {
}
}, [router]);

// Close drawer on route change
useEffect(() => { setDrawerOpen(false); }, [router.pathname]);

async function syncHubIp() {
setSyncing(true);
setSyncMsg(null);
Expand All @@ -47,78 +123,47 @@ export default function DeskLayout({ children, title }) {

if (!ready) return null;

const sidebarProps = { router, hub, syncing, syncMsg, syncHubIp, logout };

return (
<div className="min-h-screen bg-black text-white flex">
<aside className="w-60 border-r border-zinc-900 bg-zinc-950 flex flex-col">
<div className="px-5 py-6 border-b border-zinc-900">
<div className="text-lg font-semibold">Desk</div>
<div className="text-xs text-zinc-500">Web3Nova</div>
</div>

<nav className="flex-1 py-4">
{NAV.map(({ href, label, icon: Icon }) => {
const active = router.pathname === href;
return (
<Link
key={href}
href={href}
className={`flex items-center gap-3 px-5 py-2.5 text-sm ${
active
? "bg-zinc-900 text-white border-l-2 border-white"
: "text-zinc-400 hover:text-white hover:bg-zinc-900/50"
}`}
>
<Icon size={16} />
{label}
</Link>
);
})}
</nav>

<div className="border-t border-zinc-900 px-5 py-4 space-y-2">
<div className="flex items-center gap-2 text-xs text-zinc-500">
<Wifi size={12} />
<span className="font-mono truncate">
{hub?.current_ip || "not set"}
</span>
</div>
{hub?.updated_at && (
<div className="text-[10px] text-zinc-600">
updated {new Date(hub.updated_at).toLocaleString()}
</div>
)}
<button
onClick={syncHubIp}
disabled={syncing}
className="w-full flex items-center justify-center gap-2 text-xs bg-zinc-900 hover:bg-zinc-800 disabled:opacity-50 text-zinc-300 py-2 rounded border border-zinc-800"
>
<RefreshCw size={12} className={syncing ? "animate-spin" : ""} />
{syncing ? "syncing..." : "Sync hub IP"}
</button>
{syncMsg && (
<div
className={`text-[10px] ${
syncMsg.type === "ok" ? "text-emerald-400" : "text-red-400"
}`}
>
{syncMsg.text}
</div>
)}
{/* Desktop sidebar */}
<div className="hidden md:flex w-60 border-r border-zinc-900 flex-col">
<Sidebar {...sidebarProps} />
</div>

{/* Mobile drawer overlay */}
{drawerOpen && (
<div
className="fixed inset-0 z-40 bg-black/60 md:hidden"
onClick={() => setDrawerOpen(false)}
/>
)}

{/* Mobile drawer */}
{drawerOpen && (
<div className="fixed inset-y-0 left-0 z-50 w-60 border-r border-zinc-900 flex flex-col md:hidden">
<Sidebar {...sidebarProps} onClose={() => setDrawerOpen(false)} />
</div>
)}

<button
onClick={logout}
className="flex items-center gap-3 px-5 py-3 text-sm text-zinc-400 hover:text-red-400 border-t border-zinc-900"
>
<LogOut size={16} />
Sign out
</button>
</aside>
{/* Main content */}
<div className="flex-1 flex flex-col min-w-0">

{/* Mobile top bar */}
<header className="md:hidden flex items-center gap-3 px-4 py-3 border-b border-zinc-900 bg-zinc-950">
<button type="button" onClick={() => setDrawerOpen(true)} aria-label="Open navigation" className="text-zinc-400 hover:text-white">
<Menu size={20} />
</button>
<span className="text-sm font-semibold">{title || "Desk"}</span>
</header>

<main className="flex-1 p-8 overflow-auto">
{title && <h1 className="text-2xl font-semibold mb-6">{title}</h1>}
{children}
</main>
<main className="flex-1 p-4 md:p-8 overflow-auto">
{title && <h1 className="hidden md:block text-2xl font-semibold mb-6">{title}</h1>}
{children}
</main>
</div>
</div>
);
}
Loading