Files
ACVE/components/Navbar.tsx
2026-03-15 13:52:11 +00:00

237 lines
7.9 KiB
TypeScript
Executable File

"use client";
import Image from "next/image";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { usePathname } from "next/navigation";
import { useEffect, useState } from "react";
import { Moon, Sun } from "lucide-react";
import { useTheme } from "next-themes";
import { DEMO_AUTH_EMAIL_COOKIE, DEMO_AUTH_ROLE_COOKIE } from "@/lib/auth/demoAuth";
import { supabaseBrowser } from "@/lib/supabase/browser";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
type NavLink = {
href: string;
label: string;
};
const navLinks: NavLink[] = [
{ href: "/courses", label: "Formación Académica" },
{ href: "/case-studies", label: "Casos prácticos" },
{ href: "/practice", label: "Retos" },
{ href: "/eventos", label: "Eventos" },
{ href: "/noticias", label: "Noticias" },
{ href: "/comunidad", label: "Comunidad" },
{ href: "/sobre-acve", label: "Sobre ACVE" },
];
export default function Navbar() {
const pathname = usePathname();
const router = useRouter();
const [userEmail, setUserEmail] = useState<string | null>(null);
const [isTeacher, setIsTeacher] = useState(false);
const [mounted, setMounted] = useState(false);
const { resolvedTheme, setTheme } = useTheme();
useEffect(() => {
const client = supabaseBrowser();
if (!client) {
const cookieMap = new Map(
document.cookie
.split(";")
.map((entry) => entry.trim())
.filter(Boolean)
.map((entry) => {
const [key, ...rest] = entry.split("=");
return [key, decodeURIComponent(rest.join("="))] as const;
}),
);
const email = cookieMap.get(DEMO_AUTH_EMAIL_COOKIE) ?? null;
const role = cookieMap.get(DEMO_AUTH_ROLE_COOKIE) ?? "";
setUserEmail(email);
setIsTeacher(role === "teacher");
return;
}
let mounted = true;
const fetchSession = async () => {
const {
data: { user },
} = await client.auth.getUser();
if (!mounted) return;
const email = user?.email ?? null;
setUserEmail(email);
if (!user) {
setIsTeacher(false);
return;
}
try {
const res = await fetch("/api/auth/session");
if (!mounted) return;
const data = await res.json();
setIsTeacher(data.isTeacher === true);
} catch {
if (!mounted) return;
setIsTeacher(false);
}
};
fetchSession();
const {
data: { subscription },
} = client.auth.onAuthStateChange(() => {
fetchSession();
});
return () => {
mounted = false;
subscription.unsubscribe();
};
}, []);
useEffect(() => {
setMounted(true);
}, []);
const handleLogout = async () => {
const loginSwitchUrl = "/auth/login?switchUser=1&redirectTo=/courses";
document.cookie = `${DEMO_AUTH_EMAIL_COOKIE}=; path=/; max-age=0`;
document.cookie = `${DEMO_AUTH_ROLE_COOKIE}=; path=/; max-age=0`;
const client = supabaseBrowser();
if (!client) {
setUserEmail(null);
setIsTeacher(false);
router.replace(loginSwitchUrl);
router.refresh();
return;
}
await client.auth.signOut();
setUserEmail(null);
setIsTeacher(false);
router.replace(loginSwitchUrl);
router.refresh();
};
const isDark = resolvedTheme === "dark";
const loginHref = userEmail ? "/profile" : "/auth/login";
const loginLabel = userEmail ? "Mi cuenta" : "Ingresa";
const isNavActive = (href: string) => {
return pathname === href || pathname?.startsWith(`${href}/`);
};
return (
<header className="sticky top-0 z-50 border-b border-border/70 bg-background/90 backdrop-blur supports-[backdrop-filter]:bg-background/70">
<div className="border-b border-border/60 bg-card/80">
<div className="mx-auto flex w-full max-w-[1300px] items-center gap-3 px-4 py-2 text-xs text-muted-foreground md:px-6">
<div className="hidden items-center gap-4 sm:flex">
<Link className="hover:text-foreground" href="/auth/signup">
Únete
</Link>
<Link className="hover:text-foreground" href={loginHref}>
{loginLabel}
</Link>
<Link className="hover:text-foreground" href="/#footer-contact">
Contáctanos
</Link>
</div>
<div className="ml-auto flex items-center gap-2">
<Button
aria-label={isDark ? "Cambiar a modo claro" : "Cambiar a modo oscuro"}
className="rounded-full"
size="icon"
type="button"
variant="ghost"
onClick={() => setTheme(isDark ? "light" : "dark")}
>
{mounted && isDark ? <Sun className="h-4 w-4" /> : <Moon className="h-4 w-4" />}
</Button>
{userEmail ? (
<>
<span className="hidden max-w-44 truncate px-2 text-muted-foreground md:inline-block">{userEmail}</span>
<Button className="rounded-full" size="sm" variant="outline" asChild>
<Link href="/profile">Mi cuenta</Link>
</Button>
<Button className="rounded-full" size="sm" type="button" variant="ghost" onClick={handleLogout}>
Salir
</Button>
</>
) : (
<Button className="rounded-full" size="sm" variant="outline" asChild>
<Link href="/auth/login">Ingresa</Link>
</Button>
)}
</div>
</div>
</div>
<div className="mx-auto w-full max-w-[1300px] px-4 md:px-6">
<div className="flex items-center justify-between gap-4 py-4">
<Link className="flex items-center gap-3" href="/">
<div className="rounded-xl bg-primary/10 p-1.5 shadow-sm ring-1 ring-primary/20">
<Image alt="ACVE logo" className="h-10 w-10 rounded-lg object-cover" height={40} src="/images/logo.png" width={40} />
</div>
<div className="leading-tight">
<div className="text-2xl font-bold tracking-tight text-primary md:text-3xl">ACVE Centro de Estudios</div>
<div className="text-xs text-muted-foreground md:text-sm">Empowering Lawyer one word at a time</div>
</div>
</Link>
<div className="flex items-center gap-2">
{isTeacher ? (
<Button className="hidden rounded-full md:inline-flex" size="sm" variant="outline" asChild>
<Link href="/teacher">Panel docente</Link>
</Button>
) : null}
<Button className="rounded-full px-5" asChild>
<Link href="/auth/signup">¡Inscríbete!</Link>
</Button>
</div>
</div>
<nav className="hidden items-center gap-1 border-t border-border/70 py-3 text-sm lg:flex">
{navLinks.map((link) => {
const isActive = isNavActive(link.href);
return (
<Button
key={link.href}
className={cn("rounded-full px-4 font-semibold", !isActive && "text-muted-foreground hover:text-foreground")}
variant={isActive ? "default" : "ghost"}
asChild
>
<Link href={link.href}>{link.label}</Link>
</Button>
);
})}
</nav>
<nav className="flex gap-2 overflow-x-auto border-t border-border/70 pb-3 pt-2 text-sm lg:hidden">
{navLinks.map((link) => {
const isActive = isNavActive(link.href);
return (
<Button
key={link.href}
className="whitespace-nowrap rounded-full px-4"
size="sm"
variant={isActive ? "default" : "outline"}
asChild
>
<Link href={link.href}>{link.label}</Link>
</Button>
);
})}
</nav>
</div>
</header>
);
}