Customer Portal — Application Logic
Status: populated Owner: Product Engineering (Frontend) Last updated: 2026-04-18
1. Authentication Flow
Sign-In
/loginpage renders a Firebase Auth UI component (email + password).- On submit,
signInWithEmailAndPasswordis called via Firebase client SDK. - The returned
idTokenis POSTed toPOST /v1/auth/firebasevia the Next.js route handler/api/auth/login. - The backend verifies the Firebase token, checks the
customercustom claim, and returns a platform JWT + refresh token. - The route handler sets
httpOnly; Secure; SameSite=Strictcookies (__session,__refresh) and redirects to/dashboard.
Session Guard (Middleware)
middleware.ts intercepts all routes under /(protected). It:
- Reads
__sessioncookie. - Verifies JWT signature and
expusing the JWKS endpoint cached in memory. - If expired and
__refreshpresent: calls/api/auth/refresh; rotates cookies silently. - If both missing or invalid: redirects to
/loginwith?redirect=<originalPath>.
Logout
POST /api/auth/logout clears cookies and calls Firebase signOut() from the client.
2. API Key Management (/api-keys)
List
Server component calls GET /v1/api-keys on render; passes data as props to the client table component.
Create
- User clicks "Create API Key"; a modal opens (
ApiKeyCreateModal). - User enters
nameand selects scopes from a checkbox list. - On submit, client calls
POST /api/api-keys(BFF route). - On success, the modal transitions to "Key Created" state:
- Displays
rawKeyin a masked text field with a "Copy to clipboard" button. - Shows a warning: "This key will not be shown again."
- Displays
rawKeyis held only in component state (useState). On modal close, state is reset — raw key is gone from memory.- Key list is re-fetched (React
router.refresh()).
Revoke
- User clicks "Revoke" on a key row.
- Confirmation dialog appears.
- On confirm, client calls
DELETE /api/api-keys/{keyId}. - Key list re-fetches.
3. Test SMS Sender (/send-test)
Form fields: to (E.164 number), from (sender ID), body (up to 160 chars), optional reference.
On submit:
- Client-side validation: E.164 regex for
to, non-emptybody. POST /api/send-test→ proxiesPOST /v1/messages.- Success: shows
messageIdwith link to/messages/{messageId}. - Error: displays error message from API response.
Character counter shown in real-time; multi-part SMS warning at > 160 chars.
4. Message Logs (/messages)
Filter Panel
Filters: date range (date picker), status (multi-select), to (text), from (text).
Filter state stored in URL search params via useRouter and useSearchParams for shareable deep-links.
Rendering
Server component reads search params from URL, calls GET /v1/messages with those params, renders a table.
On filter change, the client updates the URL, triggering a server component re-render (full-page navigation within the same layout).
Pagination
"Load more" style — appends page param. Uses Next.js Link for SEO-friendly pagination.
Message Detail
Clicking a row navigates to /messages/{messageId}. Server component fetches GET /v1/messages/{messageId} for full detail including DLR codes, operator name, timestamps.
5. Webhook Configuration (/webhooks)
List
Server component fetches GET /v1/webhooks.
Create
Modal collects HTTPS URL and event types (multi-select checkboxes).
On success, displays signingSecret once in the same "created" modal pattern as API keys.
Edit
Inline edit form: URL and event list. PUT /api/webhooks/{webhookId}.
Delete
Confirmation dialog → DELETE /api/webhooks/{webhookId}.
6. Billing (/billing)
Two-tab layout: "Invoices" and "Usage".
Invoices tab: Paginated table of invoices. Each row has a "Download PDF" button that opens the pre-signed pdfUrl in a new tab.
Usage tab: Current period metrics card (messages sent, delivered, delivery rate, credit balance). Fetched from GET /v1/billing/usage on server.
7. Dashboard (/dashboard)
Summary cards fetched server-side:
- Messages sent today (from
billing/usage) - Delivery rate today (from
billing/usage) - Active API keys count (from
api-keyslist count) - Webhooks active count
No polling on the customer dashboard — static at render time with a manual refresh button.
8. Form Validation Rules
All forms use React Hook Form + Zod schemas for client-side validation. Server-side errors (422) are mapped back to field-level errors using the error detail array from the API response.
| Field | Rule |
|---|---|
to (SMS) | E.164 regex: ^\+[1-9]\d{1,14}$ |
body | 1–918 characters (6 SMS parts max) |
| Webhook URL | https:// prefix required; valid URL |
| API key name | 3–64 characters; alphanumeric + spaces |