Files
meezi/web/dashboard/src/components/layout/topbar.tsx
T
soroush.asadi 345ae0a4b5
CI/CD / CI · Admin API (dotnet build) (push) Successful in 41s
CI/CD / CI · Admin Web (tsc) (push) Failing after 5s
CI/CD / CI · Website (tsc) (push) Failing after 4s
CI/CD / CI · Koja (tsc) (push) Failing after 5s
CI/CD / CI · API (dotnet build + test) (push) Successful in 1m13s
CI/CD / CI · Dashboard (tsc) (push) Failing after 2m32s
CI/CD / Deploy · all services (push) Has been skipped
first commit
2026-05-31 11:06:24 +03:30

108 lines
3.9 KiB
TypeScript

"use client";
import { useLocale, useTranslations } from "next-intl";
import { Link, useRouter, usePathname } from "@/i18n/routing";
import { Pencil, LogOut } from "lucide-react";
import { useAuthStore } from "@/lib/stores/auth.store";
import { useCafeSettings } from "@/lib/hooks/use-cafe-settings";
import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { HeaderCenterCluster } from "@/components/layout/header-center-cluster";
import { BranchSwitcher } from "@/components/layout/branch-switcher";
import { NotificationCenter } from "@/components/notifications/notification-center";
import { SyncStatusIndicator } from "@/components/layout/sync-status-indicator";
const locales = ["fa", "ar", "en"] as const;
export function Topbar() {
const t = useTranslations();
const locale = useLocale();
const router = useRouter();
const pathname = usePathname();
const clearAuth = useAuthStore((s) => s.clearAuth);
const cafeId = useAuthStore((s) => s.user?.cafeId);
const { data: cafeSettings, isLoading, isPending } = useCafeSettings(cafeId);
const cafeDisplayName = cafeSettings?.name ?? t("dashboard.cafeName");
const showNameSkeleton = (isLoading || isPending) && !cafeSettings;
const switchLocale = (next: string) => {
router.replace(pathname, { locale: next });
};
return (
<header className="relative flex h-14 items-center gap-3 border-b border-border bg-background px-4 sm:px-6">
{/* Cafe name */}
<div className="flex min-w-0 flex-1 items-center gap-2">
{showNameSkeleton ? (
<Skeleton className="h-5 w-32 max-w-full" />
) : (
<Link
href="/settings"
className="group inline-flex min-w-0 max-w-full items-center gap-1.5 rounded-lg px-2 py-1 transition-colors hover:bg-accent cursor-pointer"
title={t("dashboard.editCafeSettings")}
>
<h1 className="truncate text-sm font-semibold text-foreground sm:text-base">
{cafeDisplayName}
</h1>
<Pencil
className="h-3 w-3 shrink-0 text-muted-foreground/50 transition-colors group-hover:text-primary"
aria-hidden
/>
<span className="sr-only">{t("dashboard.editCafeSettings")}</span>
</Link>
)}
</div>
<HeaderCenterCluster />
{/* Actions */}
<div className="flex flex-1 items-center justify-end gap-1.5">
<BranchSwitcher />
<SyncStatusIndicator />
<NotificationCenter />
{/* Language switcher */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="sm" className="h-8 gap-1 px-2.5 text-xs cursor-pointer">
{t(`languages.${locale}`)}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="min-w-[120px]">
{locales.map((code) => (
<DropdownMenuItem
key={code}
onClick={() => switchLocale(code)}
className={locale === code ? "font-semibold text-primary cursor-pointer" : "cursor-pointer"}
>
{t(`languages.${code}`)}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
{/* Logout */}
<Button
variant="ghost"
size="sm"
onClick={() => {
clearAuth();
router.push("/login");
}}
className="h-8 w-8 p-0 text-muted-foreground hover:text-destructive cursor-pointer"
title={t("common.logout")}
>
<LogOut className="h-3.5 w-3.5" aria-hidden />
<span className="sr-only">{t("common.logout")}</span>
</Button>
</div>
</header>
);
}