Skip to main content

Customer Portal — Application Logic

Status: populated Owner: Product Engineering (Frontend) Last updated: 2026-04-18

1. Authentication Flow

Sign-In

  1. /login page renders a Firebase Auth UI component (email + password).
  2. On submit, signInWithEmailAndPassword is called via Firebase client SDK.
  3. The returned idToken is POSTed to POST /v1/auth/firebase via the Next.js route handler /api/auth/login.
  4. The backend verifies the Firebase token, checks the customer custom claim, and returns a platform JWT + refresh token.
  5. The route handler sets httpOnly; Secure; SameSite=Strict cookies (__session, __refresh) and redirects to /dashboard.

Session Guard (Middleware)

middleware.ts intercepts all routes under /(protected). It:

  • Reads __session cookie.
  • Verifies JWT signature and exp using the JWKS endpoint cached in memory.
  • If expired and __refresh present: calls /api/auth/refresh; rotates cookies silently.
  • If both missing or invalid: redirects to /login with ?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

  1. User clicks "Create API Key"; a modal opens (ApiKeyCreateModal).
  2. User enters name and selects scopes from a checkbox list.
  3. On submit, client calls POST /api/api-keys (BFF route).
  4. On success, the modal transitions to "Key Created" state:
    • Displays rawKey in a masked text field with a "Copy to clipboard" button.
    • Shows a warning: "This key will not be shown again."
  5. rawKey is held only in component state (useState). On modal close, state is reset — raw key is gone from memory.
  6. Key list is re-fetched (React router.refresh()).

Revoke

  1. User clicks "Revoke" on a key row.
  2. Confirmation dialog appears.
  3. On confirm, client calls DELETE /api/api-keys/{keyId}.
  4. 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:

  1. Client-side validation: E.164 regex for to, non-empty body.
  2. POST /api/send-test → proxies POST /v1/messages.
  3. Success: shows messageId with link to /messages/{messageId}.
  4. 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-keys list 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.

FieldRule
to (SMS)E.164 regex: ^\+[1-9]\d{1,14}$
body1–918 characters (6 SMS parts max)
Webhook URLhttps:// prefix required; valid URL
API key name3–64 characters; alphanumeric + spaces