Skip to content

Next.js Frontend Overview

The Next.js app (app/) is the mobile PWA frontend for SeedTrust. It remains the current interface for Intended Parents, Surrogates, IP Representatives, Agency Owners, and Case Managers on mobile, and it consumes the FastAPI backend exclusively today. Strategically, this app is a maintenance/support surface while product ownership consolidates into Flask.


Tech Stack

LayerTechnology
FrameworkNext.js 15, React 18
LanguageTypeScript
StylingTailwindCSS
UI Componentsshadcn/ui + custom SeedTrust components
Data FetchingReact Query (TanStack Query)
AuthNextAuth.js (Credentials provider)
AnalyticsPostHog
PWAnext-pwa (service worker)

Route Structure

All user-facing pages live under src/app/:

Auth Routes ((auth)/)

These pages are accessible without a session:

  • /login — login form with role selector and OTP modal
  • /create-password — password creation after email invite
  • /forgot-password — password reset flow

Authenticated Routes ((authenticated)/)

These are protected by middleware — a valid session is required:

RoutePurpose
/account-setupMulti-step onboarding wizard (profile, ID, banking, escrow, final)
/casesCase dashboard — list of all user’s cases
/case/[caseId]Case detail — payments, DRs, SPC calendar, ledger, messages
/case/[caseId]/messagesMessage thread list for a case
/case/[caseId]/messages/[threadId]Single message thread
/case/[caseId]/disbursementsDisbursement request list
/case/[caseId]/disbursements/addCreate new disbursement request
/case/[caseId]/disbursements/[dr]DR detail
/case/[caseId]/funding-optionsFunding sources (e-check, card)
/case/[caseId]/paystubsLost wages paystubs
/case/[caseId]/spcScheduled payment calendar
/escrow-agreement/[caseId]Escrow agreement signing flow
/notificationsNotification history
/profileUser profile
/profile/editEdit personal information
/profile/bankingACH form (US and international)
/profile/verified-idsPhoto ID upload
/profile/2faTwo-factor authentication setup
/profile/agencyAgency information (agency users)
/profile/languageLanguage preferences
/profile/notificationsNotification settings

Component Organization

src/components/
├── common/ # Primitive UI: STInput, STCard, STCurrencyInput, OTPInput, LoadingSpinner, etc.
├── ui/ # shadcn/ui components (button, dialog, drawer, form, table, tabs, etc.)
├── layouts/ # Layout wrappers (UserHeader with back button + title)
├── case/ # Case-specific components (CaseDetails, drawer actions)
├── drawers/ # Slide-out drawer panels
├── modals/ # Dialog modals
├── profile/ # Profile-related components
└── wizard/ # Account setup wizard steps

Custom SeedTrust primitives (common/):

  • STInput, STCheckbox, STCard — basic form/UI
  • STCurrencyInput — formatted currency entry
  • STPhoneInput — phone number with formatting
  • STAvatar — user avatar display
  • AddressInput — address autocomplete
  • InstallPWADialog — PWA install prompt

Custom Hooks

HookWhat It Does
useActiveCasesFetches cases list with filters (case_name, type, stage, archived, pregnancy_status, page) and sorting
useFetchSelfFetches current user profile (GET /api/user/me)
useMessagesFetches all message threads
usePushNotificationsManages Web Push subscription lifecycle (register, subscribe, unsubscribe)
use-toastGlobal toast notification state (reducer-based, max 1 toast at a time)

All data-fetching hooks return a consistent tuple: [data | null, isLoading: boolean, errorMessage: string].


Data Fetching Pattern

The app uses a hybrid approach:

Server Components use the fetch wrapper (src/api/fetch.ts):

  • Runs on the server, automatically injects the session’s Bearer token
  • Used for initial page data (case detail, user profile)
  • Returns null on 4xx errors — handle defensively

Client Components use Axios + React Query (src/api/axiosClient.ts):

  • Used for interactive, user-triggered data fetching
  • React Query provides caching, background refresh, and optimistic updates
  • Mutations invalidate relevant query keys on success

Direct fetch is used for S3 file uploads — the app requests a presigned URL from the backend, then uploads directly to S3.


Global State

React Query handles all server state. There is no Redux or Zustand.

EscrowAgreementContext is the only significant React Context:

  • Tracks whether a user needs to sign their escrow agreement
  • On dashboard load, checks all cases for unsigned agreements
  • Automatically navigates to the signing flow on first load after account setup
  • Uses sessionStorage to avoid re-triggering once dismissed

PWA Configuration

The app is a Progressive Web App:

  • Service worker is registered via next-pwa
  • Auto-skip-waiting enabled (new service worker activates immediately)
  • PWA is disabled in development mode
  • Install prompt is handled by InstallPWADialog

Analytics & Privacy

PostHog analytics are initialized in RootProviders. Privacy settings:

  • All form inputs are masked in session replay
  • Images, videos, and iframes are blocked from recording
  • Session recording only enabled with explicit user consent
  • PostHog pageview capture uses a custom handler (not the default)

Error Handling

LayerHow Errors Are Handled
401 from AxiosGlobal interceptor: resets PostHog, calls signOut()
401 from fetch wrapperRedirects to /login
Token refresh failureSets tokenError in session; user remains logged in but API calls fail
Push notification errors4xx → unsubscribe and clean up; 5xx → keep subscription
Query errorsExposed via hook tuple errorMessage; shown as toast notifications

Gotchas for Developers

Two API clients exist — use the right one. axiosClient.ts is for client-side interactive data. fetch.ts is for server-side initial data. Mixing them up causes hydration issues or missing auth headers.

tokenError in the session is not an automatic sign-out. If the refresh token fails, the user stays logged in but all API calls will fail. The TODO comment in auth.ts notes this needs automatic sign-out — it is not yet implemented.

The escrow agreement context fires on dashboard load. Any component that mounts on the cases dashboard may trigger an escrow agreement redirect. This is intentional but can cause unexpected navigation during testing.

PWA is disabled in dev. next-pwa is disabled when NODE_ENV=development. Push notifications and service worker features will not work locally unless you build and serve the app in production mode.


Open Questions

1. Automatic sign-out on token expiry The auth.ts TODO for automatic sign-out should be weighed against the Flask consolidation path. Until the auth split is reduced, users with expired refresh tokens stay logged in until the next API call returns a 401 and the axiosClient interceptor signs them out.