Initial import of AbelBirdNest Stock

This commit is contained in:
2026-05-16 18:25:51 +07:00
commit 14bb9bf744
472 changed files with 70671 additions and 0 deletions

View File

@ -0,0 +1,149 @@
"use client";
import Link from "next/link";
import { ChevronDown, CircleHelp, KeyRound, LogOut } from "lucide-react";
import { useEffect, useMemo, useState } from "react";
import { LogoutButton } from "@/components/auth/logout-button";
import { useLocale } from "@/components/providers/locale-provider";
import { getNavigationForRole, isNavGroup } from "@/config/navigation";
import type { AppRole } from "@/config/access-control";
import { cn } from "@/lib/utils";
type MobileNavProps = {
pathname: string;
userRole: string;
};
function matchesPath(currentPath: string, href: string) {
return currentPath === href || currentPath.startsWith(`${href}/`);
}
export function MobileNav({ pathname, userRole }: MobileNavProps) {
const { dict } = useLocale();
const navigation = useMemo(() => getNavigationForRole(userRole as AppRole), [userRole]);
const buildOpenGroups = () => {
const grouped = navigation.filter(isNavGroup);
return grouped.reduce<Record<string, boolean>>((acc, item) => {
const isGroupActive = item.children.some(
(child) => matchesPath(pathname, child.href)
);
acc[item.key] = isGroupActive;
return acc;
}, {});
};
const [openGroups, setOpenGroups] = useState<Record<string, boolean>>(buildOpenGroups);
useEffect(() => {
setOpenGroups((current) => {
const next = { ...current };
navigation.filter(isNavGroup).forEach((item) => {
const isGroupActive = item.children.some(
(child) => matchesPath(pathname, child.href)
);
if (isGroupActive) {
next[item.key] = true;
}
});
return next;
});
}, [pathname, navigation]);
return (
<nav className="border-b border-line/70 bg-white px-4 py-3 xl:hidden">
<div className="space-y-2 pb-1">
{navigation.map((item) => {
if (isNavGroup(item)) {
const isGroupActive = item.children.some(
(child) => matchesPath(pathname, child.href)
);
const isOpen = openGroups[item.key] ?? isGroupActive;
return (
<div key={item.key} className="rounded border border-line/70 bg-slate-50">
<button
type="button"
onClick={() =>
setOpenGroups((current) => ({
...current,
[item.key]: !isOpen
}))
}
className="flex w-full items-center gap-2 px-4 py-2 text-left text-[13px] font-medium text-slate-700"
>
<span>
{dict.navigationGroups?.[
item.key as keyof typeof dict.navigationGroups
] ?? item.label}
</span>
<ChevronDown
className={cn("ml-auto h-4 w-4 transition-transform", isOpen ? "rotate-180" : "")}
/>
</button>
{isOpen ? (
<div className="grid gap-2 border-t border-line/70 bg-white p-2">
{item.children.map((child) => {
const isActive =
matchesPath(pathname, child.href);
return (
<Link
key={child.href}
href={child.href}
className={cn(
"rounded px-3 py-2 text-[13px] font-medium transition",
isActive
? "bg-moss/10 text-moss"
: "bg-slate-50 text-slate-500"
)}
>
{dict.navigation[child.href as keyof typeof dict.navigation] ?? child.label}
</Link>
);
})}
</div>
) : null}
</div>
);
}
const isActive =
matchesPath(pathname, item.href);
return (
<Link
key={item.href}
href={item.href}
className={cn(
"block rounded px-4 py-2 text-[13px] font-medium transition",
isActive
? "border-line/70 bg-white text-moss shadow-panel"
: "border-line/70 bg-slate-50 text-slate-500"
)}
>
{dict.navigation[item.href as keyof typeof dict.navigation] ?? item.label}
</Link>
);
})}
<Link
href="/change-password"
className="inline-flex items-center gap-2 rounded bg-slate-50 px-4 py-2 text-[13px] font-medium text-slate-500"
>
<KeyRound className="h-4 w-4" />
{dict.common.changePassword}
</Link>
<Link
href="/help"
className="inline-flex items-center gap-2 rounded bg-slate-50 px-4 py-2 text-[13px] font-medium text-slate-500"
>
<CircleHelp className="h-4 w-4" />
{dict.common.help}
</Link>
<LogoutButton
className="inline-flex whitespace-nowrap items-center gap-2 rounded border-line/70 bg-red-50 px-4 py-2 text-[13px] font-medium text-red-600"
label={dict.common.logout}
icon={<LogOut className="h-4 w-4" />}
/>
</div>
</nav>
);
}