====== Frontend Documentation ====== Full reference documentation for the Next.js 15 frontend of the Attorney Vendor Portal. For the backend documentation, see ''attorney-vendor-portal-backend/docs/start.txt'' (portal index) and ''attorney-vendor-portal-backend/docs/backend.txt'' (backend reference). ---- ===== Tech Stack ===== ^ Component ^ Technology ^ | Framework | Next.js 15.5.3 (App Router, standalone output) | | Language | TypeScript 5 (strict mode) | | UI Library | React 19.1.0 | | Styling | Tailwind CSS 4 | | Component Library | shadcn/ui (New York style), Radix UI primitives | | Icons | Lucide React | | Data Fetching | TanStack React Query (''@tanstack/react-query'') | | Tables | TanStack Table (''@tanstack/react-table'') | | Forms | React Hook Form + Zod validation | | Rich Text Editor | Tiptap (''@tiptap/react'', ''@tiptap/starter-kit'') | | Charts | Recharts | | Date Utilities | date-fns | | PDF Generation | jsPDF | | Excel Export | xlsx | | Unit Testing | Vitest + React Testing Library | | E2E Testing | Playwright | | Package Manager | npm | | Node Version | 20 (Docker) | ---- ===== Directory Structure ===== app/ (auth)/ Public authentication pages (no login required) login/ Login page registration/ Registration via invitation token forgot-password/ Password reset request password-reset/[token]/ Password reset with token verify-email/ Email verification page (dashboard)/ Protected pages (wrapped by RouteGuard + DashboardLayout) cases/ Cases list page cases/[caseId]/ Case detail page (tabs: details, notes, hearings, related, assignments) dashboard/ Dashboard page (Phase 1: hidden) hearings/[hearingId]/ Hearing detail page settings/ User settings page users/ Users list page (requires canManageUsers) users/[id]/ User detail page api/ Next.js API routes cases/ Cases API (mock support) cases/[caseId]/ Case detail API health/ Health check endpoint logs/ Log forwarding endpoint logs/batch/ Batch log forwarding endpoint warmup/ Warmup endpoint (cold start prevention) service-unavailable/ Backend-down error page not-found/ 404 error page layout.tsx Root layout (providers, fonts, metadata) page.tsx Root page (redirects to /login) components/ cases/ Case-related components hearings/ Hearing components notes/ Note/rich-text components users/ User components ui/ shadcn/ui base components contexts/ React context providers hooks/ api/ API data-fetching hooks (React Query) lib/ Utilities and API client types/ TypeScript type definitions ---- ===== Route Structure ===== ==== Public Routes (No Authentication) ==== ^ Route ^ Page Component ^ Description ^ | ''/login'' | ''login/page.tsx'' | Username + password login form | | ''/registration'' | ''registration/page.tsx'' | Invitation-based registration | | ''/forgot-password'' | ''forgot-password/page.tsx'' | Request password reset email | | ''/password-reset/[token]'' | ''password-reset/[token]/page.tsx'' | Set new password with token | | ''/verify-email'' | ''verify-email/page.tsx'' | Email verification status page | | ''/service-unavailable'' | ''service-unavailable/page.tsx'' | Backend unreachable error page | | ''/not-found'' | ''not-found/page.tsx'' | 404 error page | ==== Protected Routes (Authentication Required) ==== All wrapped by ''RouteGuard'' and ''DashboardLayout'' (sidebar + header). ^ Route ^ Page Component ^ Description ^ | ''/cases'' | ''cases/page.tsx'' | Case list with search, filters, pagination | | ''/cases/[caseId]'' | ''cases/[caseId]/page.tsx'' | Case detail with tabbed view | | ''/dashboard'' | ''dashboard/page.tsx'' | Dashboard (Phase 1: hidden in nav) | | ''/hearings/[hearingId]'' | ''hearings/[hearingId]/page.tsx'' | Hearing detail | | ''/settings'' | ''settings/page.tsx'' | User settings | | ''/users'' | ''users/page.tsx'' | User list (admin/manager only) | | ''/users/[id]'' | ''users/[id]/page.tsx'' | User detail | ==== Next.js API Routes ==== ^ Route ^ Purpose ^ | ''/api/cases'' | Proxies to backend or returns mock data (when ''NEXT_PUBLIC_USE_CASES_MOCK=1'') | | ''/api/cases/[caseId]'' | Case detail proxy/mock | | ''/api/health'' | Frontend health check (returns 200) | | ''/api/logs'' | Forwards frontend log entries to backend | | ''/api/logs/batch'' | Forwards batched log entries to backend | | ''/api/warmup'' | Warmup endpoint to prevent cold starts | ---- ===== Components ===== ==== Core Components ==== ^ Component ^ File ^ Description ^ | ''RouteGuard'' | ''components/RouteGuard.tsx'' | Wraps protected routes; checks auth, redirects to /login | | ''DashboardLayout'' | ''components/DashboardLayout.tsx'' | Main layout with sidebar navigation, header, breadcrumbs | | ''QueryProvider'' | ''components/QueryProvider.tsx'' | Wraps app with React Query ''QueryClientProvider'' | | ''BackendHealthBanner'' | ''components/BackendHealthBanner.tsx'' | Shows warning banner when backend is unreachable | | ''BackendHealthGuard'' | ''components/BackendHealthGuard.tsx'' | Redirects to /service-unavailable when backend is down | | ''RestrictedAccess'' | ''components/RestrictedAccess.tsx'' | 403 forbidden page component | | ''DataTable'' | ''components/DataTable.tsx'' | Reusable data table built on TanStack Table | | ''FeedbackDialog'' | ''components/FeedbackDialog.tsx'' | Modal dialog for submitting user feedback | | ''ThemeToggle'' | ''components/ThemeToggle.tsx'' | Light/dark/system theme switcher | ==== Case Components ==== Located in ''components/cases/'': ^ Component ^ Description ^ | ''BasicFilters'' | Basic filter UI (status, vendor, date range, etc.) | | ''BasicFilterSection'' | Filter section wrapper with collapsible groups | | ''CaseBasicSearch'' | Case search input with debounce | | ''CaseDetailsTab'' | Case detail tab (client info, debt info, case metadata) | | ''CaseHearingsTab'' | Hearings tab within case detail | | ''CaseNotesTab'' | Notes tab within case detail | | ''CaseRelatedCasesTab'' | Related cases tab within case detail | | ''CaseSearchMention'' | Case search with @-mention support | | ''CaseSearchTabs'' | Tab container for case search modes | | ''CaseUserAssignmentsTab'' | User assignments tab within case detail | | ''FilterButtonGroup'' | Group of filter toggle buttons | | ''FilterDateRange'' | Date range picker filter | | ''FilterRangeSlider'' | Numeric range slider filter | ==== Hearing Components ==== Located in ''components/hearings/'': ^ Component ^ Description ^ | ''AssignAttorneysModal'' | Modal for assigning attorneys to a hearing | ==== Note Components ==== Located in ''components/notes/'': ^ Component ^ Description ^ | ''NotePreviewCard'' | Card preview of a note with truncated content | | ''RichTextEditor'' | Full Tiptap rich text editor for creating/editing notes | | ''SimpleTextEditor'' | Simplified plain text editor alternative | ==== User Components ==== Located in ''components/users/'': ^ Component ^ Description ^ | ''UserFilters'' | Filter controls for the users list | ==== UI Components (shadcn/ui) ==== Located in ''components/ui/''. These are base components from the shadcn/ui library: ''alert'', ''avatar'', ''badge'', ''button'', ''calendar'', ''card'', ''checkbox'', ''collapsible'', ''command'', ''confirm-modal'', ''dialog'', ''dialog-simple'', ''dropdown-menu'', ''dropdown-menu-simple'', ''dynamic-charts'', ''form'', ''input'', ''label'', ''loading'', ''popover'', ''progress'', ''scroll-area'', ''select'', ''select-simple'', ''separator'', ''sheet'', ''skeleton'', ''slider'', ''switch'', ''table'', ''tabs'', ''tabs-simple'', ''textarea'' ---- ===== API Client ===== **File:** ''lib/api-client.ts'' The API client is the single point of contact between the frontend and the Laravel backend. ==== Configuration ==== * **Base URL:** ''NEXT_PUBLIC_API_URL'' environment variable * **Timeout:** 75 seconds * **Content type:** ''application/json'' (default) ==== Authentication ==== * Reads token from ''localStorage'' under key ''auth_token'' * Attaches ''Authorization: Bearer '' header to every request * On **401 response**: clears token, sets global ''has401Error'' flag, redirects to ''/login'' ==== Features ==== * **Request deduplication:** GET requests to the same URL are deduplicated (only one in-flight request per URL) * **Backend health tracking:** Detects 502/503/504 and network errors, notifies ''BackendHealthContext'' * **Error sanitization:** Wraps errors into structured ''ApiError'' objects * **Abort support:** Requests can be cancelled via ''AbortController'' ==== Helper Functions ==== ^ Function ^ Description ^ | ''get(url, params?)'' | GET request with optional query parameters | | ''post(url, data?)'' | POST request with JSON body | | ''put(url, data?)'' | PUT request with JSON body | | ''patch(url, data?)'' | PATCH request with JSON body | | ''del(url)'' | DELETE request | | ''postFormUrlEncoded(url, data)'' | POST with ''application/x-www-form-urlencoded'' | | ''postForm(url, formData)'' | POST with ''multipart/form-data'' | | ''deduplicatedGet(url, params?)'' | GET with in-flight request deduplication | ---- ===== Custom Hooks ===== ==== API Hooks ==== Located in ''hooks/api/''. All hooks use TanStack React Query for caching, background refetch, and optimistic updates. === useCases (hooks/api/useCases.ts) === ^ Hook ^ Type ^ Description ^ | ''useCases(filters)'' | Query | Fetch paginated, filtered case list | | ''useCaseDetail(id)'' | Query | Fetch single case detail | | ''useRelatedCases(id)'' | Query | Fetch related cases for a given case | | ''useCreateCase()'' | Mutation | Create a new case | | ''useUpdateCase()'' | Mutation | Update an existing case | | ''useDeleteCase()'' | Mutation | Delete a case | === useHearings (hooks/api/useHearings.ts) === ^ Hook ^ Type ^ Description ^ | ''useHearings(filters)'' | Query | Fetch paginated hearing list | | ''useHearingAttorneys(id)'' | Query | Fetch attorneys for a hearing | | ''useHearingDetail(id)'' | Query | Fetch hearing detail | | ''useAssignHearingAttorneys()'' | Mutation | Assign attorneys to a hearing | | ''useSetMainAttorney()'' | Mutation | Set the main attorney for a hearing | | ''useCancelHearing()'' | Mutation | Cancel a hearing | | ''useDeleteHearing()'' | Mutation | Delete a hearing | | ''useUpdateHearingOptions()'' | Mutation | Update hearing options | === useUsers (hooks/api/useUsers.ts) === ^ Hook ^ Type ^ Description ^ | ''useUsers(filters)'' | Query | Fetch paginated user list | | ''useUser(id)'' | Query | Fetch single user detail | === useNotes (hooks/api/useNotes.ts) === ^ Hook ^ Type ^ Description ^ | ''useCaseNotes(caseId)'' | Query | Fetch notes for a case | | ''useCreateNote()'' | Mutation | Create a note on a case | | ''useUpdateNote()'' | Mutation | Update a note | | ''useDeleteNote()'' | Mutation | Delete a note | === useRoles (hooks/api/useRoles.ts) === ^ Hook ^ Type ^ Description ^ | ''useRoles()'' | Query | Fetch available roles | | ''useMyVendors()'' | Query | Fetch user's accessible vendors | | ''useCheckPermission()'' | Mutation | Check if user has a permission | | ''useAssignRole()'' | Mutation | Assign a role to a user | | ''useRemoveAccess()'' | Mutation | Remove user's vendor access | === useMe (hooks/api/useMe.ts) === ^ Hook ^ Type ^ Description ^ | ''useMe()'' | Query | Fetch authenticated user's profile via ''/api/me'' | === useRegistration (hooks/api/useRegistration.ts) === ^ Hook ^ Type ^ Description ^ | ''useVerifyToken()'' | Mutation | Verify an invitation token | | ''useCompleteRegistration()'' | Mutation | Complete registration with password | | ''useResendVerification()'' | Mutation | Resend email verification | === useInvitations (hooks/api/useInvitations.ts) === ^ Hook ^ Type ^ Description ^ | ''useInviteUser()'' | Mutation | Send invitation to a new user | | ''useResendInvitation()'' | Mutation | Resend invitation email | | ''useGenerateInviteLink()'' | Mutation | Generate a one-time invite link | === useUserAssignments (hooks/api/useUserAssignments.ts) === ^ Hook ^ Type ^ Description ^ | ''useUserAssignments(filters)'' | Query | Fetch user assignments | | ''useCaseUserAssignments(caseId)'' | Query | Fetch assignments for a specific case | === useUserLookup (hooks/api/useUserLookup.ts) === ^ Hook ^ Type ^ Description ^ | ''useUserLookup(query)'' | Query | Search/lookup users by name or email | === useFeedback (hooks/api/useFeedback.ts) === ^ Hook ^ Type ^ Description ^ | ''useSubmitFeedback()'' | Mutation | Submit user feedback | ==== Utility Hooks ==== ^ Hook ^ File ^ Description ^ | ''useRole()'' | ''hooks/useRole.ts'' | Returns current user's role info and permission helpers | | ''useVendorPermission()'' | ''hooks/useRole.ts'' | Checks if user has a specific permission for a vendor | | ''useDataTable()'' | ''hooks/useDataTable.ts'' | Manages TanStack Table state (sorting, filtering, pagination) | ---- ===== State Management ===== The frontend uses React Context for global state and TanStack React Query for server state. ==== AuthContext ==== **File:** ''contexts/AuthContext.tsx'' Manages authentication state across the app. ^ Property / Method ^ Description ^ | ''user'' | Current user object (or ''null'') | | ''isAuthenticated'' | Boolean -- whether user has a valid token | | ''isLoading'' | Boolean -- whether auth state is being determined | | ''login(credentials)'' | Sends login request, stores token, loads profile | | ''logout()'' | Clears token, clears cache, redirects to /login | | ''setAuthToken(token)'' | Stores token in localStorage | | ''clearAuthToken()'' | Removes token from localStorage | Internally uses a React Query ''profileQuery'' to fetch ''/api/me'' and keep user data fresh. The query is disabled when no token is present, on public routes, or after a 401 error. ==== BackendHealthContext ==== **File:** ''contexts/BackendHealthContext.tsx'' Tracks whether the backend API is reachable. ^ Property / Method ^ Description ^ | ''isBackendDown'' | Boolean -- true after 2 consecutive failures | | ''reportFailure()'' | Called by API client on 502/503/504 or network error | | ''reportSuccess()'' | Called by API client on successful response | * **Failure threshold:** 2 consecutive failures before marking backend as down * **Auto-reset:** Clears ''isBackendDown'' after 30 seconds to allow retry * **Global handlers:** Exposes functions for non-React code (API client) ==== ErrorContext ==== **File:** ''contexts/ErrorContext.tsx'' Manages restricted access (403) state. ^ Property / Method ^ Description ^ | ''isRestricted'' | Boolean -- true when user lacks permission | | ''setRestricted(bool)'' | Set restricted state | | ''restrictedMessage'' | Optional message to display | ==== ThemeContext ==== **File:** ''contexts/ThemeContext.tsx'' Manages light/dark/system theme preference. ^ Property / Method ^ Description ^ | ''theme'' | Current theme: ''light'', ''dark'', or ''system'' | | ''setTheme(theme)'' | Updates theme and persists to localStorage | | ''resolvedTheme'' | Actual applied theme (resolves ''system'' to light/dark) | * Persists to ''localStorage'' under a theme key * Detects system preference via ''prefers-color-scheme'' media query * Applies theme class to '''' element for Tailwind dark mode ---- ===== TypeScript Types ===== **File:** ''types/api.ts'' All API request/response types are centralized in this file. ==== Authentication Types ==== * ''User'' -- User object with vendor accesses * ''LoginRequest'', ''LoginResponse'' * ''SignupRequest'', ''SignupResponse'' * ''ForgotPasswordRequest'' * ''ResetPasswordRequest'' * ''ChangePasswordRequest'' * ''CompleteRegistrationRequest'', ''CompleteRegistrationResponse'' ==== Case Types ==== * ''CaseRecord'' -- Case with visible/hidden field groups * ''CasesPagination'', ''CasesPaginationLink'' * ''CasesApiResponse'' * ''CaseDetailResponse'' * ''CasesFilters'' -- Query parameters for case search * ''BasicFilters'' -- Simplified filter set ==== Hearing Types ==== * ''Hearing'', ''HearingsApiResponse'' * ''HearingDetail'', ''HearingDetailResponse'' * ''HearingDetailCase'', ''HearingDetailClient'', ''HearingDetailAttorney'' * ''AssignHearingAttorneysResponse'', ''SetMainAttorneyResponse'' * ''CancelHearingResponse'', ''DeleteHearingResponse'' * ''UpdateHearingOptionsResponse'', ''UpdateHearingOptionsVariables'' ==== Role & Permission Types ==== * ''Role'', ''RoleHierarchy'' * ''RolesResponse'' * ''VendorInfo'', ''VendorAccess'' * ''MyVendorsResponse'' * ''CheckPermissionRequest'', ''CheckPermissionResponse'' * ''AssignRoleRequest'', ''AssignRoleResponse'' * ''RemoveAccessResponse'' ==== Note Types ==== * ''Note'', ''NotesPagination'' * ''NotesListResponse'' * ''CreateNoteRequest'', ''UpdateNoteRequest'' * ''NoteMutationResponse'', ''DeleteNoteResponse'' ==== User Types ==== * ''UserRecord'', ''UsersPagination'', ''UsersApiResponse'' * ''UsersFilters'' ==== Invitation Types ==== * ''InviteUserRequest'', ''InviteUserResponse'' * ''VerifyTokenRequest'', ''VerifyTokenResponse'' ==== User Assignment Types ==== * ''UserAssignment'', ''UserAssignmentsPagination'' * ''UserAssignmentsApiResponse'', ''UserAssignmentsFilters'' ==== Preset Types ==== * ''PresetApiRecord'', ''PresetApiResponse'' * ''CreatePresetRequest'' ==== Feedback Types ==== * ''FeedbackRequest'', ''FeedbackResponse'' ==== Error Types ==== * ''ApiError'' -- Structured API error with status, message, and optional field errors * ''ApiResponse'' -- Generic wrapper for API responses ---- ===== Route Protection ===== The frontend uses a **deny-by-default** approach with three layers of protection. ==== Layer 1: Next.js Middleware ==== **File:** ''middleware.ts'' * Runs on every request before rendering * Maintains a whitelist of public routes: ''/login'', ''/registration'', ''/forgot-password'', ''/password-reset'', ''/verify-email'', ''/not-found'', ''/service-unavailable'', ''/health'', ''/api/health'', ''/api/warmup'' * Redirects root (''/'') to ''/login'' * Excludes: API routes, Next.js internals (''/_next/''), static assets, favicon * **Limitation:** Cannot access ''localStorage'' (server-side), so token validation happens in Layer 2 ==== Layer 2: RouteGuard Component ==== **File:** ''components/RouteGuard.tsx'' * Wraps all ''(dashboard)'' routes * Checks ''isAuthenticated'' from ''AuthContext'' * Shows loading spinner while auth state is being determined * Redirects unauthenticated users to ''/login?redirect='' * Redirects authenticated users away from ''/login'' to ''/cases'' ==== Layer 3: Permission-Based UI ==== * Individual pages and components check permissions via ''useRole()'' and ''useVendorPermission()'' * Example: ''/users'' page only accessible to users with ''canManageUsers'' permission * UI elements hidden/disabled based on role level ---- ===== Authentication Flow (Detailed) ===== ==== Login ==== - User enters username and password on ''/login'' - ''AuthContext.login()'' calls ''POST /api/login'' with credentials - Backend validates credentials, returns ''{ token, user }'' - Frontend stores token via ''setAuthToken()'' in ''localStorage'' - ''AuthContext'' updates user state - ''profileQuery'' refetches ''/api/me'' for complete user profile (with vendor accesses) - Redirect to ''/cases'' (or URL from ''redirect'' query parameter) ==== 401 Handling ==== - API client receives 401 response from any endpoint - Sets global ''has401Error'' flag - Clears token from ''localStorage'' - Prevents further React Query requests - Redirects to ''/login'' (if not already on a public route) ==== Logout ==== - ''AuthContext.logout()'' calls ''POST /api/logout'' to invalidate server token - Clears token and user state - Clears all React Query cache - Full page redirect to ''/login'' via ''window.location.replace()'' (avoids stale state) ==== Registration ==== - Admin creates invitation via ''POST /api/user/invitation'' - User receives email with invitation link containing token - User navigates to ''/registration?token='' - ''useVerifyToken()'' validates the token against backend - User sets password and submits registration form - ''useCompleteRegistration()'' calls ''POST /api/register'' - User must verify email before logging in ==== Password Reset ==== - User requests reset on ''/forgot-password'' page - ''POST /api/forgot-password'' sends reset email via SendGrid - User clicks link in email, arrives at ''/password-reset/[token]'' - User enters new password - ''POST /api/reset-password'' validates token and updates password - Backend may return a token (auto-login) or redirect to login ---- ===== Next.js Configuration ===== **File:** ''next.config.ts'' ^ Setting ^ Value ^ | ''output'' | ''standalone'' (optimized for Docker deployment) | | ''compress'' | ''true'' | | ''poweredByHeader'' | ''false'' (security: hides X-Powered-By) | | ''eslint.ignoreDuringBuilds'' | ''true'' | ==== Security Headers ==== Applied to all routes via ''headers()'' config: ^ Header ^ Value ^ | Strict-Transport-Security | ''max-age=31536000; includeSubDomains'' | | Content-Security-Policy | Default ''self'', restricted script/style sources | | X-Content-Type-Options | ''nosniff'' | | X-Frame-Options | ''DENY'' | | X-XSS-Protection | ''1; mode=block'' | ---- ===== Deployment ===== ==== Docker Configuration ==== **Dockerfile** (multi-stage build): - **Stage 1 (deps):** Install Node 20 dependencies - **Stage 2 (builder):** Build Next.js (''npm run build'') - **Stage 3 (runner):** Production image with standalone output, non-root user (''nextjs:nodejs''), health checks **Docker Compose** (''docker-compose.yml''): services: web-frontend: build: . image: attorney-vendor-portal-frontend ports: "3000:3000" environment: - PORT=3000 - HOSTNAME=0.0.0.0 ==== AWS Deployment ==== * Deployed as a container on AWS ECS / Fargate * Behind an Application Load Balancer (ALB) * Health checks on ''/api/health'' * Warmup endpoint at ''/api/warmup'' to prevent cold starts ---- ===== Local Development ===== ==== With Docker ==== cd attorney-vendor-portal-frontend docker compose build docker compose up # Available at http://localhost:3000 ==== Without Docker ==== cd attorney-vendor-portal-frontend npm install npm run dev # Available at http://localhost:3000 Requires the backend to be running at the URL specified by ''NEXT_PUBLIC_API_URL''. ==== Mock Mode ==== Run the frontend without a backend using mock case data: NEXT_PUBLIC_USE_CASES_MOCK=1 npm run dev This enables the mock API at ''/api/cases'' which returns static case data for development and testing. ---- ===== Testing ===== ==== Unit Tests (Vitest) ==== npm run test # Run all tests npm run test:watch # Watch mode npm run test:coverage # With coverage report * Uses Vitest as the test runner * React Testing Library for component testing * MSW (Mock Service Worker) for API mocking in tests ==== E2E Tests (Playwright) ==== npx playwright test # Run all E2E tests npx playwright test --ui # Interactive UI mode npx playwright show-report # View test report * Tests run against a running frontend instance * Covers critical user flows (login, case search, navigation) For more details, see ''tests/README.md'' in the frontend directory. ---- ===== Environment Variables ===== ^ Variable ^ Description ^ | ''NEXT_PUBLIC_API_URL'' | Backend API base URL (e.g., ''http://localhost:8000'') | | ''NEXT_PUBLIC_USE_CASES_MOCK'' | Set to ''1'' to enable mock case data | | ''PORT'' | Server port (default: ''3000'') | | ''HOSTNAME'' | Server hostname (default: ''0.0.0.0'' in Docker) | ---- ===== Related Documentation ===== * **Portal Index** -- ''attorney-vendor-portal-backend/docs/start.txt'' * **Backend Documentation** -- ''attorney-vendor-portal-backend/docs/backend.txt'' * **Backend API Reference** -- ''attorney-vendor-portal-backend/docs/index.txt'' (per-endpoint docs with request/response examples)