The (fullscreen) layout redirected to /login whenever user.accessToken was
falsy — but on a page refresh that fires before Zustand finishes rehydrating
the persisted auth from localStorage, so an authenticated user was bounced to
login on every refresh. Gate the redirect on _hasHydrated (and show a loader
while rehydrating), matching RouteGuard. Tokens themselves are already long
(30d access / 365d refresh), so sessions now survive refreshes as expected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Completes the four POS v2 roadmap items:
1. Real split payments — split tab records N separate payment rows (equal split,
last row takes the remainder), each row toggles Cash/Card; posts payments[].
2. Card-terminal push — confirmPay sums Card amounts and calls requestPosPayment
(POS device) before recording; surfaces POS_DEVICE_* errors.
3. Customer + coupons + loyalty — reuses PosCustomerPicker (attach/search/create)
and validates coupons via /coupons/validate (discount in totals). Pay sheet
offers loyalty redemption (1 point = 100 toman) when a customer is attached.
4. Promote to default — /pos now renders POS v2 (full-screen, café-themed); the
classic terminal moves to /pos-classic with its sidebar+topbar chrome. The
"نسخه کلاسیک" link points there.
Order submission already carried customerId/guestName/guestPhone/couponId via the
shared cart store, so customer + coupon flow straight through send + pay.
tsc --noEmit clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
POS v2 is now a real, working point of sale at /[locale]/pos2 (was a static
mock). It reuses the existing data layer so it shares the React Query cache and
offline pipeline with the classic POS:
- Table board ← fetchCafeTableBoard (Free/Busy/Reserved/Cleaning, live totals,
guest-QR badge); polls every 15s. Open a free table to start an order; open a
busy table to hydrate its existing order (GET order → cart hydrateFromOrder).
- Order screen ← real branch/café menu + categories, bound to useCartStore
(add/qty/remove). Send via submitOrderToApi (online + offline outbox) then
re-hydrate; "ارسال (n)" shows the pending (unsynced) line count.
- Pay sheet ← POST /orders/{id}/payments. Cash (numpad + change), Card, and a
Split helper (records the full amount; split is cashier guidance for now).
- Online/offline badge, loading/empty states, toasts, busy overlay, and a
"نسخه کلاسیک" link back to /pos.
The static design mock stays at /[locale]/pos2-preview (dev-only, 404 in prod).
tsc --noEmit clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Responsive RTL big-touch reimagining of the POS order screen for judging the
redesign on real devices before decomposing the 1568-line pos-screen.tsx + wiring
real logic. Self-contained: mock menu + local cart, no API/store/SignalR.
- 3 zones: category chips + item grid · order ticket · sticky action bar.
- lg+ side-panel ticket; smaller screens get a "view order" bar + slide-over
(covers landscape tablet, portrait tablet, phone).
- Big-touch (56px primary / 44px qty), brand green #0F6E56, Toman totals.
Send/Pay are mock toasts. tsc clean.
Guest orders from the QR/digital menu already notified via SignalR, but only
screens that were open (KDS/POS/tables) reacted — and silently (a data refresh,
no alert). So staff on any other screen never knew a menu order arrived.
- Add a global useOrderAlerts() mounted in the dashboard shell: connects to
/hubs/kds, joins the café group, and on a new GUEST order plays a chime + shows
a toast (localized fa/en/ar) + nudges order/KDS/POS lists to refresh — on every
screen.
- Filter to guest QR-menu orders only (not staff POS orders): LiveOrderDto now
carries Source, set in MapLiveOrder (+ the delivery/snappfood mappers).
86 API tests pass; dashboard tsc + build clean.
- dashboard layout: wait for Zustand _hasHydrated before redirecting to /login
(was redirecting on first render before localStorage was read)
- admin shell: same fix using new _hasHydrated on admin auth store
- admin-auth.store: add _hasHydrated + onRehydrateStorage to mirror merchant store
- AdminPlansScreen: replace direct cache mutation with per-plan PlanCard component
that owns its own useState — fixes other plans disappearing after save
- AdminSettingsScreen: detect boolean values and render iOS-style Toggle switches
- AdminIntegrationsScreen: replace all <input type=checkbox> with Toggle switches;
replace OpenAI model text input with <select> dropdown (gpt-4o-mini/4o/4-turbo/4/3.5)
- blog editor: fix form never syncing existing post data into state (editing was broken);
all fields now use local form state, save uses form directly
- blog links: fix broken relative hrefs (website/blog/new → /admin/website/blog/new)
and back button using proper Link components
- ci-cd: remove image prune step entirely — never removes containers or images
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sidebar:
- All groups start collapsed on first load (v4 storage key resets old state)
- Opening one group closes all others (accordion)
- Navigating to a section opens only that section's group
Koja slug:
- SlugHelper: Persian->Latin transliteration, slug validation
- Registration accepts optional custom slug; auto-derives from cafe name
- Slug can be updated from dashboard Settings -> Profile
- Settings PATCH validates uniqueness (SLUG_TAKEN) and format (INVALID_SLUG)
- koja.meezi.ir/{slug} now redirects to /fa/cafe/{slug} (short URL support)
Bug fix:
- SupportTicketService: cafeId/status filters applied before Select() projection
to fix EF "could not be translated" crash on the support tickets page
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Insert a factor/invoice page between plan selection and payment showing
billing-period choice, line items, and totals before redirecting to the
gateway, moving payment-method selection to where the charge happens.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wrap the POS terminal in the sidebar + topbar layout via a nested
fullscreen layout, and make the sidebar collapse to an icon-only rail
with a persisted toggle so operators keep navigation on the POS screen.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduce an OTP input box on login/register, surface user roles and a
cafe chooser, add a dashboard switch button in the POS screen, and
register OTP validators explicitly to survive Docker layer caching.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
POS terminal needs the entire screen — the dashboard navigation
sidebar (224px) was eating into the cashier's working space.
Moving /pos from (dashboard) to (fullscreen) gives the POS the
full viewport with no chrome. Auth redirect and CafeThemeProvider
are applied directly in the new page.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>