Skip to content

Customers

Customer master and self-service portal. Covers the admin-side customer directory (/sales/customers), the reusable create/edit dialog, and the customer-facing portal at /customer.

Scope

  • Admin directory — staff-side list, create, edit, delete, import, export (src/pages/sales/Customers.tsx).
  • Create/edit dialog — the canonical "Add Customer" form reused from bookings, quotations, and partner flows (src/components/common/CustomerFormDialog.tsx).
  • Customer portal — logged-in customers view their bookings, invoices, visa status, requests (src/pages/customer/CustomerPortal.tsx).
  • Customer auth — email/phone login + signup for the customer portal (src/pages/customer/CustomerAuth.tsx).

The sub-directory src/pages/customer/ contains only the portal-facing screens — the admin-side customer management lives under src/pages/sales/.

Pages and components

Admin directory — src/pages/sales/Customers.tsx

Route: /sales/customers. Gated by customers.view (src/App.tsx:158).

Contains the searchable customer list, inline create form, profile dialog with ledger, document uploads to Google Drive, and CSV import/export. The inline "Add Customer" form in this page duplicates the field set from CustomerFormDialog (see src/pages/sales/Customers.tsx:1076-1141 for the inline DateInput usages). New flows should prefer the dialog component over re-implementing the form.

Reusable dialog — src/components/common/CustomerFormDialog.tsx

Shared across bookings wizard, quotations, partner portal customer creation, and anywhere else a customer record needs to be created on the fly. POST /sales/customers with the field set below.

Props (src/components/common/CustomerFormDialog.tsx:64-73):

  • open, onOpenChange — controlled dialog state.
  • onCreated(customer) — callback with the created record; callers use this to auto-select the new customer.
  • extraPayload — merged into the API payload. Used by partner portal to inject agentId, by wizards to tag source, etc.
  • title, description — override the dialog copy.

Customer portal — src/pages/customer/CustomerPortal.tsx

Route: /customer. Role-gated: allowedRoles={['customer']} (src/App.tsx:186). Permission matrix does not apply — access is enforced by ownership checks in API handlers (see docs/PERMISSIONS.md §4.5).

The portal surfaces: own bookings, payment history with receipt upload, visa case status with document download, invoice download, service-request threads, profile edit, active-session management. Data is fetched through src/services/customerPortalService.ts and src/services/customerRequestService.ts.

Customer auth — src/pages/customer/CustomerAuth.tsx

Route: /customer/auth. Public. Email or phone + password, Turnstile captcha, optional 2FA (src/pages/customer/CustomerAuth.tsx:73-80). Phone → email resolution goes through POST /auth/resolve-identifier (src/pages/customer/CustomerAuth.tsx:47-64). On success, role must be customer or the session is forcibly signed out (src/pages/customer/CustomerAuth.tsx:35-38).

src/pages/auth/Auth.tsx is the staff auth page, not the customer one. ResetPassword.tsx is shared.

Fields

Defined in CustomerFormData (src/components/common/CustomerFormDialog.tsx:20-40). Three sections:

Personal

Field Notes
title MR/MRS/MS/MSTR/MISS/DR/PROF
firstName, lastName Required. Auto-uppercased on change.
dob Date. DateInput with max={today}.
gender Male/Female
fatherName Auto-uppercased.
bloodGroup A+ through O-
nationality Free text; defaults to INDIAN.

Identity & documents

Field Notes
passportNo Alphanumeric only, uppercased, max 8 chars (CustomerFormDialog.tsx:215).
passportIssuedDate DateInput with max={today}.
passportExpiry DateInput. Warns in-form at < 6 months; flags expired (CustomerFormDialog.tsx:240-247).
gstNumber Optional. 15-char GSTIN, validated via isValidGstin / normalizeGstin from src/lib/gstin.ts. Invalid-but-non-empty shows inline error and disables Save (CustomerFormDialog.tsx:85-96, :288).

GST number is a recent addition

gstNumber was added to the customer schema and dialog. It flows into invoice numbering and GST-summary reports. Empty is valid; a value must match the 15-character format. See CustomerFormDialog.tsx:226-237.

Contact & address

Field Notes
email Lowercased on change.
phone PhoneInput component (country-code aware).
address Uppercased.
state LocationCombo over INDIA_LOCATIONS.states. Defaults to JAMMU AND KASHMIR.
district LocationCombo over INDIA_LOCATIONS.districts[state]. Cleared when state changes (CustomerFormDialog.tsx:268).
country Defaults to INDIA.
pinCode 6 digits, numeric only.

Date input behavior

All three date fields on this dialog (dob, passportIssuedDate, passportExpiry) use the shared DateInput component.

Chrome autofill was recently hardened

Chrome historically suggested saved dates and email-address values inside the DateInput text field, producing a flickering autocomplete overlay and occasionally clobbering user input. Two consecutive fixes landed on the fix/date-input-autocomplete branch:

  1. 21f841e — disables Chrome autofill suggestions on DateInput.
  2. 38cc4a2 — separates the calendar-icon trigger from the text input so picking a date no longer flickers the suggestion dropdown.

If you add a new date field, use DateInput from src/components/ui/date-input.tsx — do not render a bare <Input type="date"> or you will regress both of these fixes.

DateInput accepts user-typed dd/mm/yyyy with auto-slash insertion (date-input.tsx:60-74) and clamps against max / min (date-input.tsx:77-79). The calendar picker writes back in YYYY-MM-DD; display reformats through formatDateStamp.

Customer-facing auth flow

Route Purpose Guard
/customer/auth Sign in / sign up / OTP Public
/customer Portal home allowedRoles={['customer']}
/reset-password Shared password reset Public (token-scoped)

The staff auth page (src/pages/auth/Auth.tsx) is a separate flow for the /app admin console. A customer who signs in on the staff page is rejected; a staff user on /customer/auth is signed out on load (CustomerAuth.tsx:35-38). Partner/agent users have their own page at /partner/auth (see Partners).

Permissions

Canonical reference: docs/PERMISSIONS.md §4.1 and §6.1.

Action Permission Enforced at
View /sales/customers customers.view Route (App.tsx:158)
Create customer customers.create <PermissionGate> + API
Edit customer customers.edit <PermissionGate> + API
Delete customer customers.delete <PermissionGate> + API
Upload / delete Drive doc customers.edit Server-side
View customer ledger customers.view Server-side scoping
Portal access role = customer (no matrix permission) Route allowedRoles + API ownership