Commit Graph

27 Commits

Author SHA1 Message Date
soroush.asadi 82d1cf8e9e fix(auth): stop logging users out on every deploy
CI/CD / CI · API (dotnet build + test) (push) Successful in 46s
CI/CD / CI · Admin API (dotnet build) (push) Successful in 28s
CI/CD / CI · Dashboard (tsc) (push) Successful in 1m8s
CI/CD / CI · Admin Web (tsc) (push) Successful in 37s
CI/CD / CI · Website (tsc) (push) Successful in 44s
CI/CD / CI · Koja (tsc) (push) Successful in 50s
CI/CD / Deploy · all services (push) Successful in 2m50s
Diagnostic on prod confirmed the backend keeps sessions valid across deploys
(stable 64-char JWT key, 30-day access tokens, 62 refresh tokens persisting in
Redis with appendonly; redis/db never restart on deploy). The forced logout was
client-side:

1. The axios refresh path treated ANY refresh failure as "session gone" and
   nuked the tokens. During the ~30s API restart window of a deploy, the refresh
   POST gets a 502/timeout (transient) → user kicked to /login. Now refresh
   distinguishes a definitive 4xx (truly invalid/expired refresh → log out) from
   a transient network/5xx failure (reject + keep the session; retry later).
   Refresh tokens are opaque Redis GUIDs, so they survive even a key rotation —
   the only thing that was breaking sessions was this over-eager logout.

2. PWA service worker served a stale app shell after an update, pointing at JS
   chunks the new build replaced. Added skipWaiting + clientsClaim +
   cleanupOutdatedCaches and a NetworkFirst handler for navigations so the HTML
   and its chunk refs always match the live deploy; hashed static stays
   CacheFirst.

