186 lines
6.8 KiB
TypeScript
186 lines
6.8 KiB
TypeScript
"use client";
|
||
|
||
import { useEffect, useRef, useState } from "react";
|
||
import { useRouter } from "next/navigation";
|
||
import GroupDropdown from "@/features/groups/components/group-dropdown";
|
||
import { useAuthContext } from "@/hooks/auth-context";
|
||
import { useGroupsContext } from "@/hooks/groups-context";
|
||
|
||
export default function Navbar() {
|
||
const router = useRouter();
|
||
const { checkSession, logout } = useAuthContext();
|
||
const { activeGroupId } = useGroupsContext();
|
||
const [inviteModalCode, setInviteModalCode] = useState<string | null>(null);
|
||
const [inviteCopied, setInviteCopied] = useState(false);
|
||
const [hideNavbar, setHideNavbar] = useState(false);
|
||
const [userMenuOpen, setUserMenuOpen] = useState(false);
|
||
const [userEmail, setUserEmail] = useState<string | null>(null);
|
||
const userMenuRef = useRef<HTMLDivElement | null>(null);
|
||
|
||
async function handleCopyInvite() {
|
||
if (!inviteModalCode) return;
|
||
|
||
await navigator.clipboard.writeText(inviteModalCode);
|
||
setInviteCopied(true);
|
||
}
|
||
|
||
async function handleLogout() {
|
||
await logout();
|
||
setUserMenuOpen(false);
|
||
router.push("/login");
|
||
}
|
||
|
||
useEffect(() => {
|
||
let active = true;
|
||
async function loadUser() {
|
||
const user = await checkSession();
|
||
if (active) setUserEmail(user?.email || null);
|
||
}
|
||
loadUser();
|
||
return () => {
|
||
active = false;
|
||
};
|
||
}, [checkSession]);
|
||
|
||
useEffect(() => {
|
||
function handleClickOutside(event: MouseEvent) {
|
||
if (!userMenuOpen || !userMenuRef.current) return;
|
||
if (!userMenuRef.current.contains(event.target as Node))
|
||
setUserMenuOpen(false);
|
||
}
|
||
document.addEventListener("mousedown", handleClickOutside);
|
||
return () => document.removeEventListener("mousedown", handleClickOutside);
|
||
}, [userMenuOpen]);
|
||
|
||
useEffect(() => {
|
||
if (!inviteModalCode) return;
|
||
function handleKeyDown(event: KeyboardEvent) {
|
||
if (event.key === "Escape") setInviteModalCode(null);
|
||
}
|
||
window.addEventListener("keydown", handleKeyDown);
|
||
return () => window.removeEventListener("keydown", handleKeyDown);
|
||
}, [inviteModalCode]);
|
||
|
||
useEffect(() => {
|
||
let lastY = window.scrollY;
|
||
function onScroll() {
|
||
const current = window.scrollY;
|
||
const diff = current - lastY;
|
||
if (Math.abs(diff) < 6) return;
|
||
if (current <= 8) {
|
||
setHideNavbar(false);
|
||
} else if (diff > 0) {
|
||
setHideNavbar(true);
|
||
} else {
|
||
setHideNavbar(false);
|
||
}
|
||
lastY = current;
|
||
}
|
||
window.addEventListener("scroll", onScroll, { passive: true });
|
||
return () => window.removeEventListener("scroll", onScroll);
|
||
}, []);
|
||
|
||
return (
|
||
<>
|
||
<header className={`sticky top-0 z-40 border-b border-accent-weak bg-app backdrop-blur transition-transform duration-200 ${hideNavbar ? "-translate-y-full" : "translate-y-0"}`}>
|
||
<div className="mx-auto flex max-w-5xl items-center justify-between px-4 py-2.5">
|
||
<div className="flex items-center gap-2">
|
||
<button
|
||
type="button"
|
||
className="flex h-9 w-9 items-center justify-center overflow-hidden rounded-xl border border-accent bg-panel hover:border-accent-strong"
|
||
aria-label="Settings"
|
||
onClick={() => {
|
||
if (activeGroupId) router.push("/groups/settings");
|
||
else router.push("/");
|
||
}}
|
||
>
|
||
<img src="/icons/navbar-settings.png" alt="" className="h-full w-full rounded-lg object-cover" />
|
||
</button>
|
||
<div className="hidden sm:block text-sm font-semibold">Fiddy</div>
|
||
</div>
|
||
|
||
<GroupDropdown
|
||
onInviteCode={code => {
|
||
setInviteModalCode(code);
|
||
setInviteCopied(false);
|
||
}}
|
||
/>
|
||
|
||
<div className="relative" ref={userMenuRef}>
|
||
<button
|
||
type="button"
|
||
aria-label="User menu"
|
||
onClick={() => setUserMenuOpen(prev => !prev)}
|
||
className="flex h-9 w-9 items-center justify-center rounded-full border border-accent-weak bg-panel text-sm text-muted hover:border-accent"
|
||
>
|
||
<span className="text-base">👤</span>
|
||
</button>
|
||
{userMenuOpen ? (
|
||
<div className="absolute right-0 mt-2 w-48 rounded-xl border border-accent-weak bg-panel p-3 text-sm shadow-lg">
|
||
<div className="text-xs text-soft">Signed in as</div>
|
||
<div className="mt-1 truncate text-sm font-semibold text-[color:var(--color-text)]">
|
||
{userEmail || "User"}
|
||
</div>
|
||
<div className="my-3 h-px divider" />
|
||
<button
|
||
type="button"
|
||
className="w-full rounded-lg btn-outline-accent px-3 py-2 text-xs font-semibold"
|
||
onClick={() => {
|
||
setUserMenuOpen(false);
|
||
router.push("/settings");
|
||
}}
|
||
>
|
||
Settings
|
||
</button>
|
||
<button
|
||
type="button"
|
||
className="mt-2 w-full rounded-lg border border-red-400/60 bg-red-500/10 px-3 py-2 text-xs font-semibold text-red-200"
|
||
onClick={handleLogout}
|
||
>
|
||
Logout
|
||
</button>
|
||
</div>
|
||
) : null}
|
||
</div>
|
||
</div>
|
||
</header>
|
||
{inviteModalCode ? (
|
||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 px-4 py-6 overflow-y-auto">
|
||
<div
|
||
className="w-full max-w-sm rounded-2xl border border-accent-weak bg-panel p-5 shadow-xl max-h-[90vh]"
|
||
onKeyDown={event => {
|
||
if (event.key === "Escape") setInviteModalCode(null);
|
||
}}
|
||
role="dialog"
|
||
tabIndex={-1}
|
||
>
|
||
<div className="text-lg font-semibold">Invite code</div>
|
||
<p className="mt-2 text-sm text-muted">
|
||
Share this code to invite members. You can view it later in group settings.
|
||
</p>
|
||
<div className="mt-4 rounded-lg border border-accent-weak bg-surface px-3 py-2 text-center text-lg tracking-widest">
|
||
{inviteModalCode}
|
||
</div>
|
||
<div className="mt-4 flex items-center gap-2">
|
||
<button
|
||
type="button"
|
||
className="flex-1 rounded-lg btn-accent px-3 py-2 text-sm font-semibold"
|
||
onClick={handleCopyInvite}
|
||
>
|
||
{inviteCopied ? "Copied" : "Copy code"}
|
||
</button>
|
||
<button
|
||
type="button"
|
||
className="flex-1 rounded-lg btn-outline-accent px-3 py-2 text-sm font-semibold"
|
||
onClick={() => setInviteModalCode(null)}
|
||
>
|
||
Close
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
) : null}
|
||
</>
|
||
);
|
||
}
|