Files
guilan-ace-frontend/src/components/Navbar.tsx

165 lines
5.9 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import type { ReactNode } from "react";
import { useMemo } from "react";
import Image from "next/image";
import { LayoutDashboard, LogOut, UserRound } from "lucide-react";
import { Link, NavLink } from "@/lib/router";
import { useAuth } from "@/contexts/AuthContext";
import ModeToggle from "@/components/ModeToggle";
import NotificationsBell from "@/components/NotificationsBell";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { cn } from "@/lib/utils";
import type { SiteContent } from "@/lib/content-cms";
const NavItem = ({ to, children }: { to: string; children: ReactNode }) => (
<NavLink
to={to}
className={({ isActive }) =>
cn(
"rounded-lg px-3 py-2 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
isActive
? "bg-secondary text-primary"
: "text-muted-foreground hover:bg-secondary hover:text-foreground",
)
}
>
{children}
</NavLink>
);
function ProfileAvatarMenu() {
const { user, isAuthenticated } = useAuth();
const isAdminUser = isAuthenticated && Boolean(user?.is_staff || user?.is_superuser);
const avatarInitials = useMemo(
() =>
(user?.first_name?.[0] ||
user?.last_name?.[0] ||
user?.username?.[0] ||
"?").toUpperCase(),
[user?.first_name, user?.last_name, user?.username],
);
if (!isAuthenticated) {
return (
<Link to="/auth">
<Button className="rounded-full px-5">ورود / ثبت‌نام</Button>
</Link>
);
}
return (
<DropdownMenu dir="rtl">
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
className="h-11 w-11 rounded-full border-0 bg-transparent p-0 shadow-none transition hover:bg-background/45"
aria-label="منوی حساب کاربری"
>
<Avatar className="h-10 w-10 border border-white/30 shadow-sm">
<AvatarImage
src={
user?.profile_picture_preview_url ||
user?.profile_picture ||
undefined
}
alt={user?.username || "profile"}
/>
<AvatarFallback>{avatarInitials}</AvatarFallback>
</Avatar>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" sideOffset={12} className="w-64 rounded-2xl p-2 text-right">
<DropdownMenuLabel className="text-right">
{[user?.first_name, user?.last_name].filter(Boolean).join(" ") || user?.username || "حساب کاربری"}
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem asChild className="flex-row-reverse justify-end gap-2 rounded-xl">
<Link to="/profile">
مشاهده پروفایل
<UserRound className="h-4 w-4" />
</Link>
</DropdownMenuItem>
{isAdminUser ? (
<DropdownMenuItem asChild className="flex-row-reverse justify-end gap-2 rounded-xl">
<Link to="/admin">
داشبورد مدیریت
<LayoutDashboard className="h-4 w-4" />
</Link>
</DropdownMenuItem>
) : null}
<DropdownMenuSeparator />
<DropdownMenuItem asChild className="flex-row-reverse justify-end gap-2 rounded-xl text-destructive focus:text-destructive">
<Link to="/logout">
خروج از حساب
<LogOut className="h-4 w-4" />
</Link>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}
export default function Navbar({ siteContent }: { siteContent: SiteContent }) {
const { isAuthenticated } = useAuth();
return (
<nav
aria-label="ناوبری اصلی"
className="sticky top-0 z-40 border-b border-border bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/85"
dir="rtl"
>
<div className="container mx-auto px-4 py-2">
<div className="flex min-h-14 items-center justify-between gap-4">
<Link to="/" className="flex min-w-0 items-center gap-3 rounded-lg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">
<div className="grid h-11 w-11 shrink-0 place-items-center rounded-xl bg-brand-wash p-1.5">
<Image
src={siteContent.logo?.url || "/favicon.ico"}
alt={siteContent.logo?.alt || siteContent.short_name || "لوگوی انجمن"}
width={44}
height={44}
unoptimized
className="h-full w-full object-contain"
/>
</div>
<div className="min-w-0 text-right">
<p className="truncate text-sm font-semibold text-foreground sm:text-base">
{siteContent.short_name || siteContent.official_name}
</p>
<p className="hidden text-xs text-muted-foreground sm:block">
{siteContent.faculty_subline}
</p>
</div>
</Link>
<div className="hidden items-center gap-1 md:flex">
<NavItem to="/">{siteContent.navigation.home}</NavItem>
<NavItem to="/events">{siteContent.navigation.events}</NavItem>
<NavItem to="/blog">{siteContent.navigation.blog}</NavItem>
<ModeToggle />
{isAuthenticated ? <NotificationsBell /> : null}
<ProfileAvatarMenu />
</div>
<div className="flex items-center gap-2 md:hidden">
{isAuthenticated ? <NotificationsBell /> : null}
<ModeToggle />
<ProfileAvatarMenu />
</div>
</div>
</div>
</nav>
);
}