diff --git a/.claude/launch.json b/.claude/launch.json index 17ad4ca..e1cb742 100644 --- a/.claude/launch.json +++ b/.claude/launch.json @@ -6,6 +6,20 @@ "runtimeExecutable": "dotnet", "runtimeArgs": ["run", "--project", "F:/Projects/DrSousan/DrSousan.Api", "--urls", "http://localhost:5000"], "port": 5000 + }, + { + "name": "meezi-website", + "runtimeExecutable": "node", + "runtimeArgs": ["node_modules/next/dist/bin/next", "dev", "-p", "3013"], + "cwd": "web/website", + "port": 3013 + }, + { + "name": "meezi-dashboard", + "runtimeExecutable": "node", + "runtimeArgs": ["node_modules/next/dist/bin/next", "dev", "-p", "3015"], + "cwd": "web/dashboard", + "port": 3015 } ] } diff --git a/web/dashboard/messages/ar.json b/web/dashboard/messages/ar.json index 946465a..38a2874 100644 --- a/web/dashboard/messages/ar.json +++ b/web/dashboard/messages/ar.json @@ -102,12 +102,10 @@ "collapseSidebar": "طي الشريط الجانبي", "expandSidebar": "توسيع الشريط الجانبي", "groups": { - "operations": "العمليات اليومية", - "menuSales": "القائمة والمبيعات", - "customers": "العملاء", - "finance": "التقارير والمالية", + "customers": "العملاء والتسويق", "management": "إدارة المقهى" }, + "home": "لوحة التحكم", "pos": "نقطة البيع", "tables": "الطاولات", "menu": "القائمة", diff --git a/web/dashboard/messages/en.json b/web/dashboard/messages/en.json index 9cc160b..6f91f93 100644 --- a/web/dashboard/messages/en.json +++ b/web/dashboard/messages/en.json @@ -113,12 +113,10 @@ "collapseSidebar": "Collapse sidebar", "expandSidebar": "Expand sidebar", "groups": { - "operations": "Daily operations", - "menuSales": "Menu & sales", - "customers": "Customers", - "finance": "Reports & finance", + "customers": "Customers & marketing", "management": "Café management" }, + "home": "Dashboard", "pos": "POS", "tables": "Tables", "crm": "CRM", diff --git a/web/dashboard/messages/fa.json b/web/dashboard/messages/fa.json index a4fb674..62801ee 100644 --- a/web/dashboard/messages/fa.json +++ b/web/dashboard/messages/fa.json @@ -113,12 +113,10 @@ "collapseSidebar": "جمع کردن نوار کناری", "expandSidebar": "باز کردن نوار کناری", "groups": { - "operations": "عملیات روزانه", - "menuSales": "منو و فروش", - "customers": "مشتریان", - "finance": "گزارش و مالی", + "customers": "مشتریان و بازاریابی", "management": "مدیریت کافه" }, + "home": "داشبورد", "pos": "صندوق", "tables": "میزها", "crm": "مشتریان", diff --git a/web/dashboard/src/app/[locale]/layout.tsx b/web/dashboard/src/app/[locale]/layout.tsx index 7a1ffca..cf63b45 100644 --- a/web/dashboard/src/app/[locale]/layout.tsx +++ b/web/dashboard/src/app/[locale]/layout.tsx @@ -24,6 +24,12 @@ export function generateStaticParams() { return routing.locales.map((locale) => ({ locale })); } +// Cap the prerendered-HTML cache lifetime. Without this Next emits +// `s-maxage=31536000` and the WCDN edge in front of app.meezi.ir keeps serving +// year-old HTML shells (pointing at deleted JS chunks) long after a deploy. +// 5 minutes keeps pages static+fast while letting deploys go live promptly. +export const revalidate = 300; + export default async function LocaleLayout({ children, params, diff --git a/web/dashboard/src/app/[locale]/page.tsx b/web/dashboard/src/app/[locale]/page.tsx deleted file mode 100644 index 1c74d44..0000000 --- a/web/dashboard/src/app/[locale]/page.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { redirect } from "@/i18n/routing"; - -export default async function HomePage({ - params, -}: { - params: Promise<{ locale: string }>; -}) { - const { locale } = await params; - redirect({ href: "/pos", locale }); -} diff --git a/web/dashboard/src/components/auth/route-guard.tsx b/web/dashboard/src/components/auth/route-guard.tsx index d8daa3f..9daa13e 100644 --- a/web/dashboard/src/components/auth/route-guard.tsx +++ b/web/dashboard/src/components/auth/route-guard.tsx @@ -4,7 +4,7 @@ import { useMemo } from "react"; import { ShieldX } from "lucide-react"; import { useTranslations } from "next-intl"; import { usePathname } from "@/i18n/routing"; -import { NAV_GROUPS, type NavItemKey } from "@/lib/sidebar-nav"; +import { ALL_NAV_ITEMS, type NavItemKey } from "@/lib/sidebar-nav"; import { NAV_REQUIRED_PERMISSION } from "@/lib/permissions"; import { canSeeNavItem } from "@/lib/auth-permissions"; import { permissionsOf } from "@/lib/permissions"; @@ -12,11 +12,13 @@ import { useAuthStore } from "@/lib/stores/auth.store"; /** Resolve the nav item key that owns the given pathname (locale already stripped). */ function navKeyForPath(pathname: string): NavItemKey | null { - for (const group of NAV_GROUPS) { - for (const item of group.items) { - if (pathname === item.href || pathname.startsWith(`${item.href}/`)) { - return item.key; - } + for (const item of ALL_NAV_ITEMS) { + if (item.href === "/") { + if (pathname === "/") return item.key; + continue; + } + if (pathname === item.href || pathname.startsWith(`${item.href}/`)) { + return item.key; } } return null; diff --git a/web/dashboard/src/components/layout/sidebar.tsx b/web/dashboard/src/components/layout/sidebar.tsx index 66dc88e..ea08798 100644 --- a/web/dashboard/src/components/layout/sidebar.tsx +++ b/web/dashboard/src/components/layout/sidebar.tsx @@ -4,9 +4,10 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import { ChevronDown, ChevronLeft, ChevronRight } from "lucide-react"; import { useTranslations } from "next-intl"; import { Link, usePathname } from "@/i18n/routing"; -import { canSeeNavGroup, canSeeNavItem } from "@/lib/auth-permissions"; +import { canSeeNavItem } from "@/lib/auth-permissions"; import { permissionsOf } from "@/lib/permissions"; import { + FOOTER_NAV, NAV_GROUPS, NAV_GROUPS_STORAGE_KEY, findNavGroupForPath, @@ -45,8 +46,8 @@ function buildDefaultOpenGroups(): OpenGroupsState { const stored = readStoredOpenGroups(); const defaults: OpenGroupsState = {}; for (const g of NAV_GROUPS) { - // Default ALL groups closed on first visit; only restore if user explicitly saved state. - defaults[g.id] = stored[g.id] ?? false; + if (g.flat) continue; // flat groups are always expanded — no state + defaults[g.id] = stored[g.id] ?? g.defaultOpen; } return defaults; } @@ -59,6 +60,11 @@ function persistOpenGroups(next: OpenGroupsState): void { } } +function isItemActive(item: NavItemDef, pathname: string): boolean { + if (item.href === "/") return pathname === "/"; + return pathname === item.href || pathname.startsWith(`${item.href}/`); +} + function NavLink({ item, label, @@ -124,24 +130,27 @@ function NavGroupSection({ ); if (visibleItems.length === 0) return null; - // Collapsed: drop the group header, show items as a flat icon-only list - // with a subtle divider between groups. - if (collapsed) { + // Flat groups (daily-use primary nav) and the collapsed-rail mode both render + // as a plain list — no header, always expanded. + if (group.flat || collapsed) { return ( -