feat(dashboard): Next.js 16 merchant panel with offline POS and PWA

Complete merchant dashboard upgrade:

Next.js 16 compatibility:
- Fix params/searchParams typed as Promise<{}> throughout App Router
- Replace middleware.ts with proxy.ts (Next.js 16 convention)
- Remove unused @ts-expect-error directives caught by stricter TS
- Cast dynamic next-intl t() keys to fix TranslateArgs type errors

Offline POS:
- IndexedDB queue (meezi_pos_offline) for orders created while offline
- Zustand sync store tracking queueCount, isSyncing, isOnline
- useOfflineSync hook: auto-syncs on reconnect/visibility-change
- SyncStatusIndicator chip in topbar (amber=offline, blue=syncing)
- submitOrderToApi falls back to local order on network failure
- Local orders skip payment flow; sync on reconnect

PWA (installable):
- @ducanh2912/next-pwa with Workbox runtime caching rules
- Web App Manifest (manifest.ts) — RTL/Farsi, theme #0F6E56
- PWA icons: 192px, 512px, maskable 512px
- next.config.ts replaces next.config.mjs

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-05-27 21:34:12 +03:30
parent ef15fd6247
commit 131ecdbbe6
208 changed files with 37123 additions and 0 deletions
@@ -0,0 +1,116 @@
"use client";
import type { ReactNode } from "react";
import { useLocale } from "next-intl";
import { Toaster } from "sonner";
import {
AlertCircle,
CheckCircle2,
Info,
Loader2,
TriangleAlert,
} from "lucide-react";
import { cn } from "@/lib/utils";
function iconWrap(className: string, icon: ReactNode) {
return (
<span
className={cn(
"flex h-9 w-9 shrink-0 items-center justify-center rounded-full",
className
)}
>
{icon}
</span>
);
}
export function MeeziToaster() {
const locale = useLocale();
const isRtl = locale !== "en";
const isEn = locale === "en";
const fontClass = isEn
? "font-[family-name:var(--font-inter)]"
: "font-[family-name:var(--font-vazirmatn)]";
const toastBase = cn(
"group relative flex w-[min(calc(100vw-2rem),400px)] items-start gap-3 overflow-hidden",
"rounded-xl border border-border/60 bg-card/95 py-3.5 ps-3.5 pe-10",
"shadow-[0_10px_40px_-8px_rgba(15,23,42,0.16)] backdrop-blur-md",
"transition-[transform,opacity] duration-200",
fontClass
);
const titleClass = "text-[13px] font-semibold leading-snug tracking-tight text-foreground";
const descriptionClass = "text-xs leading-relaxed text-muted-foreground";
return (
<Toaster
dir={isRtl ? "rtl" : "ltr"}
position={isRtl ? "top-left" : "top-right"}
closeButton
richColors={false}
expand
gap={12}
offset={20}
visibleToasts={4}
className={fontClass}
toastOptions={{
unstyled: true,
style: {
fontFamily: isEn
? "var(--font-inter), system-ui, sans-serif"
: "var(--font-vazirmatn), system-ui, sans-serif",
},
classNames: {
toast: toastBase,
title: titleClass,
description: descriptionClass,
content: "flex flex-1 flex-col gap-0.5 min-w-0",
closeButton: cn(
"absolute end-2.5 top-2.5 flex h-7 w-7 items-center justify-center rounded-lg",
"border-0 bg-muted/50 text-muted-foreground opacity-80",
"transition hover:bg-muted hover:opacity-100",
fontClass
),
actionButton: cn(
"rounded-lg bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground",
fontClass
),
cancelButton: cn(
"rounded-lg border border-border/80 bg-background px-3 py-1.5 text-xs font-medium",
fontClass
),
success: "border-s-[3px] border-s-[#0F6E56]",
error: "border-s-[3px] border-s-[#A32D2D]",
warning: "border-s-[3px] border-s-[#BA7517]",
info: "border-s-[3px] border-s-[#0C447C]",
loading: "border-s-[3px] border-s-primary/40",
},
}}
icons={{
success: iconWrap(
"bg-[#E1F5EE]",
<CheckCircle2 className="h-4 w-4 text-[#0F6E56]" strokeWidth={2.25} />
),
error: iconWrap(
"bg-red-50",
<AlertCircle className="h-4 w-4 text-[#A32D2D]" strokeWidth={2.25} />
),
warning: iconWrap(
"bg-amber-50",
<TriangleAlert className="h-4 w-4 text-[#BA7517]" strokeWidth={2.25} />
),
info: iconWrap(
"bg-[#0C447C]/10",
<Info className="h-4 w-4 text-[#0C447C]" strokeWidth={2.25} />
),
loading: iconWrap(
"bg-primary/10",
<Loader2 className="h-4 w-4 animate-spin text-primary" strokeWidth={2.25} />
),
}}
/>
);
}