Pending course, rest ready for launch
This commit is contained in:
@@ -4,10 +4,10 @@ import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { ASSISTANT_TOGGLE_EVENT } from "@/components/AssistantDrawer";
|
||||
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 { isTeacherEmailAllowed, readTeacherEmailsBrowser } from "@/lib/auth/teacherAllowlist";
|
||||
import { supabaseBrowser } from "@/lib/supabase/browser";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { cn } from "@/lib/utils";
|
||||
@@ -18,10 +18,13 @@ type NavLink = {
|
||||
};
|
||||
|
||||
const navLinks: NavLink[] = [
|
||||
{ href: "/", label: "Home" },
|
||||
{ href: "/courses", label: "Courses" },
|
||||
{ href: "/case-studies", label: "Case Studies" },
|
||||
{ href: "/practice", label: "Practice" },
|
||||
{ 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() {
|
||||
@@ -29,8 +32,8 @@ export default function Navbar() {
|
||||
const router = useRouter();
|
||||
const [userEmail, setUserEmail] = useState<string | null>(null);
|
||||
const [isTeacher, setIsTeacher] = useState(false);
|
||||
|
||||
const teacherEmails = useMemo(() => readTeacherEmailsBrowser(), []);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const { resolvedTheme, setTheme } = useTheme();
|
||||
|
||||
useEffect(() => {
|
||||
const client = supabaseBrowser();
|
||||
@@ -49,140 +52,185 @@ export default function Navbar() {
|
||||
const email = cookieMap.get(DEMO_AUTH_EMAIL_COOKIE) ?? null;
|
||||
const role = cookieMap.get(DEMO_AUTH_ROLE_COOKIE) ?? "";
|
||||
setUserEmail(email);
|
||||
setIsTeacher(role === "teacher" || isTeacherEmailAllowed(email, teacherEmails));
|
||||
setIsTeacher(role === "teacher");
|
||||
return;
|
||||
}
|
||||
|
||||
let mounted = true;
|
||||
|
||||
client.auth.getUser().then(({ data }) => {
|
||||
const fetchSession = async () => {
|
||||
const {
|
||||
data: { user },
|
||||
} = await client.auth.getUser();
|
||||
if (!mounted) return;
|
||||
const email = data.user?.email ?? null;
|
||||
const email = user?.email ?? null;
|
||||
setUserEmail(email);
|
||||
setIsTeacher(isTeacherEmailAllowed(email, teacherEmails));
|
||||
});
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
const { data } = client.auth.onAuthStateChange((_event, session) => {
|
||||
const email = session?.user?.email ?? null;
|
||||
setUserEmail(email);
|
||||
setIsTeacher(isTeacherEmailAllowed(email, teacherEmails));
|
||||
fetchSession();
|
||||
|
||||
const {
|
||||
data: { subscription },
|
||||
} = client.auth.onAuthStateChange(() => {
|
||||
fetchSession();
|
||||
});
|
||||
|
||||
return () => {
|
||||
mounted = false;
|
||||
data.subscription.unsubscribe();
|
||||
subscription.unsubscribe();
|
||||
};
|
||||
}, [teacherEmails]);
|
||||
}, []);
|
||||
|
||||
const links = useMemo(() => {
|
||||
if (!isTeacher) return navLinks;
|
||||
return [...navLinks, { href: "/teacher", label: "Teacher Dashboard" }];
|
||||
}, [isTeacher]);
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
const authNode = useMemo(() => {
|
||||
if (!userEmail) {
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="outline" size="sm" asChild>
|
||||
<Link href="/auth/login">Login</Link>
|
||||
</Button>
|
||||
<Button size="sm" asChild>
|
||||
<Link href="/auth/signup">Sign up</Link>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
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;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<span className="hidden max-w-36 truncate text-muted-foreground sm:inline-block">{userEmail}</span>
|
||||
{!isTeacher ? (
|
||||
<Link
|
||||
className="inline-flex items-center rounded-md border border-amber-300 bg-amber-50 px-2 py-1 text-xs font-semibold text-amber-900"
|
||||
href="/auth/login?role=teacher"
|
||||
>
|
||||
Teacher area (Teacher only)
|
||||
</Link>
|
||||
) : null}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={async () => {
|
||||
const client = supabaseBrowser();
|
||||
if (!client) {
|
||||
document.cookie = `${DEMO_AUTH_EMAIL_COOKIE}=; path=/; max-age=0`;
|
||||
document.cookie = `${DEMO_AUTH_ROLE_COOKIE}=; path=/; max-age=0`;
|
||||
setUserEmail(null);
|
||||
setIsTeacher(false);
|
||||
router.refresh();
|
||||
return;
|
||||
}
|
||||
await client.auth.signOut();
|
||||
}}
|
||||
>
|
||||
Logout
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}, [isTeacher, router, userEmail]);
|
||||
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-40 border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
||||
<div className="mx-auto flex w-full max-w-[1300px] items-center justify-between gap-4 px-4 py-3">
|
||||
<div className="flex items-center gap-8">
|
||||
<Link className="flex items-center gap-3" href="/">
|
||||
<div className="rounded-xl bg-accent p-1.5 shadow-sm">
|
||||
<Image alt="ACVE logo" className="h-10 w-10 rounded-lg object-cover" height={40} src="/images/logo.png" width={40} />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-2xl font-bold leading-none tracking-tight text-primary md:text-4xl">ACVE</div>
|
||||
<div className="-mt-1 text-xs text-muted-foreground md:text-sm">Centro de Estudios</div>
|
||||
</div>
|
||||
</Link>
|
||||
<nav className="hidden items-center gap-1 text-sm lg:flex">
|
||||
{links.map((link) => {
|
||||
const isActive = pathname === link.href || pathname?.startsWith(`${link.href}/`);
|
||||
return (
|
||||
<Button
|
||||
key={link.href}
|
||||
variant={isActive ? "default" : "ghost"}
|
||||
asChild
|
||||
className={cn("rounded-xl text-sm font-semibold", !isActive && "text-muted-foreground hover:text-primary")}
|
||||
>
|
||||
<Link href={link.href}>{link.label}</Link>
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
</div>
|
||||
<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="flex items-center gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="border-primary/20 text-primary hover:bg-primary/5 hover:text-primary"
|
||||
onClick={() => window.dispatchEvent(new Event(ASSISTANT_TOGGLE_EVENT))}
|
||||
>
|
||||
AI Assistant
|
||||
</Button>
|
||||
{authNode}
|
||||
<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>
|
||||
<nav className="mx-auto flex w-full max-w-[1300px] gap-2 overflow-x-auto px-4 pb-3 text-sm lg:hidden">
|
||||
{links.map((link) => {
|
||||
const isActive = pathname === link.href;
|
||||
return (
|
||||
<Button
|
||||
key={link.href}
|
||||
variant={isActive ? "default" : "ghost"}
|
||||
size="sm"
|
||||
asChild
|
||||
className="whitespace-nowrap rounded-xl"
|
||||
>
|
||||
<Link href={link.href}>{link.label}</Link>
|
||||
|
||||
<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>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user