diff --git a/web/dashboard/messages/ar.json b/web/dashboard/messages/ar.json
index b346722..ea33950 100644
--- a/web/dashboard/messages/ar.json
+++ b/web/dashboard/messages/ar.json
@@ -58,6 +58,8 @@
},
"nav": {
"aria": "القائمة الرئيسية",
+ "collapseSidebar": "طي الشريط الجانبي",
+ "expandSidebar": "توسيع الشريط الجانبي",
"groups": {
"operations": "العمليات اليومية",
"menuSales": "القائمة والمبيعات",
@@ -945,7 +947,28 @@
"featureDiscover": "ملف الاكتشاف (ذكاء اصطناعي)",
"featureOn": "مفعّل",
"featureOff": "غير متاح — ترقية",
- "featureMenu3dUpgrade": "القائمة 3D متاحة في برو وما فوق."
+ "featureMenu3dUpgrade": "القائمة 3D متاحة في برو وما فوق.",
+ "checkout": {
+ "title": "الفاتورة والدفع",
+ "subtitle": "راجع طلبك وادفع",
+ "backToPlans": "العودة إلى الخطط",
+ "invalidPlan": "الخطة المحددة غير متاحة للشراء عبر الإنترنت.",
+ "invoiceLabel": "فاتورة مبدئية",
+ "invoiceNo": "رقم الفاتورة",
+ "issuedAt": "تاريخ الإصدار",
+ "billingPeriod": "مدة الاشتراك",
+ "monthsCount": "{count} شهر",
+ "description": "الوصف",
+ "qty": "الكمية",
+ "unitPrice": "سعر الوحدة",
+ "amount": "المبلغ",
+ "planLine": "اشتراك خطة {plan}",
+ "subtotal": "المجموع الفرعي",
+ "total": "المبلغ المستحق",
+ "secureNote": "تتم المعالجة عبر بوابة دفع بنكية آمنة.",
+ "payTotal": "ادفع {total}",
+ "redirecting": "جارٍ التحويل إلى البوابة..."
+ }
},
"settings": {
"title": "الإعدادات",
diff --git a/web/dashboard/messages/en.json b/web/dashboard/messages/en.json
index 4d3fa7c..539dd30 100644
--- a/web/dashboard/messages/en.json
+++ b/web/dashboard/messages/en.json
@@ -69,6 +69,8 @@
},
"nav": {
"aria": "Main navigation",
+ "collapseSidebar": "Collapse sidebar",
+ "expandSidebar": "Expand sidebar",
"groups": {
"operations": "Daily operations",
"menuSales": "Menu & sales",
@@ -1017,7 +1019,28 @@
"featureOff": "Not included — upgrade",
"featureMenu3dUpgrade": "3D menu is available on Pro and higher plans.",
"featureMenuAi3d": "AI 3D generation",
- "featureMenuAi3dUpgrade": "AI 3D generation is on Business and Enterprise (100 per month)."
+ "featureMenuAi3dUpgrade": "AI 3D generation is on Business and Enterprise (100 per month).",
+ "checkout": {
+ "title": "Invoice & Payment",
+ "subtitle": "Review your order and pay",
+ "backToPlans": "Back to plans",
+ "invalidPlan": "The selected plan is not available for online purchase.",
+ "invoiceLabel": "Proforma invoice",
+ "invoiceNo": "Invoice no.",
+ "issuedAt": "Issued",
+ "billingPeriod": "Billing period",
+ "monthsCount": "{count} mo",
+ "description": "Description",
+ "qty": "Qty",
+ "unitPrice": "Unit price",
+ "amount": "Amount",
+ "planLine": "{plan} plan subscription",
+ "subtotal": "Subtotal",
+ "total": "Amount due",
+ "secureNote": "Payment is processed through a secure bank gateway.",
+ "payTotal": "Pay {total}",
+ "redirecting": "Redirecting to gateway..."
+ }
},
"settings": {
"title": "Settings",
diff --git a/web/dashboard/messages/fa.json b/web/dashboard/messages/fa.json
index c4a4974..94b33c0 100644
--- a/web/dashboard/messages/fa.json
+++ b/web/dashboard/messages/fa.json
@@ -69,6 +69,8 @@
},
"nav": {
"aria": "منوی اصلی",
+ "collapseSidebar": "جمع کردن نوار کناری",
+ "expandSidebar": "باز کردن نوار کناری",
"groups": {
"operations": "عملیات روزانه",
"menuSales": "منو و فروش",
@@ -1018,7 +1020,28 @@
"featureOff": "غیرفعال — ارتقا دهید",
"featureMenu3dUpgrade": "منوی ۳D در پلن حرفهای و بالاتر فعال است.",
"featureMenuAi3d": "تولید ۳D با AI",
- "featureMenuAi3dUpgrade": "تولید ۳D با هوش مصنوعی در پلن کسبوکار و سازمانی (۱۰۰ بار در ماه) فعال است."
+ "featureMenuAi3dUpgrade": "تولید ۳D با هوش مصنوعی در پلن کسبوکار و سازمانی (۱۰۰ بار در ماه) فعال است.",
+ "checkout": {
+ "title": "پیشفاکتور و پرداخت",
+ "subtitle": "جزئیات سفارش را بررسی و پرداخت کنید",
+ "backToPlans": "بازگشت به پلنها",
+ "invalidPlan": "پلن انتخابشده برای خرید آنلاین معتبر نیست.",
+ "invoiceLabel": "پیشفاکتور",
+ "invoiceNo": "شماره فاکتور",
+ "issuedAt": "تاریخ صدور",
+ "billingPeriod": "مدت اشتراک",
+ "monthsCount": "{count} ماه",
+ "description": "شرح",
+ "qty": "تعداد",
+ "unitPrice": "قیمت واحد",
+ "amount": "مبلغ",
+ "planLine": "اشتراک پلن {plan}",
+ "subtotal": "جمع جزء",
+ "total": "مبلغ قابل پرداخت",
+ "secureNote": "پرداخت از طریق درگاه امن بانکی انجام میشود.",
+ "payTotal": "پرداخت {total}",
+ "redirecting": "در حال انتقال به درگاه..."
+ }
},
"settings": {
"title": "تنظیمات",
diff --git a/web/dashboard/src/app/[locale]/(fullscreen)/pos/layout.tsx b/web/dashboard/src/app/[locale]/(fullscreen)/pos/layout.tsx
new file mode 100644
index 0000000..2fd3f63
--- /dev/null
+++ b/web/dashboard/src/app/[locale]/(fullscreen)/pos/layout.tsx
@@ -0,0 +1,50 @@
+"use client";
+
+import { useLocale } from "next-intl";
+import { Sidebar } from "@/components/layout/sidebar";
+import { Topbar } from "@/components/layout/topbar";
+import { CafeThemeProvider } from "@/components/theme/cafe-theme-provider";
+
+/**
+ * POS route layout — wraps the terminal in the standard dashboard chrome
+ * (collapsible sidebar + topbar) but keeps the main content area
+ * overflow-hidden so PosScreen can manage its own internal scrolling.
+ */
+export default function PosLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ const locale = useLocale();
+ const isRtl = locale !== "en";
+
+ const mainColumn = (
+
+
+
+ {children}
+
+
+ );
+
+ return (
+
+
+ {isRtl ? (
+ <>
+
+ {mainColumn}
+ >
+ ) : (
+ <>
+
+ {mainColumn}
+ >
+ )}
+
+
+ );
+}
diff --git a/web/dashboard/src/app/[locale]/(fullscreen)/pos/page.tsx b/web/dashboard/src/app/[locale]/(fullscreen)/pos/page.tsx
index a0a76fc..c73a4a7 100644
--- a/web/dashboard/src/app/[locale]/(fullscreen)/pos/page.tsx
+++ b/web/dashboard/src/app/[locale]/(fullscreen)/pos/page.tsx
@@ -1,16 +1,11 @@
import { Suspense } from "react";
-import { CafeThemeProvider } from "@/components/theme/cafe-theme-provider";
import { PosScreen } from "@/components/pos/pos-screen";
-/** Full-viewport POS terminal — no sidebar, no topbar. */
+/** POS terminal — chrome (sidebar + topbar) is provided by layout.tsx */
export default function PosPage() {
return (
-
-
-
+
+
+
);
}
diff --git a/web/dashboard/src/components/layout/sidebar.tsx b/web/dashboard/src/components/layout/sidebar.tsx
index 69f5b02..de88055 100644
--- a/web/dashboard/src/components/layout/sidebar.tsx
+++ b/web/dashboard/src/components/layout/sidebar.tsx
@@ -1,7 +1,7 @@
"use client";
import { useCallback, useEffect, useMemo, useState } from "react";
-import { ChevronDown } from "lucide-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";
@@ -18,6 +18,17 @@ import { cn } from "@/lib/utils";
type OpenGroupsState = Partial>;
+const SIDEBAR_COLLAPSED_STORAGE_KEY = "meezi.sidebar.collapsed";
+
+function readStoredCollapsed(): boolean {
+ if (typeof window === "undefined") return false;
+ try {
+ return localStorage.getItem(SIDEBAR_COLLAPSED_STORAGE_KEY) === "1";
+ } catch {
+ return false;
+ }
+}
+
function readStoredOpenGroups(): OpenGroupsState {
if (typeof window === "undefined") return {};
try {
@@ -50,17 +61,22 @@ function NavLink({
item,
label,
active,
+ collapsed,
}: {
item: NavItemDef;
label: string;
active: boolean;
+ collapsed: boolean;
}) {
const Icon = item.icon;
return (
- {label}
+ {!collapsed && {label}}
);
}
@@ -86,6 +103,7 @@ function NavGroupSection({
role,
branchId,
tItem,
+ collapsed,
}: {
group: NavGroupDef;
title: string;
@@ -95,12 +113,35 @@ function NavGroupSection({
role: string | undefined;
branchId: string | null | undefined;
tItem: (key: string) => string;
+ collapsed: boolean;
}) {
const visibleItems = group.items.filter((item) =>
canSeeNavItem(item.key, role, branchId)
);
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) {
+ return (
+
+ {visibleItems.map((item) => {
+ const active =
+ pathname === item.href || pathname.startsWith(`${item.href}/`);
+ return (
+
+ );
+ })}
+
+ );
+ }
+
return (
);
})}
@@ -158,6 +200,30 @@ export function Sidebar({ side }: { side: "left" | "right" }) {
const branchId = user?.branchId ?? null;
const [openGroups, setOpenGroups] = useState
(buildDefaultOpenGroups);
+ const [collapsed, setCollapsed] = useState(readStoredCollapsed);
+
+ const toggleCollapsed = useCallback(() => {
+ setCollapsed((prev) => {
+ const next = !prev;
+ try {
+ localStorage.setItem(SIDEBAR_COLLAPSED_STORAGE_KEY, next ? "1" : "0");
+ } catch {
+ /* ignore quota */
+ }
+ return next;
+ });
+ }, []);
+
+ // Chevron points "inward" (toward the panel) to collapse, "outward" to expand.
+ // For a left-docked sidebar inward = left; for a right-docked one inward = right.
+ const CollapseIcon =
+ side === "left"
+ ? collapsed
+ ? ChevronRight
+ : ChevronLeft
+ : collapsed
+ ? ChevronLeft
+ : ChevronRight;
const visibleGroups = useMemo(
() =>
@@ -190,21 +256,30 @@ export function Sidebar({ side }: { side: "left" | "right" }) {
return (