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
| Layer | Technology |
|---|---|
| Framework | Next.js 15, React 18 |
| Language | TypeScript |
| Styling | TailwindCSS |
| UI Components | shadcn/ui + custom SeedTrust components |
| Data Fetching | React Query (TanStack Query) |
| Auth | NextAuth.js (Credentials provider) |
| Analytics | PostHog |
| PWA | next-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:
| Route | Purpose |
|---|---|
/account-setup | Multi-step onboarding wizard (profile, ID, banking, escrow, final) |
/cases | Case dashboard — list of all user’s cases |
/case/[caseId] | Case detail — payments, DRs, SPC calendar, ledger, messages |
/case/[caseId]/messages | Message thread list for a case |
/case/[caseId]/messages/[threadId] | Single message thread |
/case/[caseId]/disbursements | Disbursement request list |
/case/[caseId]/disbursements/add | Create new disbursement request |
/case/[caseId]/disbursements/[dr] | DR detail |
/case/[caseId]/funding-options | Funding sources (e-check, card) |
/case/[caseId]/paystubs | Lost wages paystubs |
/case/[caseId]/spc | Scheduled payment calendar |
/escrow-agreement/[caseId] | Escrow agreement signing flow |
/notifications | Notification history |
/profile | User profile |
/profile/edit | Edit personal information |
/profile/banking | ACH form (US and international) |
/profile/verified-ids | Photo ID upload |
/profile/2fa | Two-factor authentication setup |
/profile/agency | Agency information (agency users) |
/profile/language | Language preferences |
/profile/notifications | Notification 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 stepsCustom SeedTrust primitives (common/):
STInput,STCheckbox,STCard— basic form/UISTCurrencyInput— formatted currency entrySTPhoneInput— phone number with formattingSTAvatar— user avatar displayAddressInput— address autocompleteInstallPWADialog— PWA install prompt
Custom Hooks
| Hook | What It Does |
|---|---|
useActiveCases | Fetches cases list with filters (case_name, type, stage, archived, pregnancy_status, page) and sorting |
useFetchSelf | Fetches current user profile (GET /api/user/me) |
useMessages | Fetches all message threads |
usePushNotifications | Manages Web Push subscription lifecycle (register, subscribe, unsubscribe) |
use-toast | Global 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
nullon 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
sessionStorageto 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
| Layer | How Errors Are Handled |
|---|---|
| 401 from Axios | Global interceptor: resets PostHog, calls signOut() |
| 401 from fetch wrapper | Redirects to /login |
| Token refresh failure | Sets tokenError in session; user remains logged in but API calls fail |
| Push notification errors | 4xx → unsubscribe and clean up; 5xx → keep subscription |
| Query errors | Exposed 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.