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:
@@ -0,0 +1,52 @@
|
||||
import { Coffee, CupSoda, UtensilsCrossed, type LucideIcon } from "lucide-react";
|
||||
import { resolveMediaUrl } from "@/lib/api/client";
|
||||
|
||||
export type MenuItemVisualKind = "food" | "drink";
|
||||
|
||||
const DRINK_CATEGORY_IDS = new Set(["cat_demo_drinks", "cat_demo_cold"]);
|
||||
|
||||
/** Latin keywords; Persian/Arabic category names come from API `categoryName`. */
|
||||
const DRINK_HINTS = [
|
||||
"drink",
|
||||
"cold",
|
||||
"hot",
|
||||
"coffee",
|
||||
"tea",
|
||||
"juice",
|
||||
"smoothie",
|
||||
"beverage",
|
||||
"bar",
|
||||
"espresso",
|
||||
"latte",
|
||||
];
|
||||
|
||||
export function inferMenuItemKind(
|
||||
categoryId: string,
|
||||
categoryName?: string
|
||||
): MenuItemVisualKind {
|
||||
if (DRINK_CATEGORY_IDS.has(categoryId)) return "drink";
|
||||
|
||||
const haystack = `${categoryId} ${categoryName ?? ""}`.toLowerCase();
|
||||
if (DRINK_HINTS.some((h) => haystack.includes(h))) return "drink";
|
||||
|
||||
return "food";
|
||||
}
|
||||
|
||||
export function getMenuItemImageSrc(imageUrl?: string | null): string | undefined {
|
||||
return resolveMediaUrl(imageUrl);
|
||||
}
|
||||
|
||||
export function menuItemPlaceholderIcon(kind: MenuItemVisualKind): LucideIcon {
|
||||
return kind === "drink" ? CupSoda : UtensilsCrossed;
|
||||
}
|
||||
|
||||
/** Larger hero-style icon for sidebar preview */
|
||||
export function menuItemPlaceholderHeroIcon(kind: MenuItemVisualKind): LucideIcon {
|
||||
return kind === "drink" ? Coffee : UtensilsCrossed;
|
||||
}
|
||||
|
||||
export function buildCategoryNameMap(
|
||||
categories: { id: string; name: string }[]
|
||||
): Map<string, string> {
|
||||
return new Map(categories.map((c) => [c.id, c.name]));
|
||||
}
|
||||
Reference in New Issue
Block a user