Delete (every manageable entity that only had "add" now has delete):
- Ingredients (warehouse): new DELETE /inventory/ingredients/{id} (soft-delete via
the global DeletedAt filter — no FK trouble with recipes/movements) + NoOp stub +
trash button in the materials cards.
- Reservations: new DELETE /reservations/{id} (soft-delete) + per-card delete button.
- Coupons & Customers: backend DELETE already existed; wired delete buttons in the UI.
- Shared ConfirmDialog component used by all delete flows (RTL-aware).
- Audit result: tables/branches/taxes/kitchen-stations/expenses/menu/terminals already
had delete; HR has no "add" so no delete needed; shifts intentionally excluded
(financial open/close records, not add-style entities).
Koja visibility:
- New Cafe.ShowOnKoja flag, default TRUE (DB default true so existing cafés stay
listed). Discover query now filters IsVerified && !Deleted && ShowOnKoja.
- public-profile GET/PUT expose showOnKoja; dashboard public-profile panel has an
on-by-default toggle that persists immediately. Platform IsVerified gate unchanged.
- EF migration AddCafeShowOnKoja (defaultValue: true).
Also: added the missing errors.generic i18n key (fa/en/ar) so useApiError's fallback
resolves instead of rendering the literal "errors.generic". 81 API tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The branch-menu-overrides availability switch (dashboard) and the BlogToggle
(admin website editor) still moved their knob with translate-x while inheriting
RTL, so the knob escaped the track on the right. Pin both to dir="ltr" like the
other switches. All four role="switch" toggles in the codebase now share the fix.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add DELETE /api/cafes/{cafeId}/menu/items/{id} (DeleteItemAsync soft-delete,
mirroring the existing category delete) — item delete had no backend route.
- Dashboard menu admin: destructive "delete" action in the item and category
edit modals, behind a shared confirm dialog (AlertDialog). Deleting the
selected category falls back to "all items".
- Fix the availability ToggleSwitch in RTL: force dir="ltr" so the knob's
translate-x stays inside the track instead of escaping on the right
(same fix as the admin-panel toggles).
- i18n: deleteItem/deleteCategory confirm + success strings (fa/en/ar).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lets the POS agent and the QR/app customer attach a free-text note to each
order line (e.g. "no tomato", "extra hot") that reaches the kitchen/bar.
- Backend already supported it (OrderItem.Notes persists; CreateOrderItemRequest
and OrderItemDto carry Notes; LiveOrderDto items include it) — this wires the UI.
- cart.store: add setNotes(menuItemId, notes); notes already travel in
getPendingLines and round-trip via hydrateFromOrder.
- POS pos-screen: a note input under each cart line.
- QR guest menu: a note input under each cart line (QrCartLine.note).
- KDS: render the note prominently under each item so kitchen/bar sees it.
- i18n: pos.itemNotePlaceholder + qrMenu.itemNote (fa/ar/en).
Note: notes are captured on items being added; editing a note on an
already-submitted line is out of scope (no pending delta to re-send).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The redirect source "/:slug([a-z0-9][a-z0-9-]*[a-z0-9])" matched single-segment
paths including the locale itself, so /fa redirected to /fa/cafe/fa (slug "fa")
and /en to /fa/cafe/en — non-existent cafés that returned Internal Server Error.
Visiting koja.meezi.ir (-> /fa) hit this. Removed the redirect so the home page
renders; short café URLs can be re-added via middleware with reserved-word guards.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The integrations form rendered from (gateways state, falling back to fetched data) but SAVED from the state and edited via updateGateway on . If gateways hadn't hydrated, edits (e.g. Zarinpal merchantId) were written to an empty array and the save sent nothing. Now updateGateway seeds from fetched data on first edit, and the save maps over — render, edit, and save share one source. NOTE: prod admin had also been stale because recent deploys aborted on the main-API crash before the admin containers restarted.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Location card gets a 'موقعیت فعلی من' button that fills lat/lng from the browser's geolocation. Support ticket list now shows the resolved (localized) error instead of a generic message, so a failure is diagnosable.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Koja auto-detected locale from the browser Accept-Language (en for many Persian users); set localeDetection:false so locale-less URLs default to fa. Also guarded cafe.discoverProfile across the cafe page, cafe card, and JSON-LD — a café without a discover profile crashed the page (500). The cafe page now resolves the café first and notFound()s an unknown slug before fetching menu/reviews.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Plan + feature seeding was dev-gated and all-or-nothing, so production only had the Free plan (admin Plans page showed one). Now runs in every environment and upserts missing rows (adds Pro/Business/Enterprise on top of the existing Free). Also force LTR on the admin toggle switch so the knob doesn't render off-track under the RTL page.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Error toasts surfaced the raw English backend message. Added an errors namespace (fa/ar/en) keyed by error code + a useApiError() resolver that maps ApiClientError.code to the localized message (fallback to a localized generic). Wired into menu, tables, demo banner, and subscription checkout; hardened getErrorMessage so it never returns the raw backend message.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The dashboard demo-data banner is shown to Owner and Manager, but the /demo/seed endpoint required strictly Owner, so a Manager clicking it got a silent 403 (the banner had no error handler) — appearing as 'nothing happens, no tables or items'. The endpoint now allows Owner or Manager, and the banner shows the error on failure.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Marketing-site login/register/dashboard links were locale-less (app.meezi.ir/login), so the dashboard auto-detected locale from the browser Accept-Language (en-US) and redirected Persian users to /en. Links now carry the current locale, and the dashboard sets localeDetection:false so any locale-less entry defaults to fa (Iran-first) instead of guessing from the browser.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replaced the rough 40-point hand-drawn polygon with the real national border (74 vertices, Natural Earth via world.geo.json) and fitted the projection bounding box to Iran's true extent, so the silhouette is recognisable and café markers stay aligned. Reworked the marker animation from a radar-style expanding ring into a slow 3.6s ease-in-out lamp fade (opacity 1->0.2->1) with a halo that glows on and off in sync. Verified via the SVG timeline: opacity 1.0 at 0s, 0.2 at 1.8s, 1.0 at 3.6s.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Dashboard & API bug fixes for owner-reported breakage:
- MenuController validators (PosValidators): NameEn was required but the
dashboard sends null when blank, so every manual menu-item create failed
and category create failed 100% (the form never sends nameEn). Now optional.
- DemoDataBanner: only showed when a cafe was exactly empty, so
showcase-seeded cafes (2-3 cats / 3-5 items) could never trigger the
one-click seed. Widened gate to sparse menus (<5 cats && <10 items) and
added a clear "nothing to add" message when already populated.
- client.ts: added one-time JWT refresh-and-retry on 401 (shared in-flight
promise) before bouncing to /login. Expired access tokens silently broke
ticket list, add-table, and other reads.
- Surface API errors as toasts on menu + table mutations (were swallowed
silently, so failures looked like "nothing happens").
- Admin blog editor: saving an edit dropped IsPublished (defaulted false,
silently unpublishing the post on every save); now persisted with a
toggle. Also hoisted the inner Field component to module scope - it was
remounting every input on each keystroke and dropping focus.
- Admin integrations: replaced raw radio gateway selector with a styled
RadioDot matching the iOS toggles.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Without --alias, meezi-db joins meezi_default but is only reachable
as "meezi-db". The API uses Host=postgres — DNS lookup fails after
~5s, migration throws, container crashes.
Fix: disconnect first, then reconnect with service-name aliases
so "postgres" and "redis" resolve correctly on meezi_default.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Root cause: after successful creation the form stayed on /blog/new.
User couldn't tell it worked, clicked Save again, the second attempt
hit the unique slug constraint and showed an error — making it look
like creation was broken.
Fix: adminPost is now typed, onSuccess redirects to /blog/{id} on new
posts so the user lands on the edit page immediately.
Also fixes commentCount being undefined in the list (MapPost now
includes comment count via eager-loaded Comments).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds POST /api/cafes/{cafeId}/demo/seed (owner-only) that seeds:
- 9% default VAT tax
- 7 menu categories + 59+ items via DemoMenuSeeder
- 15 inventory ingredients (coffee shop staples)
- 10 tables across 3 floors on the first active branch
Frontend DemoDataBanner appears on menu, tables, and inventory
pages when the café is completely empty, so owners can populate
demo data in one click instead of entering everything manually.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
Previously the subscribe mutation had no onError handler, so any
payment initiation failure (wrong merchant ID, ZarinPal API error,
disabled payment method) would silently re-enable the button with
no user feedback. Now errors are shown below the Pay button.
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>
npm ci failed in Docker because package-lock.json was stale (missing three
and the workbox/PWA deps) and @google/model-viewer@4.2.0 requires three@^0.182.0
while package.json pinned ^0.163.0. Bumped three and regenerated the lockfile.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Plan comparison and website pricing advertised branch counts that did not
match PlanLimitsData.ForTier: Pro now shows 3 (was 1) and Business shows
unlimited (was 5), matching what the backend actually enforces.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The admin app runs Next.js 14.2.18, where `next build --webpack` is an
unknown option (the flag only exists in Next 15+). This broke the CI
admin-web image build. Other web apps stay on the flag since they're on
Next 16.
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>
Next 16 defaults `next build` to Turbopack, which requires native SWC
bindings unavailable for Alpine musl from our npm mirror (only the WASM
fallback loads). Pass --webpack so the build uses the WASM SWC fallback
and succeeds inside the Docker images.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Move Capacitor and cordova-plugin-pushe to optionalDependencies. They are
only needed for the native mobile shell, are never imported by the Next.js
source, and are not served by the Liara npm mirror — so installing them as
hard dependencies broke the web image build. As optional deps, npm skips
them when the mirror can't resolve them.
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>
Embed Vazirmatn web font in printed bills, add branded header with logo
and tagline, and wait for fonts to load before printing for clean output.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Rebrand the public café-discovery app: directories web/finder→web/koja and
docker/finder→docker/koja, plus all service wiring (docker-compose, Caddy
subdomain koja.meezi.ir, env vars KOJA_PORT / NEXT_PUBLIC_KOJA_URL, CI
workflows) and the app's display name (Koja / کجا).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Problem: window.print() on the main page used A4 height (blank paper
after receipt), no RTL direction, and Tailwind styles leaked into print.
Solution — iframe isolation:
- lib/thermal-print.ts: builds a self-contained HTML document
(@page { size: 80mm auto; margin: 0 }, html { direction: rtl })
and fires it through a hidden off-screen <iframe>. The iframe
document contains only the receipt so height == content height.
- pos-slip-modal.tsx: Print button calls printThermal(buildThermalDocument())
instead of window.print(). Preview panel is unchanged (screen only).
- pos-receipt-print.css: updated @page + direction as fallback for any
remaining window.print() callers.
Works with USB driver (Atom A300) as default printer — OS print spooler
receives the job exactly as if it were any other document.
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>
crypto.randomUUID() is only available over HTTPS. Add a timestamp+random
fallback so the dashboard works on plain HTTP during development/IP access.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Empty directories are not tracked by git — without this the runner stage
COPY --from=builder /app/public ./public fails with "not found".
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Next.js 15+ requires params to be typed as Promise<{...}> and awaited.
Fixed 17 files: all [locale] pages, layouts, and blog [slug] page.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Next.js 15+ changed dynamic route params to Promise<...>.
Update GET and POST type signatures in blog comments route.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Initial commit of the Super-Admin web panel (Next.js + TypeScript).
CI admin-web-check job was failing because the directory was never
tracked in git.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>