216 lines
8.3 KiB
TypeScript
216 lines
8.3 KiB
TypeScript
"use client";
|
||
|
||
import { useEffect, useState } from "react";
|
||
import Link from "next/link";
|
||
import { useAuth } from "@/hooks/useAuth";
|
||
import { useHeaderSession } from "@/hooks/useHeaderSession";
|
||
import { useTenantHost } from "@/hooks/useTenantHost";
|
||
import { useTheme } from "@/hooks/useTheme";
|
||
import { Button, Container } from "@/components/ui";
|
||
import { useColorMode } from "@/components/providers/ColorModeProvider";
|
||
import { loadCommercialNotifications } from "@/modules/commercial/adapters";
|
||
import type { CommercialNotification } from "@/modules/commercial/types";
|
||
|
||
export function SiteHeader() {
|
||
const { theme } = useTheme();
|
||
const host = useTenantHost();
|
||
const { login, loading, user } = useAuth();
|
||
const { isSsoAuth, isLoggedIn } = useHeaderSession();
|
||
const { mode, toggle } = useColorMode();
|
||
const isPlatformAdmin =
|
||
user?.roles?.includes("platform_admin") || user?.roles?.includes("tenant_admin");
|
||
|
||
const brandName = host.isTenantHost
|
||
? theme?.site_name ?? "Workspace"
|
||
: theme?.site_name ?? "Torbatyar";
|
||
const brandSub = host.isTenantHost ? "workspace روی تربتیار" : "Commercial SaaS Platform";
|
||
|
||
const [notifications, setNotifications] = useState<CommercialNotification[]>([]);
|
||
const [notifOpen, setNotifOpen] = useState(false);
|
||
const [notifMsg, setNotifMsg] = useState<string | null>(null);
|
||
const [search, setSearch] = useState("");
|
||
|
||
useEffect(() => {
|
||
if (!isLoggedIn || !isSsoAuth) return;
|
||
void loadCommercialNotifications().then((r) => {
|
||
if (r.availability === "ready") {
|
||
setNotifications(r.data);
|
||
setNotifMsg(null);
|
||
} else {
|
||
setNotifications([]);
|
||
setNotifMsg(r.message || null);
|
||
}
|
||
});
|
||
}, [isLoggedIn, isSsoAuth]);
|
||
|
||
const onSearch = (e: React.FormEvent) => {
|
||
e.preventDefault();
|
||
const q = search.trim();
|
||
if (!q) {
|
||
window.location.href = "/search";
|
||
return;
|
||
}
|
||
window.location.href = `/search?q=${encodeURIComponent(q)}`;
|
||
};
|
||
|
||
return (
|
||
<header className="sticky top-0 z-50 border-b border-gray-100/80 bg-white/80 backdrop-blur-md dark:border-gray-800 dark:bg-gray-950/80">
|
||
<Container className="flex h-16 items-center justify-between gap-3">
|
||
<Link href="/" className="flex shrink-0 items-center gap-3">
|
||
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-primary text-lg font-bold text-white shadow-md">
|
||
{brandName.charAt(0)}
|
||
</div>
|
||
<div className="hidden sm:block">
|
||
<p className="text-base font-bold text-secondary dark:text-gray-100">{brandName}</p>
|
||
<p className="text-xs text-gray-500">{brandSub}</p>
|
||
</div>
|
||
</Link>
|
||
|
||
{isLoggedIn && isSsoAuth ? (
|
||
<form onSubmit={onSearch} className="hidden min-w-0 flex-1 md:block md:max-w-xs lg:max-w-sm">
|
||
<input
|
||
value={search}
|
||
onChange={(e) => setSearch(e.target.value)}
|
||
placeholder="جستجوی اپها…"
|
||
className="w-full rounded-xl border border-gray-200 bg-white px-3 py-2 text-sm dark:border-gray-700 dark:bg-gray-900"
|
||
aria-label="جستجو"
|
||
/>
|
||
</form>
|
||
) : null}
|
||
|
||
<nav className="flex flex-wrap items-center justify-end gap-1 sm:gap-2">
|
||
{!host.isTenantHost && !isLoggedIn ? (
|
||
<>
|
||
<Link href="/discover">
|
||
<Button variant="ghost" size="sm">
|
||
کشف
|
||
</Button>
|
||
</Link>
|
||
<Link href="/pricing">
|
||
<Button variant="ghost" size="sm">
|
||
قیمت
|
||
</Button>
|
||
</Link>
|
||
<Link href="/help">
|
||
<Button variant="ghost" size="sm" className="hidden md:inline-flex">
|
||
راهنما
|
||
</Button>
|
||
</Link>
|
||
</>
|
||
) : null}
|
||
{!host.isTenantHost && isLoggedIn ? (
|
||
<Link href="/apps">
|
||
<Button variant="ghost" size="sm">
|
||
Products
|
||
</Button>
|
||
</Link>
|
||
) : null}
|
||
{isLoggedIn && isSsoAuth ? (
|
||
<>
|
||
<Link href="/dashboard">
|
||
<Button variant="ghost" size="sm">
|
||
Workspace
|
||
</Button>
|
||
</Link>
|
||
<Link href="/apps">
|
||
<Button variant="ghost" size="sm" className="hidden sm:inline-flex">
|
||
Apps
|
||
</Button>
|
||
</Link>
|
||
<Link href="/billing">
|
||
<Button variant="ghost" size="sm">
|
||
Billing
|
||
</Button>
|
||
</Link>
|
||
<Link href="/domains">
|
||
<Button variant="ghost" size="sm" className="hidden md:inline-flex">
|
||
دامنه
|
||
</Button>
|
||
</Link>
|
||
<div className="relative hidden sm:block">
|
||
<Button
|
||
variant="ghost"
|
||
size="sm"
|
||
onClick={() => setNotifOpen((v) => !v)}
|
||
aria-expanded={notifOpen}
|
||
aria-haspopup="true"
|
||
>
|
||
اعلان
|
||
{notifications.length ? (
|
||
<span className="ms-1 text-[10px] text-primary">{notifications.length}</span>
|
||
) : null}
|
||
</Button>
|
||
{notifOpen ? (
|
||
<div className="absolute end-0 z-50 mt-2 w-72 rounded-xl border border-gray-100 bg-white p-3 shadow-lg dark:border-gray-800 dark:bg-gray-950">
|
||
{notifications.length ? (
|
||
<ul className="max-h-64 space-y-2 overflow-auto text-sm">
|
||
{notifications.slice(0, 5).map((n) => (
|
||
<li key={n.id} className="border-b border-gray-50 pb-2 last:border-0 dark:border-gray-800">
|
||
<p className="font-medium text-secondary dark:text-gray-100">{n.title}</p>
|
||
{n.body ? (
|
||
<p className="mt-0.5 text-xs text-gray-500">{n.body}</p>
|
||
) : null}
|
||
</li>
|
||
))}
|
||
</ul>
|
||
) : (
|
||
<p className="text-xs text-gray-500">
|
||
{notifMsg || "اعلانی از رجیستری نیست"}
|
||
</p>
|
||
)}
|
||
<Link
|
||
href="/notifications"
|
||
className="mt-2 block text-center text-xs font-semibold text-primary"
|
||
onClick={() => setNotifOpen(false)}
|
||
>
|
||
همه اعلانها
|
||
</Link>
|
||
</div>
|
||
) : null}
|
||
</div>
|
||
{!host.isTenantHost && isPlatformAdmin ? (
|
||
<Link href="/admin">
|
||
<Button variant="primary" size="sm">
|
||
Admin
|
||
</Button>
|
||
</Link>
|
||
) : null}
|
||
<Link
|
||
href={
|
||
isPlatformAdmin && !host.isTenantHost
|
||
? "/admin/settings"
|
||
: "/dashboard/settings"
|
||
}
|
||
>
|
||
<Button variant="outline" size="sm">
|
||
Account
|
||
</Button>
|
||
</Link>
|
||
</>
|
||
) : (
|
||
<>
|
||
<Link href="/register?redirect=/discover">
|
||
<Button variant="outline" size="sm" className="hidden sm:inline-flex">
|
||
ثبتنام
|
||
</Button>
|
||
</Link>
|
||
<Button
|
||
variant="primary"
|
||
size="sm"
|
||
onClick={() => login()}
|
||
loading={loading}
|
||
loadingText="..."
|
||
>
|
||
ورود
|
||
</Button>
|
||
</>
|
||
)}
|
||
<Button variant="ghost" size="sm" onClick={toggle} aria-label="تغییر تم">
|
||
{mode === "dark" ? "☀" : "☾"}
|
||
</Button>
|
||
</nav>
|
||
</Container>
|
||
</header>
|
||
);
|
||
}
|