Component Guide - striae-org/striae GitHub Wiki
- Component Architecture Overview
- Component Directory Structure
- Core Components
- Component State Management
- Component Communication Patterns
- Styling Approach
- Performance Considerations
- Accessibility Features
- Development Guidelines
Striae's frontend is built using React components organized in a modular structure. This guide covers the major components, their purposes, and how they interact within the application.
app/components/
├── actions/ # Data handling and business logic modules
├── audit/ # Audit trail UI components
│ └── viewer/ # Refactored audit viewer sub-components and hooks
├── auth/ # Authentication components (provider, MFA)
├── button/ # Reusable button components
├── canvas/ # Main canvas for image annotation
│ ├── box-annotations/ # Interactive box annotation drawing system
│ └── confirmation/ # Authenticated confirmation modal
├── colors/ # Color picker components
├── form/ # Shared form/input primitives (BaseForm, FormField, etc.)
├── icon/ # SVG sprite icon system
├── mobile-warning/ # Desktop-only session warning dialog
├── navbar/ # Top navigation bar, case modals, and case import
│ ├── case-import/ # Case import modal and hooks (moved from sidebar)
│ └── case-modals/ # All-cases, open, rename, archive, delete, export modals
├── sidebar/ # Sidebar navigation and controls
│ ├── cases/ # Case-specific sidebar panel
│ ├── files/ # File list modal and delete modal
│ ├── notes/ # Notes editor, additional notes, item details
│ │ └── item-details/ # Item-specific measurement/feature forms
│ └── upload/ # Image upload drop zone
├── theme-provider/ # Theme management
├── toast/ # Toast notification system
├── toolbar/ # Main toolbar and color selector
└── user/ # User management and MFA components
Purpose: Top-level authentication context provider with session lifecycle management
Features:
- Wraps the application to provide
AuthContext(Firebase Auth state) - Manages inactivity timeout warnings and forced logouts via
useInactivityTimeouthook - Tracks session state with
remainingSecondscountdown for warning display - Renders
InactivityWarningmodal as a child of the provider - Audit logging on timeout logouts (includes
sessionId, duration,'timeout'logout reason) - On manual session extension: clears warning and resets inactivity timer
Key Props:
interface AuthProviderProps {
children: React.ReactNode;
}Integration Points:
- Consumes
auth.onAuthStateChangedfrom Firebase - Uses
useInactivityTimeouthook for timeout detection - Calls
auditService.logUserLogout()with timeout metadata on forced logout - Provides the
AuthContextconsumed by downstream components
Purpose: Multi-factor authentication setup with method selection
Features:
- Method selection screen: users choose between SMS (phone) or Authenticator App (TOTP)
- Phone number verification for SMS-based MFA
- TOTP enrollment via
MfaTotpEnrollmentfor authenticator app enrollment - SMS code validation and reCAPTCHA verification for phone path
- Phone number validation including US/Canada and international formats
- Forwards TOTP flow to
MfaTotpEnrollmentsub-component
Type Definition: Uses Firebase authentication types for MFA setup
Key Props:
-
user: User- Firebase user object -
onSuccess: () => void- Success callback -
onError: (message: string) => void- Error callback -
mandatory: boolean- Whether MFA is required -
onSkip?: () => void- Optional skip for non-mandatory scenarios
Purpose: MFA second-factor challenge during login
Features:
- Multi-factor resolver handling via Firebase
MultiFactorResolver - Method selection UI when the user has multiple enrolled factors
- SMS code flow: reCAPTCHA challenge, send/resend code, code validation
- TOTP code flow: 6-digit code entry and assertion via
TotpMultiFactorGenerator.assertionForSignIn - Audit logging for both successful and failed verification attempts (SMS and TOTP separately)
- Security violation audit events on failed TOTP or SMS code attempts
Type Definition: Uses Firebase MultiFactorResolver interface for MFA challenges
Purpose: TOTP authenticator app enrollment flow
Features:
- Generates a TOTP secret and QR code using
TotpMultiFactorGenerator.generateSecret - Renders a scannable QR code (
qrcodelibrary) for authenticator app setup - Optional display of the raw base32 secret key for manual entry
- Uses
useRefto persist the generated secret across re-renders and avoid regeneration - 6-digit verification code entry to confirm enrollment
- Enrolls the factor via
multiFactor(user).enroll(assertion, 'Authenticator App') - Audit logging on successful enrollment and security violation event on failure
Key Props:
-
user: User- Firebase user object -
onSuccess: () => void- Called when enrollment succeeds -
onError: (error: string) => void- Called on error -
onBack?: () => void- Optional back navigation callback
Purpose: Main image display and annotation interface
Features:
- High-resolution image rendering
- Annotation overlay display
- Loading states and error handling
- Flash effects for user feedback (subclass characteristics)
Type Definition: Uses AnnotationData interface from app/types/annotations.ts
Key Props:
interface CanvasProps {
imageUrl?: string;
filename?: string;
company?: string;
badgeId?: string;
firstName?: string;
error?: string;
activeAnnotations?: Set<string>;
annotationData?: AnnotationData | null;
onAnnotationUpdate?: (annotationData: AnnotationData) => void;
isBoxAnnotationMode?: boolean;
boxAnnotationColor?: string;
isReadOnly?: boolean;
isArchivedCase?: boolean;
caseNumber: string;
currentImageId?: string;
}State Management:
- Image loading states
- Error handling for network issues
- Flash effects for user feedback (subclass characteristics)
- Read-only and archived case mode handling
- Confirmation modal integration for authenticated verification
- Box annotation mode with color selection pass-through
Key Methods:
- Image load error detection
- Annotation overlay rendering
- User interaction handling
Purpose: Interactive box annotation drawing and management system
Features:
- Mouse-based box drawing with real-time visual feedback
- Percentage-based coordinate system for device independence
- Double-click and right-click removal functionality
- Hover effects with deletion indicators
- Transparent styling with colored borders
- Automatic saving integration with existing annotation system
- Label dialogs for annotation labeling with viewport boundary awareness
- Timestamp tracking for earliest annotation
- Audit logging integration for annotation operations
- Read-only mode support
Type Definition: Uses BoxAnnotation interface from app/types/annotations.ts
Key Props:
interface BoxAnnotationsProps {
imageRef: React.RefObject<HTMLImageElement | null>;
annotations: BoxAnnotation[];
onAnnotationsChange: (annotations: BoxAnnotation[]) => void;
isAnnotationMode: boolean;
annotationColor: string;
className?: string;
annotationData?: {
additionalNotes?: string;
earliestAnnotationTimestamp?: string;
};
onAnnotationDataChange?: (data: {
additionalNotes?: string;
boxAnnotations?: BoxAnnotation[];
earliestAnnotationTimestamp?: string;
}) => void;
isReadOnly?: boolean;
caseNumber: string;
imageFileId?: string;
originalImageFileName?: string;
}State Management:
- Drawing state tracking (isDrawing, startPosition, currentBox)
- Box annotation array management
- Real-time coordinate calculation and display
- Integration with toolbar visibility controls
Key Methods:
-
handleMouseDown: Initiates box drawing on mouse press -
handleMouseMove: Updates current box dimensions during drawing -
handleMouseUp: Finalizes box creation and triggers save -
handleDoubleClick/handleRightClick: Box removal functionality -
calculatePercentageCoordinates: Converts pixel coordinates to percentages
Purpose: Dynamic color selection interface for box annotations
Features:
- Preset color grid with common annotation colors
- Custom color wheel for precise color selection
- Confirm/cancel workflow with visual preview
- Automatic appearance when box annotation tool is active
- Keyboard Escape dismiss support
Type Definition: Uses component-specific ToolbarColorSelectorProps interface
Key Props:
interface ToolbarColorSelectorProps {
selectedColor: string;
onColorConfirm: (color: string) => void;
onCancel: () => void;
isVisible: boolean;
}Purpose: Authenticated confirmation modal for comparison image verification workflow
Features:
- Digital authentication with examiner credentials (badge ID and full name)
- Unique confirmation ID generation for audit trails
- Timestamp capture with human-readable and ISO formats
- View-only mode for existing confirmations
- Form validation requiring badge ID input
- Keyboard event handling (Escape key to close)
- Dual-mode operation: new confirmation creation or existing confirmation viewing
- Automatic population of user metadata (name, email, company)
- Integration with confirmation data structure for PDF export
Type Definition: Uses ConfirmationData interface from app/types/annotations.ts
Key Props:
interface ConfirmationModalProps {
isOpen: boolean;
onClose: () => void;
onConfirm?: (confirmationData: ConfirmationData) => void;
company?: string;
defaultBadgeId?: string;
existingConfirmation?: ConfirmationData | null;
}Confirmation Data Structure:
interface ConfirmationData {
fullName: string; // Confirming examiner's full name
badgeId: string; // Badge/ID number of confirming examiner
timestamp: string; // Human-readable confirmation timestamp
confirmationId: string; // Unique ID generated at confirmation time
confirmedBy: string; // User UID of the confirming examiner
confirmedByEmail: string; // Email of the confirming examiner
confirmedByCompany: string; // Company/Lab of the confirming examiner
confirmedAt: string; // ISO timestamp of confirmation
}State Management:
- Badge ID input tracking with validation
- Error state for invalid or missing inputs
- Confirmation processing state (loading/submitting)
- Automatic form reset on modal open
- Detection and display of existing confirmation data
Key Methods:
-
handleConfirm: Validates badge ID, generates confirmation data with unique ID, triggers callback -
handleOverlayClick: Closes modal when clicking outside content area -
formatTimestamp: Generates human-readable timestamp with full date and time -
generateConfirmationId: Creates unique confirmation identifier (via utility function)
UI Modes:
-
New Confirmation Mode:
- Editable badge ID field with autofocus
- Active confirm button when badge ID is provided
- Displays current user's credentials
- Generates new confirmation ID and timestamp
-
View Mode (when
existingConfirmationprovided):- All fields read-only
- Green banner indicating confirmed status
- Displays original confirming examiner's credentials
- Close button instead of confirm/cancel actions
- Shows preserved confirmation ID and timestamp
Security Features:
- Requires user authentication (Firebase user context)
- Captures user UID for confirmation attribution
- Generates unique IDs for confirmation tracking
- Immutable confirmation data once created
- ISO timestamp for precise temporal documentation
Integration Points:
- Connected to Image Notes modal/editor workflow for "Include confirmation" state
- Confirmation data stored within
AnnotationDatastructure - Exported in case data for reviewing examiner import
- Appears in PDF reports with full confirmation details
- Part of forensic workflow for independent verification
Workflow Context:
- Original examiner marks image with "Include confirmation" checkbox
- Case exported and transferred to reviewing examiner
- Reviewing examiner imports case in read-only mode
- Upon independent verification, clicks "Confirm" button
- This modal captures confirming examiner's credentials
- Confirmation data exported back to original examiner
- Original examiner imports confirmation and generates final report
Purpose: Main sidebar wrapper with footer integration
Features:
- Sidebar component orchestration
- Footer modal management ("About & Support" dialog with links to support, bug reporting, privacy policy, terms, security policy, and membership management)
- Keyboard event handling (Escape key)
- Upload status and case export action pass-through
- Notes view permission integration via centralized
getNotesViewPermission
Type Definition: Uses FileData interface, UserConfirmationSummaryDocument, and Firebase User type
Key Props:
interface SidebarContainerProps {
user: User;
onImageSelect: (file: FileData) => void;
onOpenCase: () => void;
imageId?: string;
currentCase: string;
files: FileData[];
setFiles: React.Dispatch<React.SetStateAction<FileData[]>>;
imageLoaded: boolean;
setImageLoaded: (loaded: boolean) => void;
showNotes: boolean;
setShowNotes: (show: boolean) => void;
onAnnotationRefresh?: () => void;
isReadOnly?: boolean;
isReviewOnlyCase?: boolean;
isArchivedCase?: boolean;
confirmationSaveVersion?: number;
isUploading?: boolean;
onUploadStatusChange?: (isUploading: boolean) => void;
onOpenCaseExport?: () => void;
initialConfirmationSummary?: UserConfirmationSummaryDocument;
}Purpose: Core sidebar functionality
Features:
- Contextual case/file side panel composition
- File upload and selection support
- Toast-driven workflow feedback
- Integration point for navbar-driven case/file action dialogs
Type Definition: Uses Firebase User type and FileData interface
Purpose: Case-specific sidebar functionality
Features:
- Case loading context and file operations
- File upload interface
- Image selection and deletion
- Trigger support for Image Notes modal/editor workflows
- Case validation and error handling
- Confirmation save version signaling for downstream confirmation status refreshes
Type Definition: Uses component-specific CaseSidebarProps interface with FileData and Firebase User types
Key Props:
interface CaseSidebarProps {
user: User;
onImageSelect: (file: FileData) => void;
onOpenCase: () => void;
imageLoaded: boolean;
setImageLoaded: (loaded: boolean) => void;
onNotesClick: () => void;
files: FileData[];
setFiles: React.Dispatch<React.SetStateAction<FileData[]>>;
currentCase: string | null;
isReadOnly?: boolean;
isArchivedCase?: boolean;
isConfirmed?: boolean;
confirmationSaveVersion?: number;
selectedFileId?: string;
isUploading?: boolean;
onUploadStatusChange?: (isUploading: boolean) => void;
onUploadComplete?: (result: { successCount: number; failedFiles: string[] }) => void;
onOpenCaseExport?: () => void;
}Purpose: Drag-and-drop file upload interface for adding images to a case
Features:
- Drag-and-drop zone with visual feedback
- File type validation (PNG, GIF, JPEG, WebP, SVG)
- Maximum file size enforcement (10 MB)
- Upload queue management with per-file progress tracking
- Permission checks before upload initiation
- Upload error display with auto-dismiss timeouts
- Read-only mode disables the upload zone
Type Definition: Uses component-specific ImageUploadZoneProps interface
Props:
interface ImageUploadZoneProps {
user: User;
currentCase: string | null;
isReadOnly: boolean;
canUploadNewFile: boolean;
uploadFileError: string;
onFilesChanged: (files: FileData[]) => void;
onUploadPermissionCheck?: (fileCount: number) => Promise<void>;
currentFiles: FileData[];
onUploadStatusChange?: (isUploading: boolean) => void;
onUploadComplete?: (result: { successCount: number; failedFiles: string[] }) => void;
}Purpose: Comprehensive case selection and management modal with inline CRUD operations
Features:
- Paginated case listing with sort/filter preferences (via
useCaseListPreferences) - Case selection with currently-active case highlighting
- Inline rename, archive, and delete via sub-modals (
RenameCaseModal,ArchiveCaseModal,DeleteCaseModal) - Confirmation status hydration per case — re-evaluated when
confirmationSaveVersionincrements - Loading states and error handling
- Keyboard navigation (Escape key)
- Delete button shows a context-sensitive
titletooltip per case state - Read-only review-only cases (imported for review) are blocked from deletion; the tooltip and inline message direct the user to use Clear RO Case under Case Management first. Regular cases and archived regular cases remain deletable.
Type Definition: Uses component-specific CasesModalProps interface
Props:
interface CasesModalProps {
isOpen: boolean;
onClose: () => void;
onSelectCase: (caseNum: string) => void;
currentCase: string;
user: User;
confirmationSaveVersion?: number; // Increment to trigger confirmation status refresh
}confirmationSaveVersion: An optional monotonically-increasing counter passed from the parent. When the value changes (for example after a confirmation is stored or imported), the modal re-fetches confirmation status for all listed cases to keep indicators current.
Purpose: File list dialog for the active case with selection, deletion, and confirmation status controls.
Features:
- Paginated file listing with sort/filter preferences (via
useFileListPreferences) - File selection with active-file highlighting
- Per-file confirmation status indicators — re-evaluated when
confirmationSaveVersionincrements - Item type labels displayed alongside file names when item data is present
- Batched multi-file deletion via
DeleteFilesModal - Read-only guard that disables delete controls for imported review cases
- Keyboard support (Escape to close)
Type Definition: Uses component-specific FilesModalProps interface
Props:
interface FilesModalProps {
isOpen: boolean;
onClose: () => void;
onFileSelect?: (file: FileData) => void;
currentCase: string | null;
files: FileData[];
setFiles: React.Dispatch<React.SetStateAction<FileData[]>>;
isReadOnly?: boolean;
selectedFileId?: string;
confirmationSaveVersion?: number; // Increment to trigger confirmation status refresh
}confirmationSaveVersion: Works identically to the CasesModal pattern. When the value changes the modal re-fetches per-file confirmation status to keep indicators current without a full page reload.
Purpose: Additional case notes editing modal
Features:
- Text area for detailed notes
- Save/cancel functionality
- Overlay dismiss handling through shared modal hook
- Toast-style success/error notification callbacks
- Temporary state management with save-pending state
Type Definition: Uses component-specific NotesModalProps interface
Props:
interface NotesModalProps {
isOpen: boolean;
onClose: () => void;
notes: string;
onSave: (notes: string) => void;
showNotification?: (message: string, type: 'success' | 'error' | 'warning') => void;
}Purpose: Captures item-specific measurement and feature data for Bullet, Cartridge Case, and Shotshell examinations across left/right comparison sides.
Features:
- Type-specific form sections rendered by selected item type (
BulletSection,CartridgeCaseSection,ShotshellSection) - Caliber dropdowns grouped by pistol/rifle categories with
SelectWithCustomFieldfallback for non-listed values - Boolean mark fields (extractor marks, ejector marks, chamber marks, etc.) rendered as labelled checkboxes
- Land/groove width arrays (L1..Ln, G1..Gn) linked to the
lgNumbercount for Bullet examinations - State managed by
useItemDetailsStatehook with typedBulletDetailsState,CartridgeCaseDetailsState,ShotshellDetailsStateinterfaces - On save,
buildItemDetailsSummary()generates plain-text summaries for the selected side and appends into side-specific notes state - Read-only mode disables all inputs when case is imported for review
Type Definition: Uses ItemDetailsModalProps with item-specific data sub-objects from app/types/annotations.ts
Props:
interface ItemDetailsModalProps {
isOpen: boolean;
onClose: () => void;
itemType: 'Bullet' | 'Cartridge Case' | 'Shotshell' | 'Other' | '';
bulletData?: BulletAnnotationData;
cartridgeCaseData?: CartridgeCaseAnnotationData;
shotshellData?: ShotshellAnnotationData;
onSave: (
bulletData: BulletAnnotationData | undefined,
cartridgeCaseData: CartridgeCaseAnnotationData | undefined,
shotshellData: ShotshellAnnotationData | undefined,
) => void;
showNotification?: (message: string, type: 'success' | 'error' | 'warning') => void;
isReadOnly?: boolean;
}Supporting modules:
| File | Purpose |
|---|---|
item-details/item-details-sections.tsx |
Renders BulletSection, CartridgeCaseSection, and ShotshellSection form layouts |
item-details/item-details-fields.tsx |
Reusable field components (SelectWithCustomField, boolean mark groups) |
item-details/use-item-details-state.ts |
Hook managing typed detail state + buildSaveData() helper |
item-details/item-details-shared.ts |
Option lists, constants, and buildItemDetailsSummary()
|
Integration in notes workflow:
The Item Details modal is invoked from notes-editor-form.tsx when the examiner clicks the item details button in the notes panel. When saved, the returned sub-objects are stored in AnnotationData.leftBulletData / .leftCartridgeCaseData / .leftShotshellData or their right-side counterparts based on selected side. The notes editor maintains side-specific notes (leftAdditionalNotes, rightAdditionalNotes) and a general additionalNotes field for compatibility and shared notes flows used by export and PDF rendering.
Purpose: Image-scoped notes editor shell for annotation-form interactions.
Features:
- Dedicated modal framing for image notes workflows
- Uses shared
useOverlayDismissbehavior for close interactions - Wraps
NotesEditorFormto keep form logic separate from modal container concerns - Supports optional notification callback for save/validation messaging
Props:
interface NotesEditorModalProps {
isOpen: boolean;
onClose: () => void;
currentCase: string;
user: User;
imageId: string;
originalFileName?: string;
onAnnotationRefresh?: () => void;
isUploading?: boolean;
showNotification?: (message: string, type: 'success' | 'error' | 'warning') => void;
}Purpose: Core form content for image-scoped ballistics annotation editing
Features:
- Case identification field display
- Left/right item identification and item type selection (Bullet, Cartridge Case, Shotshell, Other)
- Support level selection (Identification, Exclusion, Inconclusive)
- Indexing by color or number
- Additional notes modal integration with side-aware note fields and compatibility handling
- Item Details modal integration for measurement/feature data
- Confirmed image status tracking
- Loading state management for annotation data fetch
Type Definition: Uses component-specific NotesEditorFormProps interface
Props:
interface NotesEditorFormProps {
currentCase: string;
user: User;
imageId: string;
onAnnotationRefresh?: () => void;
originalFileName?: string;
isUploading?: boolean;
showNotification?: (message: string, type: 'success' | 'error' | 'warning') => void;
}Case Export Triggers (app/components/navbar/navbar.tsx, app/components/sidebar/cases/case-sidebar.tsx, app/components/navbar/case-modals/export-confirmations-modal.tsx, app/routes/striae/utils/case-export.ts)
Purpose: Current user-facing entry points for case-package export, archive re-export, and confirmation export workflows.
Features:
- Export is launched from the navbar
Case Managementmenu and from a quick-action button in the sidebar. - All export entry points call a single centralized
handleOpenCaseExport()function inapp/routes/striae/striae.tsxthat branches on case state. - The action label is context-driven based on case state:
-
Writable cases:
Export Case Package -
Archived regular read-only cases:
Export Archive -
Review-only imported cases:
Export Confirmations
-
Writable cases:
- When opening an archive re-export,
handleOpenCaseExport()immediately invokeshandleExport()(no modal) with{ includeBundledAuditTrail: true, useArchiveFileName: true }, which routes throughdownloadCaseAsZip()to the sharedbuildArchivePackage()builder. The resulting package is structurally identical to the initial archive package. - Selecting
Export ConfirmationsopensExportConfirmationsModal, which displays confirmed and unconfirmed image counts for the case and requires explicit confirmation before proceeding. - Action gating disables export while files are uploading or when no case is loaded. Archived regular read-only cases are no longer blocked from export.
- The sidebar export button (
case-sidebar.tsx) is visible for all read-only cases (both archived regular and review-only). Its label isExport Archivefor archived cases andExport Confirmationsfor review-only cases. -
app/routes/striae/utils/case-export.tslazy-loads the heavy case-export action module and provides progress labels for the toast/progress UI.
Integration:
- Writable case exports route to
downloadCaseAsZip(user, caseNumber, onProgress)(standard export path). - Archived case re-exports route to
downloadCaseAsZip(user, caseNumber, onProgress, { includeBundledAuditTrail: true, useArchiveFileName: true }), which delegates tobuildArchivePackage()incase-manage/archive-package-builder.ts. - Read-only review cases route to
exportConfirmationData(user, caseNumber)after the user confirms inExportConfirmationsModal. - The old
app/components/sidebar/case-export/modal flow no longer exists.
Purpose: Modal dialog for collecting the designated reviewer email before case export
Features:
- Email input for designated reviewing examiner
- Self-email export prevention (cannot export to own email)
- Email validation feedback
- Submit state management with loading indicator
- Overlay dismiss support
Type Definition: Uses component-specific ExportCaseModalProps interface
Props:
interface ExportCaseModalProps {
isOpen: boolean;
caseNumber: string;
currentUserEmail?: string;
isSubmitting?: boolean;
onClose: () => void;
onSubmit: (designatedReviewerEmail: string | undefined) => Promise<void>;
}Purpose: Modular ZIP package import system for reviewing complete case data in read-only mode
Architecture: Component composition pattern with custom hooks for business logic separation
Note: The case import module was relocated from app/components/sidebar/case-import/ to app/components/navbar/case-import/ as part of component reorganization. Import paths and barrel exports updated accordingly.
Directory Structure:
case-import/
├── case-import.tsx # Main orchestrator component
├── components/ # UI sub-components
│ ├── FileSelector.tsx # File selection interface
│ ├── CasePreviewSection.tsx # Case metadata preview
│ ├── ConfirmationPreviewSection.tsx # Import confirmation details
│ ├── ProgressSection.tsx # Real-time progress display
│ ├── ExistingCaseSection.tsx # Existing case management
│ └── ConfirmationDialog.tsx # Final confirmation modal
├── hooks/ # Custom business logic hooks
│ ├── useImportState.ts # State management hook
│ ├── useFilePreview.ts # File processing hook
│ └── useImportExecution.ts # Import execution hook
├── utils/ # Pure utility functions
│ └── file-validation.ts # File type validation
└── index.ts # Barrel exportComponent Architecture Features:
- Single Responsibility: Each component handles one specific aspect of the import process
- Custom Hooks Pattern: Business logic encapsulated in reusable hooks
- Component Composition: Main component orchestrates sub-components without complex logic
- Barrel Exports: Clean import structure through centralized index.ts
- Type Safety: Comprehensive TypeScript interfaces for all component interactions
Main Component Props:
interface CaseImportProps {
isOpen: boolean;
onClose: () => void;
onImportComplete?: (result: ImportResult | ConfirmationImportResult) => void;
}Custom Hooks:
- useImportState: Manages import progress, file selection, and UI state
- useFilePreview: Handles ZIP parsing, validation, and preview generation
- useImportExecution: Orchestrates the complete import process with progress callbacks
Import Process Features:
- ZIP File Selection: Modular file browser interface with validation
- Read-Only Case Review: Imported cases automatically protected from modification
- Progress Tracking: Real-time import progress with stage-by-stage updates
- Existing Case Detection: Automatic detection and management of existing read-only cases
- Image Integration: Automatic import and association of all case image data and annotations
- Metadata Preservation: Complete preservation of original case metadata and timestamps
- Clear Management: Option to remove imported cases from review bin
- Error Handling: Comprehensive error reporting with detailed failure messages
- Security Validation: Prevents import of cases where user was original analyst
- ZIP Validation: Comprehensive validation of ZIP package structure and contents
- Encrypted ZIP Validation: fail-closed validation of encrypted package structure and contents before import proceeds
- Duplicate Prevention: Prevents import if user was original case analyst
- Progress Callbacks: Multi-stage progress reporting (ZIP parsing, image upload, annotation import)
- Cleanup Operations: Automatic cleanup of existing case data when overwriting
- File Mapping: Internal mapping system for connecting imported images to annotation data
-
Confirmation Package Extraction: Specialized handling for encrypted/unencrypted confirmation ZIPs via
extractConfirmationImportPackageFromZip() - Integrity Validation: Optional case data integrity verification during import
- User Profile Integration: Automatic addition of imported cases to user's read-only case list
Purpose: Complete case lifecycle management, organized into focused modules.
Module Structure:
case-manage/
├── archive-package-builder.ts # Shared archive ZIP package builder
├── delete-helpers.ts # Per-file deletion helper (without audit emit)
├── operations.ts # Core case CRUD operations
├── types.ts # Shared interface definitions
├── utils.ts # Pure utility functions (validateCaseNumber, sortCaseNumbers, isReadOnlyCaseData)
└── index.ts # Barrel export
app/components/actions/case-manage.ts is a backward-compatible facade that re-exports everything from case-manage/index.ts. All existing import paths continue to work.
Key Functions:
export const validateCaseNumber = (caseNumber: string): boolean
export const checkExistingCase = async (
caseNumber: string,
user: User
): Promise<boolean>
export const createNewCase = async (
caseNumber: string,
user: User
): Promise<{ success: boolean; message: string }>
export const renameCase = async (
oldCaseNumber: string,
newCaseNumber: string,
user: User
): Promise<{ success: boolean; message: string }>
export const deleteCase = async (
caseNumber: string,
user: User
): Promise<{ success: boolean; message: string }>
export const archiveCase = async (
user: User,
caseNumber: string,
archiveReason?: string
): Promise<void>
export const getCaseArchiveDetails = async (
user: User,
caseNumber: string
): Promise<{
archived: boolean;
archivedAt?: string;
archivedBy?: string;
archivedByDisplay?: string;
archiveReason?: string;
}>
export const listCases = async (user: User): Promise<string[]>buildArchivePackage() (shared archive builder):
Source: case-manage/archive-package-builder.ts
Accepts a BuildArchivePackageInput containing user, caseNumber, caseJsonContent, files, auditConfig (startDate/endDate/additionalEntries), and readmeConfig (archivedAt/archivedByDisplay/archiveReason).
Steps performed:
- Fetch image blobs via signed URLs.
- Generate a signed forensic manifest.
- Embed the public signing key PEM.
- Query the audit trail for the case date range; include any
additionalEntries(e.g. the syntheticcase-archiveevent). - Generate a signed bundled audit trail (
signAuditExport); writeaudit/case-audit-trail.jsonandaudit/case-audit-signature.json. - Encrypt case data, image files, and audit files together as a single AES-256-GCM batch.
- Write
ENCRYPTION_MANIFEST.jsonandFORENSIC_MANIFEST.json. - Assemble the ZIP and return
{ zipBlob, publicKeyFileName, manifestSignatureKeyId }.
Returns the ZIP blob and associated metadata to the caller; the caller triggers the browser download and logs the audit event.
Features:
- Case number validation
- Duplicate case detection
- Case creation and deletion
- Case renaming functionality
- Case archival packaging with forensic manifest signing and bundled signed audit trail
- Shared archive package builder used by both initial archive and archived case re-export paths
- Archive metadata capture (
archivedAt,archivedBy,archivedByDisplay,archiveReason) - Confirmation summary cleanup on case delete/archive lifecycle events
- User case list management
Purpose: Modular encrypted case-package export system used by the current navbar-driven export flow.
Architecture: Organized into specialized modules for maintainability and testability
Directory Structure:
case-export/
├── core-export.ts # Collects case/file/annotation data into CaseExportData
├── download-handlers.ts # Builds encrypted ZIP packages and triggers download
├── metadata-helpers.ts # Resolves export metadata and README/forensic warnings
├── types-constants.ts # Shared filename/date helpers
├── validation-utils.ts # Case-number validation helpers
└── index.ts # Backward-compatible barrel exportModular Components:
- Core Export: Collects case data, files, notes, and summary metadata into a typed export payload.
- Download Handlers: Signs the forensic manifest, encrypts payloads/images, writes ZIP contents, and triggers the browser download.
- Metadata Helpers: Resolves user/export metadata and user-facing forensic warnings.
- Types & Constants: Shared filename/date helpers used by the packaging flow.
- Validation Utilities: Case-number validation and export prerequisites.
Key Functions:
export const exportCaseData = async (
user: User,
caseNumber: string,
options?: ExportOptions,
onProgress?: (current: number, total: number, label: string) => void
): Promise<CaseExportData>
export const downloadCaseAsZip = async (
user: User,
caseNumber: string,
onProgress?: (progress: number) => void,
options?: ExportOptions
): Promise<void>
export async function getUserExportMetadata(user: User): Promise<ExportMetadata>
export function validateCaseNumberForExport(caseNumber: string): { isValid: boolean; error?: string }
export function formatDateForFilename(date: Date): stringExport Options:
export interface ExportOptions {
includeMetadata?: boolean;
includeUserInfo?: boolean;
protectForensicData?: boolean;
includeBundledAuditTrail?: boolean; // Used for archive re-exports; bundles signed audit trail into ZIP
useArchiveFileName?: boolean; // Used for archive re-exports; applies archive-style filename
}Enhanced Features:
- Encrypted ZIP only: user-facing case export now produces encrypted case packages only; legacy unencrypted JSON/CSV/Excel export flows have been removed.
-
JSZip packaging: builds a structured ZIP containing encrypted case data, encrypted images,
FORENSIC_MANIFEST.json,ENCRYPTION_MANIFEST.json, a public signing-key PEM, and read-only instructions. - Mandatory encryption: export fails closed when no export-encryption public key is configured.
- Manifest signing: the package includes a server-signed forensic manifest before encryption metadata is written.
- Image packaging: fetches browser-accessible signed image URLs, downloads the blobs, and replaces them with encrypted payloads inside the ZIP.
-
Audit logging: successful and failed package exports are recorded through
auditServiceonce filename/format details are known. -
Route-level lazy loading:
app/routes/striae/utils/case-export.tsdefers loading this module until export is requested.
Purpose: Modular ZIP package import system for read-only case review and collaboration
Architecture: Organized into specialized modules for complex import operations
Directory Structure:
case-import/
├── orchestrator.ts # Main import orchestration function
├── validation.ts # Import validation and security checks
├── zip-processing.ts # ZIP file parsing and preview generation
├── storage-operations.ts # R2 storage and case management operations
├── image-operations.ts # Image upload and processing
├── annotation-import.ts # Annotation data import and mapping
├── confirmation-import.ts # Confirmation data import processing
├── confirmation-package.ts # Encrypted confirmation ZIP parsing and extraction
└── index.ts # Barrel exportModular Components:
-
Orchestrator: Main import workflow coordination (
importCaseForReview) - Validation: Security checks, exporter UID validation, hash verification
- ZIP Processing: Archive parsing, case data extraction, preview generation
- Storage Operations: R2 case storage, read-only case management, user profile updates
- Image Operations: Blob processing and upload to image worker
- Annotation Import: Complete annotation data mapping and storage
- Confirmation Import: Confirmation data processing with integrity validation
-
Confirmation Package: Encrypted/unencrypted confirmation ZIP extraction via
extractConfirmationImportPackageFromZip()
Key Functions:
// Main orchestrator
export const importCaseForReview = async (
user: User,
zipFile: File,
options: ImportOptions = {},
onProgress?: (stage: string, progress: number, details?: string) => void
): Promise<ImportResult>
// Validation functions
export const validateExporterUid = async (exporterUid: string, currentUser: User): Promise<{ exists: boolean; isSelf: boolean }>
export const validateConfirmationHash = (jsonContent: string, expectedHash: string): boolean
export const validateCaseIntegrity = (caseData: CaseExportData, imageFiles: { [filename: string]: Blob }): { isValid: boolean; issues: string[] }
// ZIP processing
export const previewCaseImport = async (zipFile: File): Promise<CaseImportPreview>
export const parseImportZip = async (zipFile: File): Promise<{ caseData: CaseExportData; imageFiles: { [filename: string]: Blob }; metadata?: any }>
// Storage operations
export const checkReadOnlyCaseExists = async (user: User, caseNumber: string): Promise<boolean>
export const addReadOnlyCaseToUser = async (user: User, metadata: ReadOnlyCaseMetadata): Promise<boolean>
export const storeCaseDataInR2 = async (user: User, caseNumber: string, caseData: CaseExportData): Promise<boolean>
export const listReadOnlyCases = async (user: User): Promise<ReadOnlyCaseMetadata[]>
export const deleteReadOnlyCase = async (user: User, caseNumber: string): Promise<boolean>
// Import operations
export const uploadImageBlob = async (blob: Blob, filename: string, user: User): Promise<string>
export const importAnnotations = async (user: User, caseNumber: string, fileAnnotations: any): Promise<boolean>
export const importConfirmationData = async (user: User, confirmationData: ConfirmationImportData): Promise<ConfirmationImportResult>Import Type System:
export interface ImportOptions {
overwriteExisting?: boolean;
validateIntegrity?: boolean;
preserveTimestamps?: boolean;
}
export interface ImportResult {
success: boolean;
caseNumber: string;
isReadOnly: boolean;
filesImported: number;
annotationsImported: number;
errors?: string[];
warnings?: string[];
}
export interface ReadOnlyCaseMetadata {
caseNumber: string;
importedAt: string;
originalExportDate: string;
originalExportedBy: string;
sourceHash?: string;
isReadOnly: true;
}
export interface CaseImportPreview {
caseNumber: string;
fileCount: number;
annotationCount: number;
exportDate: string;
exportedBy: string;
hasImages: boolean;
canImport: boolean;
warnings: string[];
}Core Import Features:
- Complete ZIP Package Import: Full case data and image import from exported ZIP packages
- Read-Only Protection: Imported cases are automatically set to read-only mode for secure review
- Duplicate Prevention: Prevents import if user was the original case analyst
- Archive Import UX Messaging: Archive/read-only conflict and self-import messaging is routed through centralized case-message constants to keep alert text consistent between preview and import execution flows
- Progress Tracking: Multi-stage progress reporting with detailed status updates
- Image Integration: Automatic upload and association of all case images
- Metadata Preservation: Complete preservation of original export metadata and timestamps
- Data Integrity: Comprehensive validation of ZIP contents and case data structure
- Forensic Warning Handling: Proper removal of forensic warnings for hash validation
- Confirmation Data Support: Specialized import handling for confirmation data files
- Error Recovery: Graceful handling of import failures with detailed error reporting
- Security Validation: Prevents modification of imported cases and restricts access appropriately
Purpose: Barrel re-export module providing backward-compatible access to case import/review functionality
Architecture: Re-exports all public functions from ./case-import to maintain existing import paths
Exports: All validation, ZIP processing, storage, image, annotation, confirmation, and orchestration functions from the case-import/ module. Consumers can import from either case-review or case-import/index interchangeably.
Purpose: Confirmation data storage, retrieval, and export with forensic integrity
Architecture: Specialized module for managing authenticated confirmation data in the forensic workflow
Key Functions:
// Store confirmation
export async function storeConfirmation(
user: User,
caseNumber: string,
currentImageId: string,
confirmationData: ConfirmationData,
originalImageFileName?: string
): Promise<boolean>
// Retrieve confirmations
export async function getCaseConfirmations(
user: User,
caseNumber: string
): Promise<CaseConfirmations | null>
export async function getImageConfirmations(
user: User,
caseNumber: string,
originalImageId: string
): Promise<ConfirmationData[]>
export async function getCaseDataWithManifest(
user: User,
caseNumber: string
): Promise<{ confirmations: CaseConfirmations | null; forensicManifestCreatedAt?: string }>
// Export confirmation data
export async function exportConfirmationData(
user: User,
caseNumber: string
): Promise<void>Core Features:
- Confirmation Storage: Stores authenticated confirmation data linked to original image IDs
- Multi-Confirmation Support: Multiple confirmations per image (multiple reviewing examiners)
- Original Image ID Mapping: Maps current image IDs to original export image IDs for traceability
- Forensic Data Export: Exports confirmations as JSON with SHA256 hash for integrity verification
- Metadata Integration: Includes user credentials, timestamps, and forensic manifest linking
- Case Data Integration: Confirmations stored within case data structure for unified management
- Audit Trail Logging: Comprehensive logging of all confirmation operations (creation, export, failures)
- Workflow Tracking: Integrated with audit service workflow system for forensic accountability
Data Structures:
interface CaseConfirmations {
[originalImageId: string]: ConfirmationData[];
}
interface CaseDataWithConfirmations extends CaseData {
confirmations?: CaseConfirmations;
forensicManifestCreatedAt?: string;
}
interface ConfirmationExportData {
metadata: {
caseNumber: string;
exportDate: string;
exportedBy: string;
exportedByUid: string;
exportedByName: string;
exportedByCompany: string;
totalConfirmations: number;
version: string;
originalExportCreatedAt?: string;
hash: string;
};
confirmations: CaseConfirmations;
}Export File Format:
-
Filename Pattern:
confirmation-data-{caseNumber}-{timestamp}.json -
Timestamp Format:
YYYYMMDD-HHMMSSin local timezone -
Content Type:
application/jsonwith pretty-printed formatting - Hash Algorithm: SHA-256 secure hash calculated from JSON payload
- Forensic Linking: Includes original export timestamp for traceability
Workflow Integration:
-
Confirmation Creation:
- Reviewing examiner confirms findings via Confirmation modal
-
storeConfirmationcalled with confirmation data - Original image ID mapping established
- Confirmation added to case data array
- Audit event logged with image metadata
-
Confirmation Retrieval:
- Original examiner retrieves confirmations via
getCaseConfirmations - Image-specific confirmations via
getImageConfirmations - Forensic manifest metadata via
getCaseDataWithManifest
- Original examiner retrieves confirmations via
-
Confirmation Export:
Export is triggered from the reviewing examiner's system. Complete confirmation data is collected with metadata, a SHA-256 hash is calculated for forensic integrity, and an encrypted confirmation ZIP package is downloaded with the hash and signature protected inside the payload. The audit trail logs file size and confirmation count.
- Import Process (handled by case-import module):
The original examiner imports an encrypted confirmation ZIP package. The hash is validated against the calculated hash, confirmation data is merged into the original case, and forensic linking is preserved via the original export timestamp.
Security Features:
- User Identity Validation: Captures confirming examiner's UID and email
- Tamper Detection: SHA-256 hash enables integrity verification
- Immutable Records: Confirmations cannot be modified once created
- Audit Trail: All operations logged for compliance and traceability
- Original Image Linking: Prevents confirmation misattribution via ID mapping
Error Handling:
- Validation of case existence before operations
- Original image ID lookup with fallback mechanisms
- Graceful handling of missing confirmation data
- Comprehensive error logging in audit trail
- User metadata retrieval with fallback defaults
Performance Considerations:
- Processing time tracked for all operations
- File size calculated for audit logging
- Minimal network calls via centralized data operations
- Efficient JSON serialization and hash calculation
Purpose: Image upload and retrieval operations
Key Functions:
export const fetchFiles = async (
user: User,
caseNumber: string,
options?: { skipValidation?: boolean }
): Promise<FileData[]>
export const uploadFile = async (
user: User,
caseNumber: string,
file: File,
onProgress?: (progress: number) => void
): Promise<FileData>
export const deleteFile = async (
user: User,
caseNumber: string,
imageId: string,
options?: DeleteFileOptions
): Promise<DeleteFileResult>
export const getImageUrl = async (
user: User,
caseNumber: string,
imageId: string
): Promise<string>Supporting Types:
export interface DeleteFileResult {
imageMissing: boolean;
fileName: string;
}
export interface DeleteFileOptions {
skipValidation?: boolean;
skipCaseDataUpdate?: boolean;
suppressAudit?: boolean;
}Features:
- Permission checks via
canUploadFilebefore upload operations - Audit logging for uploads and deletions
- Progress callbacks for upload status
- Case data synchronization on file changes
- Annotation cleanup on file deletion
getImageUrl() attempts the signed URL flow first by calling POST /api/image/{fileId}/signed-url. If signed URL minting fails, it falls back to authenticated blob retrieval and a temporary object URL so the viewer remains functional during rollout or partial configuration states.
getImageUrl() now attempts the signed URL flow first by calling POST /api/image/{fileId}/signed-url. If signed URL minting fails, it falls back to authenticated blob retrieval and a temporary object URL so the viewer remains functional during rollout or partial configuration states.
Purpose: PDF report generation
Features:
- Dynamic PDF creation
- Annotation integration
- Custom formatting
- Error handling and progress feedback
Key Function:
interface GeneratePDFParams {
user: User;
selectedImage: string | undefined;
sourceImageId?: string;
selectedFilename: string | undefined;
userCompany: string;
userFirstName: string;
userLastName: string;
userBadgeId: string;
currentCase: string;
annotationData: AnnotationData | null;
activeAnnotations: Set<string>;
setIsGeneratingPDF: (isGenerating: boolean) => void;
setToastType: (type: ToastType) => void;
setToastMessage: (message: string) => void;
setShowToast: (show: boolean) => void;
setToastDuration?: (duration: number) => void;
}
export const generatePDF = async (
params: GeneratePDFParams
): Promise<void>Features:
- PDF report generation via worker API
- Image URL resolution (blob, signed URL, and data URL formats)
- User metadata and annotation data formatting for report content
- Async state management with toast notifications for progress/completion
- Auto-generated filenames from annotation data or case number with sanitization
- Audit logging for successful and failed generation attempts
Purpose: Annotation and notes data management
Features:
- CRUD operations for annotation data
- Data validation and sanitization
- Error handling for API operations
Key Functions:
export const saveNotes = async (
user: User,
caseNumber: string,
imageId: string,
annotationData: AnnotationData,
options?: DataOperationOptions
): Promise<void>
export const getNotes = async (
user: User,
caseNumber: string,
imageId: string
): Promise<AnnotationData | null>These are thin wrappers around centralized data-operations.ts utility functions, providing built-in permission validation and error handling.
Purpose: User authentication logout
Features:
- Firebase sign out
- Local storage cleanup
- Redirect handling
- Error handling
Type Definition: Uses component-specific SignOutProps interface
Props:
interface SignOutProps {
redirectTo?: string;
disabled?: boolean;
}Purpose: Reusable button components
Components:
-
Button- Icon-based button with active/disabled states
Type Definition: Uses component-specific ButtonProps interface
Props:
interface ButtonProps {
iconId: string;
isActive?: boolean;
onClick?: () => void;
ariaLabel: string;
title?: string;
disabled?: boolean;
showSpinner?: boolean;
}Features:
- Icon-based rendering via SVG sprite system
- Active state with
aria-pressedtoggle - Optional spinner display for async operations
- Full accessibility support (aria-pressed, aria-label)
- Title tooltips
- Click handler blocking when disabled
Purpose: Color selection interface
Features:
- Predefined color palette
- Custom color wheel
- Color validation
- Real-time preview
Type Definition: Uses component-specific ColorSelectorProps interface
Props:
interface ColorSelectorProps {
selectedColor: string;
onColorSelect: (color: string) => void;
}Purpose: Shared form/input primitives used across authentication, profile, and modal forms
Components (via barrel export in index.ts):
-
BaseForm- Base form wrapper with consistent layout and submit handling -
FormField- Labeled input field with validation support -
FormMessage- Inline success/error/info message display -
FormButton- Styled form submit/action button -
FormToggle- Boolean toggle/switch input
Usage:
import { BaseForm, FormField, FormMessage, FormButton, FormToggle } from '~/components/form';Purpose: Centralized icon management
Type Definition: Uses custom icon type definitions for type-safe icon selection
Available Icons: number, item, index, id, box, notes, print, eye, eye-off
Props:
interface IconProps extends React.SVGProps<SVGSVGElement> {
icon: string;
className?: string;
size?: number; // default 24px
}Features:
- SVG sprite-based icon system using
icons.svgsymbol references -
forwardRefsupport for ref forwarding - Customizable size with default 24px
- Flexible className prop for styling overrides
Usage:
<Icon icon="eye" />
<Icon icon="box" size={20} />Purpose: Session-persistent desktop-only warning dialog
Features:
- Displays a modal warning that the application is designed for desktop use
- Dismissible via backdrop click or Escape key
- Stores dismissal state in
sessionStorageunder key'striae-mobile-warning-dismissed' - Re-shows on new sessions (not persistent across browser restarts)
- No props — renders based on internal session state
Purpose: User feedback and notifications
Type Definition: Uses component-specific ToastProps interface
Props:
export type ToastType = 'success' | 'error' | 'warning' | 'loading';
interface ToastProps {
message: ReactNode;
type: ToastType;
isVisible: boolean;
onClose: () => void;
duration?: number; // default 4000ms
}Features:
- Auto-dismiss via configurable
durationtimer (default 4 seconds) - Backdrop click to dismiss
- Type-specific icons (✓ success, ✗ error, ! warning, spinner loading)
- Close button
-
useOverlayDismisshook integration for consistent dismiss behavior
Purpose: Main application toolbar
Type Definition: Uses component-specific ToolbarProps interface and ToolId type
Props:
interface ToolbarProps {
onToolSelect?: (toolId: ToolId, active: boolean) => void;
onGeneratePDF?: () => void;
canGeneratePDF?: boolean;
onVisibilityChange?: (visible: boolean) => void;
isGeneratingPDF?: boolean;
onColorChange?: (color: string) => void;
selectedColor?: string;
isReadOnly?: boolean;
isConfirmed?: boolean;
isNotesOpen?: boolean;
}
type ToolId = 'number' | 'item' | 'index' | 'id' | 'notes' | 'print' | 'visibility' | 'box';Features:
- 8 tools: number, item, index, id, notes, print, visibility, box
- Toggle activation tracking with active tool state
- Auto-deactivate box tool when notes sidebar opens
- PDF generation controls with loading state
- Color selector integration for box annotation mode
- Read-only and confirmed modes disable certain tool actions
Purpose: Application theme management
Features:
- Theme context provision
- Theme persistence
- System theme detection
- Theme switching functionality
Type Definition: Uses custom Theme type definition from theme.ts
Theme Types (app/components/theme-provider/theme.ts):
type Theme = 'light' | 'dark' | 'system';Purpose: Comprehensive user profile management
Features:
- Profile information editing (display name)
- Email address viewing (read-only)
- Badge/ID number viewing (read-only)
- Company information viewing (read-only)
- Password change functionality
- User reauthentication
- Firebase integration
- Error handling with detailed messages
Type Definition: Uses component-specific ManageProfileProps interface
Props:
interface ManageProfileProps {
isOpen: boolean;
onClose: () => void;
}Key Features:
- Display name modification
- Badge/ID and company are treated as administrator-managed identity fields and cannot be edited in the client profile modal.
- Email address display (read-only)
- Badge/ID entered during registration is loaded back into the profile surface for confirmation, audit, and archive workflows.
- User permission status loading
- Account deletion functionality
- Firebase error handling integration
Purpose: Secure account deletion with permission-based restrictions
Features:
- User account deletion with confirmation
- Demo account protection (deletion disabled for
permitted=false) - Dual confirmation requirements (UID + email)
- Conditional messaging based on account type
- Email notifications on successful deletion
- Firebase authentication integration
- Automatic logout after deletion
Type Definition: Uses component-specific DeleteAccountProps interface with user object type
Props:
interface DeleteAccountProps {
isOpen: boolean;
onClose: () => void;
user: {
uid: string;
displayName: string | null;
email: string | null;
};
company: string;
}Security Features:
- Requires exact UID confirmation
- Requires exact email address confirmation
- API key authentication for deletion requests
- Email notifications on successful deletion
- Automatic logout after deletion
Purpose: Session timeout management
Features:
- Inactivity detection
- Warning countdown display
- Session extension handling
Type Definition: Uses custom hook types from useInactivityTimeout hook
Purpose: In-profile SMS MFA phone number update flow
Features:
- Displays current enrolled phone MFA status with masked phone number
- Handles re-authentication challenge before phone update (recent-login enforcement)
- Supports re-auth via password and, when needed, via MFA resolver (SMS challenge)
- Sends SMS verification code and validates the new phone number before enrolling
- Phone number validation with US/Canada and international format support
- Audit logging on successful phone MFA updates
- Independent reCAPTCHA container scoped to this component (
recaptcha-container-manage-profile)
Key Props:
-
user: User | null- Firebase user object -
isOpen: boolean- Whether the profile section is expanded -
onBusyChange?: (isBusy: boolean) => void- Propagates busy state to parent
Purpose: In-profile TOTP authenticator app enrollment management
Features:
- Detects current TOTP enrollment state (
hasTotpEnrolled) and shows enrollment or already-enrolled status accordingly - Handles re-authentication before TOTP enrollment (recent-login enforcement)
- Re-auth flow supports password and SMS MFA resolver challenges
- Launches
MfaTotpEnrollmentsub-component for the actual QR-code/verification step - Independent reCAPTCHA container scoped to this component (
recaptcha-container-totp-section) - Propagates busy state to parent for coordinated UI disable
Key Props:
-
user: User | null- Firebase user object -
isOpen: boolean- Whether the profile section is expanded -
onBusyChange?: (isBusy: boolean) => void- Propagates busy state to parent -
onTotpEnrolled: () => void- Called when TOTP enrollment completes successfully
Purpose: Displays and manages all enrolled MFA factors
Features:
- Lists all currently enrolled MFA factors (SMS and TOTP) with human-readable labels via
getMfaMethodLabel - Supports unenrolling any individual factor via
multiFactor(user).unenroll(uid) - Reloads factor list after unenrollment to keep display current
- Shows meaningful error if unenrollment requires recent login
- Optional
refreshKeyprop — increment to force a factor list reload from the parent - Propagates busy state to parent component through
onBusyChange
Key Props:
-
user: User | null- Firebase user object -
refreshKey?: number- Increment to trigger factor list reload -
onFactorRemoved: () => void- Called after a factor is successfully removed -
onBusyChange?: (isBusy: boolean) => void- Propagates removal busy state to parent
Most components use React's built-in state management:
// Typical state structure
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | undefined>();
const [data, setData] = useState<DataType | null>(null);Purpose: Global authentication state
Provided Values:
- Current user information
- Authentication status
- Login/logout functions
Purpose: Session inactivity management
Features:
- Configurable timeout periods
- Activity detection
- Automatic logout on timeout
The application implements a custom hooks pattern for encapsulating complex business logic, as demonstrated in the case-import system:
Purpose: Centralized state management for import workflow
Pattern: Encapsulates all stateful logic for the import process
Returns:
interface ImportState {
// File management
selectedFile: File | null;
setSelectedFile: (file: File | null) => void;
// Progress tracking
isImporting: boolean;
importProgress: number;
progressStage: string;
// Case management
existingCase: string | null;
previewData: CaseData | null;
// UI state
showConfirmation: boolean;
setShowConfirmation: (show: boolean) => void;
}Benefits:
- Consolidates related state in one hook
- Provides clean API for components
- Enables easy testing of state logic
Purpose: File processing and validation logic
Pattern: Handles asynchronous file operations with error handling
Functionality:
interface FilePreviewHook {
processFile: (file: File) => Promise<ProcessResult>;
validateZipStructure: (zipData: JSZip) => ValidationResult;
extractCaseData: (zipData: JSZip) => Promise<CaseData>;
previewImages: (zipData: JSZip) => Promise<ImagePreview[]>;
}Benefits:
- Isolates complex file processing logic
- Provides reusable validation functions
- Handles error states consistently
Purpose: Orchestrates the complete import process
Pattern: Manages side effects and external API calls
Process Management:
interface ImportExecutionHook {
executeImport: (importData: ImportData) => Promise<ImportResult>;
uploadImages: (images: ImageData[]) => Promise<UploadResult[]>;
saveCaseData: (caseData: CaseData) => Promise<SaveResult>;
updateUserProfile: (caseNumber: string) => Promise<UpdateResult>;
reportProgress: (stage: string, progress: number) => void;
}Benefits:
- Separates API calls from UI components
- Provides consistent progress reporting
- Enables comprehensive error handling
Single Responsibility: Each hook handles one aspect of business logic
// ✅ Good: Focused responsibility
const useFileValidation = (file: File) => {
// Only handles file validation logic
};
// ❌ Avoid: Mixed concerns
const useFileAndUserManagement = (file: File, user: User) => {
// Handles both file operations AND user management
};Return Object Pattern: Return objects for multiple values
// ✅ Good: Named returns
const useImportState = () => {
return {
isLoading,
error,
data,
refetch
};
};
// ❌ Avoid: Array returns for complex state
const useImportState = () => [isLoading, error, data, refetch];Error Handling: Consistent error handling patterns
const useAsyncOperation = () => {
const [error, setError] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);
const execute = async (params: OperationParams) => {
setIsLoading(true);
setError(null);
try {
const result = await performOperation(params);
return result;
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
throw err;
} finally {
setIsLoading(false);
}
};
return { execute, isLoading, error };
};Components follow React's unidirectional data flow:
// Parent component
const [selectedImage, setSelectedImage] = useState<string>();
// Child component receives data and callbacks
<ImageSelector
images={images}
onImageSelect={setSelectedImage}
/>Components use callback props for communication:
interface ComponentProps {
onSuccess: () => void;
onError: (message: string) => void;
onDataChange: (data: DataType) => void;
}Many components follow consistent modal patterns:
// Common modal interface
interface ModalProps {
isOpen: boolean;
onClose: () => void;
}
// Shared close/overlay behavior
const { overlayProps, getCloseButtonProps } = useOverlayDismiss({
isOpen,
onClose,
closeOnEscape: true,
closeOnBackdrop: true
});Use useOverlayDismiss for new modal/dialog work to keep escape/backdrop/click-close semantics consistent across the app.
Components use CSS Modules for scoped styling:
// Component file
import styles from './component.module.css';
// Usage
<div className={styles.container}>
<button className={styles.primaryButton}>
Click me
</button>
</div>-
BEM-like naming:
styles.componentName__elementName--modifier - CSS Custom Properties: For theming and consistency
- Intuitive Design: Clean, simple, user-friendly interfaces
- Accessibility: ARIA labels and semantic HTML
Components are designed for efficient mounting and unmounting:
useEffect(() => {
// Setup
const cleanup = setupComponent();
// Cleanup
return cleanup;
}, [dependencies]);Purpose: Comprehensive audit trail viewing and management for forensic accountability
Features:
- Date-range filtering (preset and custom ranges)
- Action/result/case filtering for investigation workflows
- Badge/ID filtering across loaded audit entries
- Export functionality (CSV, JSON, signed summary reports)
- Case-specific audit trail summary generation
- Compliance and security incident visibility
- Loading, empty, and error states with keyboard dismiss support
Type Definition: Uses ValidationAuditEntry from audit types
Props:
interface UserAuditViewerProps {
isOpen: boolean;
onClose: () => void;
caseNumber?: string;
title?: string;
}Key Features:
Data Management:
- Fetches audit entries through
auditService(Audit Worker-backed) - Uses
use-audit-viewer-datafor load/retry/error state management - Builds case-level audit summaries when case filters are applied
Filtering Capabilities:
- Date range filtering (from/to dates)
- Action type filtering (authentication, case operations, data access, etc.)
- Result status filtering (success, failure, warning, blocked)
- Case-specific filtering for workflow tracking
- Badge/ID filtering from
details.userProfileDetails.badgeId
Audit Trail Access:
Audit trails are bundled into encrypted case archive ZIP packages during case export and viewed in the UserAuditViewer component with filtering and search.
Refactored Viewer Composition:
-
user-audit-viewer.tsxorchestrates modal lifecycle and composition. -
viewer/audit-viewer-header.tsxhandles title and close actions. -
viewer/audit-user-info-card.tsxrenders user profile context including Badge/ID. -
viewer/audit-activity-summary.tsxrenders aggregate counts. -
viewer/audit-filters-panel.tsxrenders all filter controls. -
viewer/audit-entries-list.tsxrenders entry cards/details. -
viewer/use-audit-viewer-data.tsloads user/audit data. -
viewer/use-audit-viewer-filters.tsowns filter state and apply/clear handlers. -
viewer/audit-viewer-utils.tsprovides display helpers and summary computation.
Usage Example:
import { UserAuditViewer } from '~/components/audit/user-audit-viewer';
// Basic usage - current user's audit trail
<UserAuditViewer
isOpen={showAudit}
onClose={() => setShowAudit(false)}
/>
// Case-specific audit viewing
<UserAuditViewer
isOpen={showCaseAudit}
onClose={() => setShowCaseAudit(false)}
caseNumber="CASE-2024-001"
/>- Semantic HTML: Proper element usage
- ARIA Labels: Screen reader support
- Keyboard Navigation: Full keyboard accessibility
- Focus Management: Proper focus handling
- Color Contrast: WCAG compliance
- ✅ Create component directory
- ✅ Implement TypeScript interfaces
- ✅ Add CSS Module styling
- ✅ Include error handling (follow Error Handling Guide)
- ✅ Add loading states
- ✅ Implement accessibility features
- ✅ Add documentation
- Single Responsibility: Each component has one clear purpose
- Type Safety: Full TypeScript coverage
- Error Boundaries: Graceful error handling (see Error Handling Guide)
- Performance: Optimized for large datasets
- Maintainability: Clear, documented code