Skip to main content

Portal Pages

Customer Portal (/portal/*)

Session-required. Customers can only see their own orders (API enforces ownerUserId filtering).

/portal/orders

Order list page. Shows non-archived, published orders belonging to the logged-in user.

Polling: The order list auto-refreshes every 30 seconds to pick up status changes pushed by staff. Implemented via useEffect + setInterval in the client component.

/portal/orders/[id]

Order detail page. Shows:

  • Order status across all three axes (order status, payment status, fulfillment status)
  • Line items (components, OS, services, warranties, shipping, peripherals, software)
  • Timeline of status changes (OrderStatusHistory)
  • Build timeline steps (public-facing only — no QA data, no internal notes)
  • Attachments (invoice + any published attachments)
  • Accept Quote button (when orderStatus is quote or claimed)
  • Pay button (when paymentStatus is awaiting_payment and orderStatus is confirmed)

Accept Quote flow: Calls POST /portal/orders/:id/accept-quote. Does not change order status — sends a staff notification. Staff must still convert the quote.

Pay flow:

  1. Calls POST /payments/create-order
  2. Gets back { orderCode }
  3. Redirects browser to Viva checkout

/portal/claim

Intermediate page shown after login when a claim code is in the session. Calls POST /claim with the stored claim code.

/portal/settings

Profile settings: name, email change (requires re-verification), password change.


Staff Portal (/staff/portal/*)

Session + admin/superadmin role required. Staff subdomain (staff.pcmr.gr) also gated by Cloudflare Access.

/staff/portal/orders

Paginated order list with full filters:

  • type (quote/order)
  • orderStatus, paymentStatus, fulfillmentStatus
  • Date range
  • Customer email search
  • Has invoice filter

/staff/portal/orders/[id]

Full order management panel. Actions available:

  • Edit all fields (status transitions, line items, tracking info)
  • Upload/replace invoice PDF
  • Publish order to customer
  • Convert quote to order
  • Archive/unarchive
  • Cancel
  • View full status history

Line item panel: Add/edit/remove/reorder items from all 7 catalog types. Prices are snapshot-stored at the time of adding — catalog price changes don't retroactively affect order items.

Totals: Recalculated automatically after any line item change (OrderTotalsService.recalc()).

/staff/portal/builds

Build list with filters for status and builder (staff member).

/staff/portal/builds/[id]

Full build management panel:

  • Build status transitions
  • Photo upload (10 named slots via drag-drop)
  • QA form (checklist, notes, pass/fail, sign-off)
  • Test runs (benchmark, stress test records)
  • Build timeline (chronological milestones)
  • Event log (freeform notes, incidents, corrections)
  • Temperature readings
  • Booklet generation / download

Booklet download: Resolves presigned URL for the generated PDF attachment, opens in new tab. If quickStartBookletId / technicalDossierBookletId is null, shows a "Generate" button instead.

/staff/portal/components

Component catalog management. Full CRUD for components with per-category spec editing. Price history attached to each component.

/staff/portal/users

User list + detail. Staff can:

  • Search users by email/name
  • View order count, session count
  • Ban/unban customers (role user only — cannot ban staff)
  • Change staff roles (cannot change own role)

/staff/portal/orders/create

New order form. Staff-initiated quote creation:

  • Customer email autocomplete (GET /admin/users/search?email=<q>)
  • All three order axes pre-set
  • claimCode generated if no customer selected

Key Client Components

ComponentLocationNotes
OrderStatusBadgecomponents/Maps all three status axes to badge colors
BuildPhotoUploadcomponents/build/Drag-drop with slot labeling
QAChecklistFormcomponents/build/Structured checklist with pass/fail per item
TimelineViewercomponents/build/Chronological build timeline
InvoiceUploadcomponents/order/PDF-only file upload with replace behavior
OrderItemsPanelcomponents/order/Full CRUD for all 7 line item types

Auto-Refresh Pattern

Customer portal polls for order status updates:

useEffect(() => {
const interval = setInterval(async () => {
const fresh = await fetch(`/api/portal/orders/${orderId}`);
const data = await fresh.json();
setOrder(data);
}, 30_000); // 30 seconds

return () => clearInterval(interval);
}, [orderId]);

Why 30 seconds? Balances freshness with API load. Staff status changes are not time-critical from the customer's perspective — a 30-second delay is acceptable.