Surfaces three events without a full notification center: - service worker (public/sw.js) gains push + notificationclick handlers (RTL, focuses an existing tab or opens the target URL). - usePushNotifications hook: permission + VAPID subscribe/unsubscribe against the backend push endpoints. - opt-in UI both ways: a one-time dismissible post-login banner (PushPermissionBanner, rendered in MyLayout) and a permanent toggle (PushSettingToggle) on buyer profile settings and seller store settings. - useBadgeCounts polls /api/notif/v1/badges/; small red badges on the bottom-nav profile icon (chat + order updates), the profile sidebar (پیامها / سفارشها) and the seller dashboard sidebar. Visiting orders clears its badge. Degrades gracefully: if the backend endpoints are absent the counts fall back to zero and nothing throws. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
104 lines
3.6 KiB
TypeScript
104 lines
3.6 KiB
TypeScript
import { Link, Outlet, useLocation, useParams } from "@remix-run/react";
|
||
import {
|
||
LayoutGrid,
|
||
Package,
|
||
ClipboardList,
|
||
Wallet,
|
||
Truck,
|
||
MessageCircle,
|
||
Settings,
|
||
ArrowRight,
|
||
} from "lucide-react";
|
||
import { useSellerData } from "~/requestHandler/use-seller-hooks";
|
||
import SellerLogo from "~/components/SellerLogo";
|
||
import { useBadgeCounts } from "~/hooks/useBadgeCounts";
|
||
|
||
/**
|
||
* Desktop layout for the seller dashboard. Renders a sticky admin sidebar
|
||
* next to the page content at lg+; on mobile it's just the page content
|
||
* (the existing mobile dashboard, with its store-mode bottom nav, is unchanged).
|
||
*/
|
||
export default function StoreLayout() {
|
||
const { storeId } = useParams();
|
||
const location = useLocation();
|
||
const { data } = useSellerData(storeId);
|
||
const { data: badges } = useBadgeCounts();
|
||
const base = `/store/${storeId}`;
|
||
|
||
const NAV = [
|
||
{ label: "فروشگاه", to: base, icon: LayoutGrid, exact: true },
|
||
{ label: "محصولات", to: `${base}/products`, icon: Package },
|
||
{
|
||
label: "سفارشها",
|
||
to: `${base}/orders`,
|
||
icon: ClipboardList,
|
||
badge: badges?.seller_orders || 0,
|
||
},
|
||
{ label: "مالی", to: `${base}/financial-dashboard`, icon: Wallet },
|
||
{ label: "روش ارسال", to: `${base}/shipping-method`, icon: Truck },
|
||
{
|
||
label: "پیامها",
|
||
to: `${base}/threads`,
|
||
icon: MessageCircle,
|
||
badge: badges?.chat || 0,
|
||
},
|
||
{ label: "تنظیمات", to: `${base}/setting`, icon: Settings },
|
||
];
|
||
|
||
const isActive = (item: { to: string; exact?: boolean }) =>
|
||
item.exact
|
||
? location.pathname === item.to
|
||
: location.pathname.startsWith(item.to);
|
||
|
||
return (
|
||
<div className="lg:max-w-[1320px] lg:mx-auto lg:w-full lg:px-8 lg:py-8 lg:grid lg:grid-cols-[260px_1fr] lg:gap-7 lg:items-start">
|
||
{/* Desktop admin sidebar */}
|
||
<aside className="hidden lg:block lg:sticky lg:top-6 border border-WHITE3 rounded-2xl overflow-hidden">
|
||
<div className="p-4 border-b border-WHITE3 flex items-center gap-3">
|
||
<SellerLogo size="sm" src={data?.storeLogo} alt="store" />
|
||
<div className="min-w-0">
|
||
<p className="font-bold text-[15px] truncate">
|
||
{data?.storeName || "فروشگاه من"}
|
||
</p>
|
||
<p className="text-GRAY text-[12px]">پنل مدیریت</p>
|
||
</div>
|
||
</div>
|
||
<nav className="p-2.5 flex flex-col gap-0.5">
|
||
{NAV.map((n) => (
|
||
<Link
|
||
key={n.to}
|
||
to={n.to}
|
||
prefetch="intent"
|
||
className={`flex items-center gap-3 px-3.5 py-3 rounded-xl text-[14.5px] font-semibold transition-colors ${
|
||
isActive(n)
|
||
? "bg-BLACK text-white"
|
||
: "text-BLACK2 hover:bg-WHITE2"
|
||
}`}
|
||
>
|
||
<n.icon size={20} />
|
||
{n.label}
|
||
{n.badge && n.badge > 0 ? (
|
||
<span className="ml-auto min-w-[18px] h-[18px] px-1 rounded-full bg-RED text-white text-[10px] font-bold flex items-center justify-center leading-none">
|
||
{n.badge > 99 ? "99+" : n.badge}
|
||
</span>
|
||
) : null}
|
||
</Link>
|
||
))}
|
||
<Link
|
||
to="/"
|
||
className="flex items-center gap-3 px-3.5 py-3 rounded-xl text-[14.5px] font-semibold text-BLACK2 hover:bg-WHITE2 transition-colors"
|
||
>
|
||
<ArrowRight size={20} />
|
||
بازگشت به فروشگاه
|
||
</Link>
|
||
</nav>
|
||
</aside>
|
||
|
||
{/* Page content */}
|
||
<div className="min-w-0">
|
||
<Outlet />
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|