Net: a normal update no longer logs anyone out. tsc clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 20:42:38 +03:30
soroush.asadi 76d4434581 fix(qr): guest menu 500 (SSR) + remove café discovery from owner panel
1. The /q/{code} guest menu returned HTTP 500 on every load. Root cause:
   menu-item-model-viewer.tsx did a top-level `import "@google/model-viewer"`,
   a browser-only lib that touches `self` at module evaluation. Next pulled
   it into the server module graph (page → qr-guest-menu → qr-menu-3d-sheet →
   model-viewer) and SSR crashed with "self is not defined". Now the library
   is imported lazily inside useEffect (client-only); a poster placeholder
   shows until the custom element registers. Verified /q/* now returns 200.

2. Removed the "discover" (browse other cafés) item from the café owner
   sidebar — café discovery belongs in Koja, not the owner panel. The owner
   still manages their OWN Koja listing from Settings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 15:08:48 +03:30
soroush.asadi 00649d0248 feat(sms): bring-your-own-provider — cafés use their own SMS account
CI/CD / CI · API (dotnet build + test) (push) Successful in 40s
CI/CD / CI · Admin API (dotnet build) (push) Successful in 30s
CI/CD / CI · Dashboard (tsc) (push) Successful in 1m8s
CI/CD / CI · Admin Web (tsc) (push) Successful in 37s
CI/CD / CI · Website (tsc) (push) Successful in 45s
CI/CD / CI · Koja (tsc) (push) Successful in 50s
CI/CD / Deploy · all services (push) Successful in 5m16s
The platform no longer sells SMS. Each café saves its OWN Kavenegar API
key + sender line (new Cafes columns + migration) and campaigns are sent
and billed through that account.

Backend:
- GET/PUT /sms/settings (Manager/Owner; key echoed masked, verified
  against the provider before saving)
- campaign + balance use the café's credentials; SMS_NOT_CONFIGURED
  error when missing; plan-tier SMS gating removed everywhere
  (PlanLimitChecker, SmsMarketingService, billing status)
- platform Kavenegar config stays ONLY for login OTPs (env/DB)
- design-time DbContext factory so `dotnet ef migrations add` works
  without booting the host

Dashboard:
- SMS screen: provider-settings card, not-configured callout, campaign
  form disabled until configured; quota bar removed (usage stays as info)
- subscription screen + plan comparison no longer show SMS limits

Admin panel:
- Kavenegar/SMS section removed from integrations (request field now
  optional; stored OTP config untouched)
- SMS limit field removed from the plan editor
- nav label "درگاه و پیامک" → "درگاه پرداخت و AI"

fa/en/ar translations. 86 tests pass; all tsc clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 09:23:50 +03:30
soroush.asadi 615d5348de fix(subscription): plan comparison + checkout read the live plan catalog
CI/CD / CI · API (dotnet build + test) (push) Successful in 42s
CI/CD / CI · Admin API (dotnet build) (push) Successful in 30s
CI/CD / CI · Dashboard (tsc) (push) Successful in 1m10s
CI/CD / CI · Admin Web (tsc) (push) Successful in 38s
CI/CD / CI · Website (tsc) (push) Successful in 47s
CI/CD / CI · Koja (tsc) (push) Successful in 50s
CI/CD / Deploy · all services (push) Successful in 3m27s
The merchant plan page hard-coded 4 tiers, prices and a feature matrix
that drifted from the admin-editable platform catalog (Starter tier
missing, stale prices/features). PlanComparison and CheckoutScreen now
consume /platform/plans + new /platform/features-catalog:

- columns = active plans by SortOrder (incl. Starter), names from
  DisplayNameFa/En, prices from MonthlyPriceToman
- limit rows from PlanLimitsData (int.MaxValue → "نامحدود")
- feature rows from the feature catalog, ticked via FeatureKeys
- checkout validates the ?plan= param against isBillableOnline and
  prices from the catalog — no more client-side price constants

fa/en/ar limit-row labels added.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 08:16:29 +03:30
soroush.asadi d811b7d6d5 feat(dashboard): simplify navigation — frequency-based IA
CI/CD / CI · API (dotnet build + test) (push) Successful in 47s
CI/CD / CI · Admin API (dotnet build) (push) Successful in 28s
CI/CD / CI · Dashboard (tsc) (push) Successful in 1m6s
CI/CD / CI · Admin Web (tsc) (push) Successful in 40s
CI/CD / CI · Website (tsc) (push) Successful in 44s
CI/CD / CI · Koja (tsc) (push) Successful in 50s
CI/CD / Deploy · all services (push) Successful in 2m43s
The sidebar had 22 items in 5 accordion groups, all defaulting closed:
first visit showed five vague headers and zero destinations, there was
no Dashboard/Home link at all, and rare pages (taxes, subscription) had
equal weight with POS. Restructured around usage frequency:

- Flat primary (always visible, no header): Dashboard, POS, Tables,
  Kitchen, Queue, Reservations, Menu, Reports
- Two collapsible groups: Customers & marketing (crm, coupons, sms,
  reviews, discover) and Café management (inventory, expenses, shifts,
  taxes, hr, branches)
- Footer utility icons: settings, subscription, support
- Removed "notifications" from the nav (duplicate of the topbar bell)

Other fixes folded in:
- Deleted [locale]/page.tsx which redirected "/" to /pos — it made the
  POS exit button a no-op loop and left OverviewScreen unreachable.
  "/" now renders the overview home; login still lands on /pos.
- Branch gating moved from group-level to an item whitelist
  (BRANCH_ALLOWED_NAV_KEYS) — also closes the hole where branch
  accounts could deep-link to /reports etc. past the RouteGuard.
- RouteGuard now checks footer items too (subscription stays gated).
- revalidate=300 on the locale layout: Next emitted s-maxage=31536000
  and the WCDN edge kept serving year-old HTML shells after deploys.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 22:06:59 +03:30
soroush.asadi 59486cdf24 fix(pos2): wait for branch before fetching menu + add left category sidebar
CI/CD / CI · API (dotnet build + test) (push) Failing after 3m20s
CI/CD / CI · Admin API (dotnet build) (push) Failing after 3m19s
CI/CD / CI · Dashboard (tsc) (push) Successful in 1m5s
CI/CD / CI · Admin Web (tsc) (push) Successful in 41s
CI/CD / CI · Website (tsc) (push) Successful in 44s
CI/CD / CI · Koja (tsc) (push) Successful in 52s
CI/CD / Deploy · all services (push) Has been cancelled
Race fix: orderBranchId now returns `undefined` (not null) while the /branches
query is in flight. usePos2Menu treats undefined as "not yet determined" and
skips the fetch, preventing getBranchMenu(cafeId, null) → empty array.
Once branchesFetched=true, orderBranchId resolves to the correct branchId
(or null for café-wide fallback).

Layout: desktop order screen now shows a left vertical category sidebar
(116 px, md+) instead of horizontal chips, giving the classic POS sidebar
feel. Horizontal chips kept for mobile (<md). Menu grid columns adjusted.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 07:10:03 +03:30
soroush.asadi 75a0a1c834 feat(pos): wire POS v2 to live data (board, orders, payments)
CI/CD / CI · API (dotnet build + test) (push) Successful in 43s
CI/CD / CI · Admin API (dotnet build) (push) Successful in 32s
CI/CD / CI · Dashboard (tsc) (push) Successful in 1m5s
CI/CD / CI · Admin Web (tsc) (push) Successful in 37s
CI/CD / CI · Website (tsc) (push) Successful in 45s
CI/CD / CI · Koja (tsc) (push) Successful in 49s
CI/CD / Deploy · all services (push) Successful in 2m42s
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>
2026-06-04 00:16:37 +03:30
soroush.asadi dcdb0d5747 feat(realtime): global guest-order alert on the dashboard
CI/CD / CI · API (dotnet build + test) (push) Successful in 47s
CI/CD / CI · Admin API (dotnet build) (push) Successful in 32s
CI/CD / CI · Dashboard (tsc) (push) Successful in 1m4s
CI/CD / CI · Admin Web (tsc) (push) Successful in 37s
CI/CD / CI · Website (tsc) (push) Successful in 44s
CI/CD / CI · Koja (tsc) (push) Successful in 49s
CI/CD / Deploy · all services (push) Successful in 3m9s
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.
2026-06-03 02:42:29 +03:30
soroush.asadi 7d06f149d3 feat(plans): menu watermark on Free (removed by paid feature)
Guest QR menu shows a "ساخته‌شده با میزی" watermark under the menu unless the café's
plan has the `watermark_removed` feature (Starter+).

- PublicMenuDto gains ShowWatermark; PublicService computes it from
  IsFeatureEnabledForCafeAsync("watermark_removed") for both slug and branch menus.
- Guest menu renders the watermark footer when showWatermark.
- NoOpPlatformCatalogService test double (all features on) for the PublicService
  ctor; QrMenuTests updated.

86 tests pass; dashboard tsc clean.
2026-06-03 02:10:24 +03:30
soroush.asadi eb165db182 feat(offline): make every dashboard write durable offline (P2–P5)
CI/CD / CI · API (dotnet build + test) (push) Successful in 1m0s
CI/CD / CI · Admin API (dotnet build) (push) Successful in 38s
CI/CD / CI · Dashboard (tsc) (push) Successful in 1m5s
CI/CD / CI · Admin Web (tsc) (push) Successful in 36s
CI/CD / CI · Website (tsc) (push) Successful in 44s
CI/CD / CI · Koja (tsc) (push) Successful in 49s
CI/CD / Deploy · all services (push) Successful in 3m11s
Builds on the outbox engine to take the whole dashboard offline in one place
instead of wiring 114 mutation sites individually.

Frontend (single chokepoint = the API client):
- offline-write: any write auto-queues to the outbox on offline/network failure
  and returns an optimistic value; the online path is unchanged apart from an
  Idempotency-Key header (so even online retries de-dup). entityType is derived
  from the URL; POSTs get a remappable local id.
- client.doWrite unifies POST/PUT/PATCH/DELETE through this path. WriteOptions
  gains `offline: "queue" | "reject" | "manual"`.
- Guardrails: auth / billing / payments / SMS / exports are online-only and throw
  OFFLINE_UNAVAILABLE offline rather than queueing (no queued double-charges or
  surprise SMS blasts). use-api-error resolves the friendly localized message
  (fa/en/ar).
- submit-order opts out ("manual") to keep its richer local-Order mock; shared
  helpers de-duplicated into offline-write.
- Request persistent storage on mount so unsynced writes survive eviction.

Backend:
- IdempotencyCleanupJob: daily purge of idempotency records older than 7 days
  (the table now gets a row per keyed write). Registered in Hangfire. No migration.

86 API tests pass; dashboard tsc + build clean.
2026-06-02 18:34:54 +03:30
soroush.asadi 3b468b48d9 feat(dashboard/offline): generic idempotent outbox + ID remapping
CI/CD / CI · API (dotnet build + test) (push) Successful in 48s
CI/CD / CI · Admin API (dotnet build) (push) Successful in 53s
CI/CD / CI · Dashboard (tsc) (push) Successful in 1m5s
CI/CD / CI · Admin Web (tsc) (push) Successful in 35s
CI/CD / CI · Website (tsc) (push) Successful in 45s
CI/CD / CI · Koja (tsc) (push) Successful in 49s
CI/CD / Deploy · all services (push) Successful in 3m12s
Completes offline Phase 1 (frontend). Generalises the POS-orders-only queue into
a reusable write engine and fixes the two correctness bugs in the old path.

- offline-db: generic `outbox` store (DB v3, order_queue/kv preserved) with
  enqueue/list/update/remove + a persisted client→server id map.
- outbox.ts: drains in causal order — remaps local_* ids to server ids (blocking
  an op until its creator syncs), sends each op with its idempotency key, and
  classifies failures (offline → stop; 5xx / in-progress → retry; 4xx → poison
  after 5 attempts). remap/blocked logic validated against representative cases.
- client: apiPost/Put/Patch/Delete take an optional idempotencyKey →
  `Idempotency-Key` header; ApiClientError now carries HTTP status.
- submit-order: generates ONE idempotency key per submit, used for both the
  online attempt and the queued replay → server de-dups (no more double-create);
  offline create carries createsClientId so a later add-items remaps onto the
  real order instead of spawning a second order.
- use-offline-sync: drains the outbox, one-time migrates legacy order_queue
  items, invalidates queries after a successful sync.

tsc + production build clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 18:19:29 +03:30
soroush.asadi 132f0921e0 feat(dashboard/offline): persist React Query cache for offline reads
First slice of offline-first (Phase 1). Makes every dashboard area *viewable*
offline with last-synced data, instead of empty lists on an offline reload
(previously only next-pwa's 10-min API cache survived).

- offline-db: add a generic `kv` IndexedDB store (DB v2, preserves order_queue)
  with kvGet/kvSet/kvDelete; all degrade silently on quota/unavailable.
- query-persister: debounced snapshot of the React Query cache via
  dehydrate/hydrate (no new dependency). Restore is guarded by a schema buster,
  24h max-age, and a café scope so one tenant never hydrates another's data.
- providers: gcTime 24h so hydrated data isn't GC'd; restore on mount + persist
  on cache changes, re-scoped when the signed-in café changes.

No write-path changes; the existing POS order queue is untouched. Next in
Phase 1: generalize that queue into an idempotent outbox with client→server
ID remapping.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 17:41:15 +03:30
soroush.asadi 15def7ff1c feat: delete actions for warehouse/reservations/coupons/customers + Koja listing toggle
CI/CD / CI · API (dotnet build + test) (push) Successful in 1m10s
CI/CD / CI · Admin API (dotnet build) (push) Successful in 52s
CI/CD / CI · Dashboard (tsc) (push) Successful in 1m5s
CI/CD / CI · Admin Web (tsc) (push) Successful in 35s
CI/CD / CI · Website (tsc) (push) Successful in 45s
CI/CD / CI · Koja (tsc) (push) Successful in 55s
CI/CD / Deploy · all services (push) Successful in 3m29s
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>
2026-06-02 16:14:40 +03:30
soroush.asadi 24da1e0522 feat(orders): per-item kitchen/bar notes (POS + QR app + KDS)
CI/CD / CI · API (dotnet build + test) (push) Successful in 57s
CI/CD / CI · Admin API (dotnet build) (push) Successful in 57s
CI/CD / CI · Dashboard (tsc) (push) Successful in 1m6s
CI/CD / CI · Admin Web (tsc) (push) Successful in 35s
CI/CD / CI · Website (tsc) (push) Successful in 44s
CI/CD / CI · Koja (tsc) (push) Successful in 49s
CI/CD / Deploy · all services (push) Successful in 2m43s
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>
2026-06-02 09:37:59 +03:30
soroush.asadi 75d5bbc84a fix(i18n): localize API error messages by code (no more raw English)
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>
2026-06-02 00:04:48 +03:30
soroush.asadi 024a455ab3 fix: menu item/category create, demo banner reach, token refresh, blog publish
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>
2026-06-01 18:25:34 +03:30
soroush.asadi 5e980cdfc0 feat: plan limits, café location, nearby API, Iran map section
CI/CD / CI · API (dotnet build + test) (push) Successful in 56s
CI/CD / CI · Admin API (dotnet build) (push) Successful in 49s
CI/CD / CI · Dashboard (tsc) (push) Successful in 1m4s
CI/CD / CI · Admin Web (tsc) (push) Successful in 34s
CI/CD / CI · Website (tsc) (push) Successful in 44s
CI/CD / CI · Koja (tsc) (push) Successful in 50s
CI/CD / Deploy · all services (push) Successful in 3m15s
• PlanLimits: add MaxMenuCategories (Free→3), MaxMenuItems (Free→30),
  CanAccessCrm and CanAccessStatistics (Pro+ only)
• MenuController: enforce category/item limits before create (403 + PLAN_LIMIT_REACHED)
• Cafe entity + EF migration: Latitude/Longitude (double?, nullable)
• CafeSettingsController: PATCH accepts lat/lng with range validation
• PublicController: GET /api/public/map-markers (marketing SVG map feed)
  and GET /api/public/nearby (Koja nearby-cafés with Haversine sort)
• Dashboard settings: location card with OSM iframe preview + Neshan link
• Website homepage: IranMapSection — stylised SVG silhouette with
  SMIL-animated blinking dots at real café coordinates

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 15:09:09 +03:30
soroush.asadi cd1af30bbc fix: sidebar accordion + koja slug + support ticket LINQ crash
CI/CD / CI · API (dotnet build + test) (push) Successful in 5m50s
CI/CD / CI · Admin API (dotnet build) (push) Successful in 32s
CI/CD / CI · Dashboard (tsc) (push) Successful in 1m3s
CI/CD / CI · Admin Web (tsc) (push) Successful in 35s
CI/CD / CI · Website (tsc) (push) Successful in 44s
CI/CD / CI · Koja (tsc) (push) Successful in 48s
CI/CD / Deploy · all services (push) Has been cancelled
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>
2026-05-31 22:28:25 +03:30
soroush.asadi 345ae0a4b5 first commit
CI/CD / CI · Admin API (dotnet build) (push) Successful in 41s
CI/CD / CI · Admin Web (tsc) (push) Failing after 5s
CI/CD / CI · Website (tsc) (push) Failing after 4s
CI/CD / CI · Koja (tsc) (push) Failing after 5s
CI/CD / CI · API (dotnet build + test) (push) Successful in 1m13s
CI/CD / CI · Dashboard (tsc) (push) Failing after 2m32s
CI/CD / Deploy · all services (push) Has been skipped
2026-05-31 11:06:24 +03:30
soroush.asadi 51e422272d bugfix : remove orphan 2026-05-30 09:42:32 +03:30
soroush.asadi c68cca4f17 Add OTP login flow and multi-cafe role switching
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>
2026-05-29 17:14:46 +03:30
soroush.asadi 923a00b113 Redesign thermal receipts with Vazirmatn font and cafe branding
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>
2026-05-29 17:14:32 +03:30
soroush.asadi fc21471131 Native thermal printing via iframe — 80mm, RTL, no blank tail
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>
2026-05-29 02:52:46 +03:30
soroush.asadi 42d7667735 Fully implement Kavenegar SMS support
Core changes:
- ISmsService: add SendBulkAsync (batches of 200) + GetAccountInfoAsync
- KavenegarSmsService: POST requests, sender number config, bulk send
  via comma-separated receptors, account balance, full error code mapping
  (HTTP 400-432), enabled-flag check before any send
- SmsMarketingService: replaced per-recipient loop with SendBulkAsync
- SmsController: new GET /sms/balance endpoint returns Kavenegar credit
- SmsDtos: SmsBalanceDto
- IntegrationDtos + PlatformIntegrationService: SenderNumber field
- appsettings.json + docker-compose: Kavenegar__SenderNumber = 90005671

Dashboard:
- sms-screen: char counter, SMS parts indicator (Persian 70/67 chars,
  Latin 160/153), account balance card, sender line display, result banner

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 02:38:06 +03:30
soroush.asadi a21cb7dd8e fix(dashboard): fallback for crypto.randomUUID on HTTP (non-secure context)
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>
2026-05-29 02:01:21 +03:30
soroush.asadi 79deab543a Redesign POS order flow with order type picker and counter/takeaway support
- Add OrderTypePicker screen: Table / Counter / Takeaway cards shown when no
  active session, replacing the old always-visible table board
- Move PosTableBoard into a modal overlay (opens on Table selection or
  "Assign Table" for counter orders)
- Add orderType field + setOrderType action to cart store
- Counter and Takeaway orders no longer require a table to submit
- Add "Assign Table →" button in cart for counter orders with active session
- Rewrite category tabs as horizontal scrollable row (no wrapping)
- Larger product cards with 4:3 thumbnail + quantity badge overlay
- Bigger quantity controls (h-8 w-8) and "New order" back button in header
- Add i18n keys for order types in en/fa/ar

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 00:07:58 +03:30
soroush.asadi 131ecdbbe6 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>
2026-05-27 21:34:12 +03:30