Merchant dashboard

Routes & screens

Every dashboard route, the permission it needs, and the implementation details worth knowing. For the user-facing description, see the product tour.


Route table

RouteGatePage
(signed out)—Login.tsx: email, then 6-digit OTP (auto-submits). Has its own language switch.
/ownerOverview.tsx
/ordersorders.viewOrders.tsx
/orders/neworders.createNewOrder.tsx
/orders/:id (+ panels)ownerOrderDetail.tsx
/customers, /customers/:idcustomers.view / ownerCustomers.tsx, CustomerDetail.tsx
/products, /products/new, /products/:id/edit, /products/view-settingsproducts.view / products.writeProducts.tsx, ProductEditor.tsx, ProductViewSettings.tsx
/products/modifiersproducts.view (edit: products.write)Modifiers.tsx
/categoriesproducts.viewCategories.tsx
/inventoryinventory.writeInventory.tsx
/jardeinventory.write + app jardeJarde.tsx
/discounts, /discounts/new, /discounts/:id/editdiscounts.view / discounts.writeDiscounts.tsx, DiscountEditor.tsx (old /discounts/v2/* redirects)
/sections, /sections/new, /sections/:id/editproducts.view / sections.writeSections.tsx, SectionEditor.tsx
/metadata/*products.viewMetadata*.tsx (old /lists/* redirects)
/staff, /staff/new, /staff/:id/editstaff.manage (editor: owner)Staff.tsx, StaffEditor.tsx
/staff/roles/*staff.manage (editor: owner)StaffRoles.tsx, StaffRoleEditor.tsx
/staff/discount-pinsownerDiscountPins.tsx
/locations/*products.viewLocations.tsx, LocationEditor.tsx
/packages, /carriersorders.fulfillPackages.tsx, Carriers.tsx
/shipping (hidden)orders.fulfillShipping.tsx
/billing (hidden)ownerBilling.tsx
/reportsreports.view + app reportsReports.tsx
/appsowner (others see why they can't change anything)Apps.tsx
/profileownerProfile.tsx
*—Redirects to /

The sidebar (components/nav.ts, AppSidebar.tsx) also shows a shop switcher, the plan name (owners only), an active-orders badge, and POS / Accounting launchers. It collapses to an icon rail, becomes a sheet on mobile, and mirrors in RTL.

Implementation notes

Overview

  • The figures are computed client-side (lib/analytics.ts) from up to 4 pages of get-shop-orders, including archived orders, because the analytics endpoints aren't served.
  • The page labels the window it covers. There are no period comparisons.
  • The alerts strip comes from get-shop-alerts.

Orders

  • Filters live in a drawer. The sales channel enum is in lib/order.ts:96.
  • Row actions: one get-order-actions call per page decides which rows get the payment menu (OrderPaymentMenu.tsx).
  • New order:
    • Lines are grouped by source location, or autoRoute lets the server choose.
    • A price override is sent only if the merchant typed one. Line discounts can be an amount or a percent (PR #63).
    • Tax shows "—", because only the server knows the rate.
    • The order is always created unpaid. A dirty form warns before you leave.
  • Order detail:
    • Panels are nested routes: fulfill/:foId, tracking/:fulfillmentId, refund, return, returns/:rid/receive, returns/:rid/tracking/:rfoId.
    • Every button comes from get-order-actions / get-fulfillment-actions.
    • Print documents are in OrderPrintDoc.tsx + styles/print.css.
    • There's no timeline, because there's no endpoint for one.
OrderDetail: the primary action and menus come from get-order-actions.
OrderDetail: the primary action and menus come from get-order-actions.

Products

  • List:
    • search-products hits the configured backend (Postgres by default) when searching or filtering.
    • Facets come from filterable fields and are applied on Done.
    • Status is derived from stock; there's no draft/active state.
  • View settings are stored per shop in localStorage (lib/productViewSettings.ts).
  • Import (lib/productTransfer.ts):
    • Required columns: product_key, title, price.
    • Other columns: field.*, variant_field.*, stock_<locationId>, and more.
    • The whole file is validated, then create-only: rows with product_id are skipped. Products are written one per request, with a partial-failure count.
  • Export: CSV (with BOM) or XLSX, in the same format as import.
  • Editor:
    • Saves the full product every time (desired-state set-product).
    • Refuses to edit a multi-variant product that has no options, to avoid damaging it.
    • Modifier group checkboxes save immediately.
    • Margin colours: ≥ 35% green, ≥ 20% amber, otherwise red.
    • Media goes to Cloudinary via a signed upload (MediaUploader.tsx).

Catalogue

  • Categories import/export (lib/categoryTransfer*.ts): title + parent_1..n columns. Existing paths are skipped, and parents are created first.
  • Metadata:
    • New fields can be text, rich text, integer, decimal, boolean, date or URL.
    • JSON and reference fields still open, but new reference fields can't be created for now.
    • The entry-type field editor is commented out (MetadataEntryTypeDetail.tsx:208-237).
  • Sections: drag-reorder saves immediately. Dangling items are flagged.

Inventory & Jarde

  • Adjust sets an absolute quantity with reason manual. Transfer moves stock between branches. Transfer selected (PR #62) sends the selected rows to transfer/bulk, all or nothing. History shows movements across all branches.
  • Jarde parses sheets in lib/jarde.ts, then calls plan and apply.

Team

  • Phone fields all use the country-code picker (staff since PR #65). Israel and US-embargoed countries are excluded from phone and country pickers (PR #64, lib/countries.ts).
  • Role editor: permissions are grouped as served by get-staff-permissions. CategoryScopePicker sets catalogue scope.
  • Staff editor: RoleAssignmentEditor sets role × locations. Ticking every branch saves as all.
  • Discount PINs: shown once, with a copy button.

Settings & apps

  • Locations: AddressSearch → places proxy, and a draggable pin reverse-geocodes. Required: name, country, city, address line 1, coordinates. The Shelves panel depends on the shelves app.
  • Store profile: saves the full shop record, leaving flat shipping rate and country untouched. Language, currency and weight unit come from the API lists.
  • Reports:
    • The report list, filters and columns all come from the server.
    • 10 rows per page.
    • Print and Excel export re-run the report for up to 500 rows, keeping group headers and subtotals, and say so when the result is truncated.
  • AI assistant (components/assistant/*): behind VITE_ASSISTANT_ENABLED. Replies come from a local stub, not a model.
Previous
Dashboard architecture