diff --git a/.cursor/rules/appflow.mdc b/.cursor/rules/appflow.mdc new file mode 100644 index 0000000..4747c45 --- /dev/null +++ b/.cursor/rules/appflow.mdc @@ -0,0 +1,351 @@ +--- +alwaysApply: true +--- +# Physician App - Application Flow Rules + +## ๐Ÿš€ App Launch & Authentication Flow + +### 1. Initial App Launch +``` +App Launch โ†’ Splash Screen โ†’ Authentication Check + โ†“ +[Authenticated] โ†’ ER Dashboard +[Not Authenticated] โ†’ Login Screen +``` + +### 2. Authentication Flow Rules +``` +Login Screen Options: +โ”œโ”€โ”€ Hospital SSO Login (Primary) +โ”œโ”€โ”€ Credential Login (Fallback) +โ”œโ”€โ”€ Emergency Access (Quick) +โ””โ”€โ”€ Temporary Login (Limited) +``` + +### 3. SSO Integration Rules +- **Primary**: Hospital SSO integration +- **Fallback**: Username/password authentication +- **Emergency**: Quick access codes for urgent situations +- **Remember Device**: 30-day device authentication +- **Session Timeout**: 8 hours for security + +## ๐Ÿ“Š ER Dashboard Flow + +### 4. Dashboard Entry Points +``` +ER Dashboard โ†’ Load Patient List โ†’ Real-time Updates + โ†“ +โ”œโ”€โ”€ Critical Alerts (Priority 1) +โ”œโ”€โ”€ Pending Scans (Priority 2) +โ”œโ”€โ”€ Recent Reports (Priority 3) +โ””โ”€โ”€ All Patients (Complete View) +``` + +### 5. Real-time Data Flow +``` +WebSocket Connection โ†’ Live Updates + โ†“ +โ”œโ”€โ”€ Patient Status Changes +โ”œโ”€โ”€ New Scan Results +โ”œโ”€โ”€ Critical Findings +โ”œโ”€โ”€ Bed Assignments +โ””โ”€โ”€ Shift Changes +``` + +### 6. Alert Priority System +- **๐Ÿ”ด Critical**: Immediate action required (0-2 minutes) +- **๐ŸŸก Warning**: Attention needed (2-10 minutes) +- **๐ŸŸข Info**: Routine updates (10+ minutes) +- **๐Ÿ”ต Status**: General information + +## ๐Ÿšจ Critical Finding Response Workflow + +### 7. Critical Alert Reception +``` +Push Notification โ†’ Alert Screen โ†’ Patient Details + โ†“ +AI Summary โ†’ Image Review โ†’ Treatment Protocol + โ†“ +Consultation โ†’ Action โ†’ Documentation +``` + +### 8. Alert Response Timeline +- **0-30 seconds**: Alert received & acknowledged +- **30-60 seconds**: Patient details reviewed +- **1-2 minutes**: AI findings assessed +- **2-5 minutes**: Treatment decision made +- **5+ minutes**: Action initiated & documented + +### 9. Critical Alert Components +``` +Alert Screen Elements: +โ”œโ”€โ”€ Patient Identification +โ”œโ”€โ”€ Bed Location +โ”œโ”€โ”€ Critical Finding Type +โ”œโ”€โ”€ AI-Generated Summary +โ”œโ”€โ”€ Urgency Level +โ”œโ”€โ”€ Time Stamp +โ””โ”€โ”€ Action Buttons +``` + +## ๐Ÿฅ Patient Care Flow + +### 10. Patient Selection Flow +``` +Patient List โ†’ Patient Card โ†’ Patient Details + โ†“ +โ”œโ”€โ”€ Vital Signs +โ”œโ”€โ”€ Medical History +โ”œโ”€โ”€ Current Medications +โ”œโ”€โ”€ Allergy Information +โ”œโ”€โ”€ Bed Information +โ””โ”€โ”€ Admission Details +``` + +### 11. Patient Detail Navigation +``` +Patient Details Screen: +โ”œโ”€โ”€ Demographics (Top) +โ”œโ”€โ”€ Vital Signs (Real-time) +โ”œโ”€โ”€ Medical History (Expandable) +โ”œโ”€โ”€ Current Medications (List) +โ”œโ”€โ”€ Allergy Information (Alert) +โ”œโ”€โ”€ Bed Information (Status) +โ””โ”€โ”€ Action Buttons (Bottom) +``` + +### 12. Medical Record Integration +- **EMR Sync**: Real-time patient data +- **Vital Signs**: Live monitoring integration +- **Medication History**: Pharmacy system sync +- **Lab Results**: Laboratory system integration +- **Imaging**: PACS system connection + +## ๐Ÿ“ฑ Screen Navigation Patterns + +### 13. Primary Navigation Structure +``` +Bottom Tab Navigation: +โ”œโ”€โ”€ Dashboard (Home) +โ”œโ”€โ”€ Patients +โ”œโ”€โ”€ Alerts +โ”œโ”€โ”€ Reports +โ””โ”€โ”€ Settings +``` + +### 14. Secondary Navigation +``` +Stack Navigation per Tab: +โ”œโ”€โ”€ List View โ†’ Detail View +โ”œโ”€โ”€ Detail View โ†’ Action View +โ”œโ”€โ”€ Action View โ†’ Confirmation +โ””โ”€โ”€ Confirmation โ†’ Return to List +``` + +### 15. Modal Navigation Rules +- **Quick Actions**: Modal overlays +- **Critical Actions**: Full-screen modals +- **Confirmation**: Alert modals +- **Settings**: Sheet modals + +## ๐Ÿ”„ State Management Flow + +### 16. Redux State Structure +``` +Root State: +โ”œโ”€โ”€ Auth (Authentication state) +โ”œโ”€โ”€ Dashboard (ER dashboard data) +โ”œโ”€โ”€ PatientCare (Patient information) +โ”œโ”€โ”€ Alerts (Notification system) +โ”œโ”€โ”€ Settings (User preferences) +โ””โ”€โ”€ UI (Interface state) +``` + +### 17. Data Flow Patterns +``` +API Call โ†’ Redux Action โ†’ State Update โ†’ UI Re-render + โ†“ +WebSocket โ†’ Real-time Update โ†’ Immediate UI Change + โ†“ +User Action โ†’ Local State โ†’ API Call โ†’ Server Sync +``` + +### 18. Caching Strategy +- **Patient Data**: 15-minute cache +- **Critical Alerts**: No cache (real-time) +- **User Settings**: Persistent storage +- **Medical Records**: 5-minute cache + +## ๐Ÿ“‹ Workflow Rules + +### 19. Critical Finding Workflow +``` +Step 1: Alert Reception +โ”œโ”€โ”€ Push notification received +โ”œโ”€โ”€ Alert screen displayed +โ”œโ”€โ”€ Patient context loaded +โ””โ”€โ”€ AI summary generated + +Step 2: Assessment +โ”œโ”€โ”€ Patient details reviewed +โ”œโ”€โ”€ Medical history checked +โ”œโ”€โ”€ Current status assessed +โ””โ”€โ”€ Urgency level determined + +Step 3: Action Planning +โ”œโ”€โ”€ Treatment protocol loaded +โ”œโ”€โ”€ Specialist consultation initiated +โ”œโ”€โ”€ Emergency procedures prepared +โ””โ”€โ”€ Documentation started + +Step 4: Implementation +โ”œโ”€โ”€ Actions executed +โ”œโ”€โ”€ Status updated +โ”œโ”€โ”€ Team notified +โ””โ”€โ”€ Record documented +``` + +### 20. Routine Scan Processing +``` +Step 1: Report Notification +โ”œโ”€โ”€ Scan completion notification +โ”œโ”€โ”€ Report status update +โ”œโ”€โ”€ AI findings summary +โ””โ”€โ”€ Priority assignment + +Step 2: Review Process +โ”œโ”€โ”€ Report details loaded +โ”œโ”€โ”€ Images reviewed +โ”œโ”€โ”€ Findings assessed +โ””โ”€โ”€ Action plan created + +Step 3: Documentation +โ”œโ”€โ”€ Patient record updated +โ”œโ”€โ”€ Treatment plan documented +โ”œโ”€โ”€ Follow-up scheduled +โ””โ”€โ”€ Discharge planning initiated +``` + +## ๐Ÿ” Security & Access Control + +### 21. Authentication Rules +- **Session Management**: 8-hour timeout +- **Auto-logout**: Inactivity after 30 minutes +- **Device Remembering**: 30-day trusted devices +- **Emergency Access**: Limited functionality +- **Audit Trail**: All actions logged + +### 22. Permission Levels +``` +User Roles: +โ”œโ”€โ”€ ER Physician (Full Access) +โ”œโ”€โ”€ Resident (Limited Access) +โ”œโ”€โ”€ Medical Student (Read-only) +โ”œโ”€โ”€ Emergency Access (Critical Only) +โ””โ”€โ”€ Temporary Access (Time-limited) +``` + +### 23. Data Access Rules +- **Patient Data**: Role-based access +- **Critical Alerts**: All ER staff +- **Medical Records**: Authorized personnel only +- **Settings**: User-specific +- **Audit Logs**: Admin only + +## ๐Ÿ“Š Performance & Optimization + +### 24. Loading States +``` +Loading Hierarchy: +โ”œโ”€โ”€ Critical Alerts (Immediate) +โ”œโ”€โ”€ Patient List (Fast) +โ”œโ”€โ”€ Patient Details (Medium) +โ”œโ”€โ”€ Medical History (Medium) +โ””โ”€โ”€ Full Reports (Slow) +``` + +### 25. Offline Capabilities +- **Critical Alerts**: Always available +- **Patient List**: Cached data +- **Recent Reports**: Offline access +- **Settings**: Local storage +- **Sync**: Automatic when online + +### 26. Error Handling +``` +Error Recovery: +โ”œโ”€โ”€ Network Errors โ†’ Retry with backoff +โ”œโ”€โ”€ Authentication Errors โ†’ Re-login +โ”œโ”€โ”€ Data Errors โ†’ Fallback to cache +โ”œโ”€โ”€ Critical Errors โ†’ Emergency mode +โ””โ”€โ”€ UI Errors โ†’ Graceful degradation +``` + +## ๐ŸŽฏ User Experience Rules + +### 27. Interaction Patterns +- **Critical Actions**: Confirmation required +- **Quick Actions**: One-tap execution +- **Navigation**: Intuitive flow +- **Feedback**: Immediate response +- **Accessibility**: WCAG 2.1 compliance + +### 28. Visual Hierarchy +``` +Priority Order: +โ”œโ”€โ”€ Critical Alerts (Red, Large) +โ”œโ”€โ”€ Active Patients (Blue, Medium) +โ”œโ”€โ”€ Pending Items (Yellow, Medium) +โ”œโ”€โ”€ Completed Items (Green, Small) +โ””โ”€โ”€ Background Info (Gray, Small) +``` + +### 29. Responsive Design +- **Mobile First**: Optimized for phones +- **Tablet Support**: Enhanced layouts +- **Landscape Mode**: Alternative views +- **Accessibility**: Voice commands support + +## ๐Ÿ”„ Data Synchronization + +### 30. Real-time Updates +``` +Update Types: +โ”œโ”€โ”€ Patient Status (Immediate) +โ”œโ”€โ”€ Vital Signs (30-second intervals) +โ”œโ”€โ”€ Alert Status (Real-time) +โ”œโ”€โ”€ Bed Assignments (Real-time) +โ””โ”€โ”€ Report Status (5-minute intervals) +``` + +### 31. Conflict Resolution +- **Server Priority**: Server data overrides local +- **Timestamp Comparison**: Latest data wins +- **User Confirmation**: Manual resolution for conflicts +- **Audit Trail**: All changes tracked + +## ๐Ÿ“ฑ Device Integration + +### 32. Hardware Integration +- **Camera**: Document scanning +- **Microphone**: Voice notes +- **Biometrics**: Secure access +- **NFC**: Patient identification +- **Bluetooth**: Medical device connection + +### 33. Platform-Specific Features +``` +iOS Features: +โ”œโ”€โ”€ Face ID authentication +โ”œโ”€โ”€ Apple Health integration +โ”œโ”€โ”€ Siri shortcuts +โ””โ”€โ”€ iOS notifications + +Android Features: +โ”œโ”€โ”€ Fingerprint authentication +โ”œโ”€โ”€ Google Fit integration +โ”œโ”€โ”€ Android Auto +โ””โ”€โ”€ Android notifications +``` + +This comprehensive flow ensures efficient, secure, and user-friendly operation of the Physician App in emergency medical scenarios. \ No newline at end of file diff --git a/.cursor/rules/projectstructurerule.mdc b/.cursor/rules/projectstructurerule.mdc new file mode 100644 index 0000000..259f26a --- /dev/null +++ b/.cursor/rules/projectstructurerule.mdc @@ -0,0 +1,308 @@ +--- +alwaysApply: true +--- +# Physician App - Project Structure & File Naming Rules + +## ๐Ÿ“ Directory Structure Rules + +### 1. Root Level Organization +``` +NeoScan_Physician/ +โ”œโ”€โ”€ app/ # Main application code +โ”œโ”€โ”€ docs/ # Documentation +โ”œโ”€โ”€ android/ # Android native code +โ”œโ”€โ”€ ios/ # iOS native code +โ”œโ”€โ”€ index.js # React Native entry point +โ”œโ”€โ”€ package.json # Dependencies +โ”œโ”€โ”€ tsconfig.json # TypeScript config +โ”œโ”€โ”€ metro.config.js # Metro bundler config +โ”œโ”€โ”€ babel.config.js # Babel config +โ””โ”€โ”€ .eslintrc.js # ESLint config +``` + +### 2. App Directory Structure +``` +app/ +โ”œโ”€โ”€ modules/ # Feature-based modules +โ”œโ”€โ”€ shared/ # Shared utilities & components +โ”œโ”€โ”€ store/ # Redux store configuration +โ”œโ”€โ”€ navigation/ # Navigation setup +โ”œโ”€โ”€ theme/ # Styling & theming +โ”œโ”€โ”€ config/ # Configuration files +โ”œโ”€โ”€ assets/ # Static assets +โ”œโ”€โ”€ localization/ # i18n +โ”œโ”€โ”€ App.tsx # Root component +โ””โ”€โ”€ index.tsx # App entry point +``` + +## ๐Ÿ—๏ธ Module Architecture Rules + +### 3. Module Structure (Feature-based) +Each module MUST follow this structure: +``` +modules/ModuleName/ +โ”œโ”€โ”€ components/ # Reusable UI components +โ”œโ”€โ”€ screens/ # Screen components +โ”œโ”€โ”€ hooks/ # Custom hooks +โ”œโ”€โ”€ redux/ # State management +โ”œโ”€โ”€ services/ # API & external services +โ”œโ”€โ”€ __tests__/ # Test files +โ””โ”€โ”€ index.ts # Module exports +``` + +### 4. Required Modules +- **Auth/** - Authentication & SSO +- **Dashboard/** - ER Dashboard & patient tracking +- **PatientCare/** - Patient details & medical records +- **Settings/** - User preferences & app settings + +## ๐Ÿ“ File Naming Conventions + +### 5. Component Files +- **PascalCase** for all component files +- **Suffix with type**: `.tsx` for components, `.ts` for utilities +- **Examples**: + - `LoginScreen.tsx` + - `PatientCard.tsx` + - `CriticalAlerts.tsx` + - `HospitalSSO.tsx` + +### 6. Hook Files +- **camelCase** with `use` prefix +- **Examples**: + - `useAuth.ts` + - `usePatientList.ts` + - `useRealTimeAlerts.ts` + - `useCriticalAlerts.ts` + +### 7. Service Files +- **camelCase** with descriptive names +- **Suffix with type**: `API.ts`, `Service.ts` +- **Examples**: + - `authAPI.ts` + - `patientCareAPI.ts` + - `notificationService.ts` + - `emrIntegration.ts` + +### 8. Redux Files +- **camelCase** with descriptive suffixes +- **Examples**: + - `authSlice.ts` + - `erDashboardSlice.ts` + - `patientCareActions.ts` + - `dashboardSelectors.ts` + +### 9. Test Files +- **Same name as source file** + `.test.ts` or `.test.tsx` +- **Examples**: + - `LoginScreen.test.tsx` + - `useAuth.test.ts` + - `authSlice.test.ts` + - `PatientCard.test.tsx` + +## ๐Ÿ”ง Shared Components Rules + +### 10. UI Components Structure +``` +shared/components/ +โ”œโ”€โ”€ UI/ # Basic UI components +โ”‚ โ”œโ”€โ”€ Button.tsx +โ”‚ โ”œโ”€โ”€ Input.tsx +โ”‚ โ”œโ”€โ”€ Card.tsx +โ”‚ โ”œโ”€โ”€ Modal.tsx +โ”‚ โ”œโ”€โ”€ Badge.tsx +โ”‚ โ”œโ”€โ”€ Spinner.tsx +โ”‚ โ”œโ”€โ”€ Alert.tsx +โ”‚ โ”œโ”€โ”€ Dropdown.tsx +โ”‚ โ”œโ”€โ”€ Tabs.tsx +โ”‚ โ”œโ”€โ”€ ProgressBar.tsx +โ”‚ โ””โ”€โ”€ index.ts +โ”œโ”€โ”€ Forms/ # Form-related components +โ”‚ โ”œโ”€โ”€ FormField.tsx +โ”‚ โ”œโ”€โ”€ ValidationMessage.tsx +โ”‚ โ”œโ”€โ”€ FormContainer.tsx +โ”‚ โ””โ”€โ”€ index.ts +โ”œโ”€โ”€ Icons/ # Icon components +โ”‚ โ”œโ”€โ”€ MedicalIcons.tsx +โ”‚ โ”œโ”€โ”€ StatusIcons.tsx +โ”‚ โ”œโ”€โ”€ NavigationIcons.tsx +โ”‚ โ””โ”€โ”€ index.ts +โ””โ”€โ”€ index.ts +``` + +### 11. Utility Files +``` +shared/utils/ +โ”œโ”€โ”€ api.ts # API utilities +โ”œโ”€โ”€ constants.ts # App constants +โ”œโ”€โ”€ helpers.ts # Helper functions +โ”œโ”€โ”€ validators.ts # Validation functions +โ”œโ”€โ”€ formatters.ts # Data formatting +โ”œโ”€โ”€ dateUtils.ts # Date utilities +โ”œโ”€โ”€ medicalUtils.ts # Medical-specific utilities +โ”œโ”€โ”€ imageUtils.ts # Image processing +โ”œโ”€โ”€ stringUtils.ts # String manipulation +โ””โ”€โ”€ index.ts +``` + +## ๐ŸŽจ Assets Organization + +### 12. Image Assets +``` +assets/images/ +โ”œโ”€โ”€ logos/ # Hospital & app logos +โ”œโ”€โ”€ icons/ # UI icons +โ”‚ โ”œโ”€โ”€ medical/ # Medical-specific icons +โ”‚ โ”œโ”€โ”€ ui/ # General UI icons +โ”‚ โ””โ”€โ”€ status/ # Status indicators +โ”œโ”€โ”€ backgrounds/ # Background images +โ””โ”€โ”€ placeholders/ # Placeholder images +``` + +### 13. Asset Naming +- **kebab-case** for all asset files +- **Examples**: + - `hospital-logo.png` + - `critical-alert.mp3` + - `ct-scan-placeholder.png` + - `emergency-bg.jpg` + +## ๐Ÿ“ฑ Navigation Structure + +### 14. Navigation Files +``` +navigation/ +โ”œโ”€โ”€ AppNavigator.tsx # Root navigator +โ”œโ”€โ”€ AuthNavigator.tsx # Authentication flow +โ”œโ”€โ”€ MainNavigator.tsx # Main app flow +โ”œโ”€โ”€ TabNavigator.tsx # Tab navigation +โ”œโ”€โ”€ navigationTypes.ts # Type definitions +โ”œโ”€โ”€ navigationUtils.ts # Navigation utilities +โ””โ”€โ”€ __tests__/ +``` + +## ๐Ÿ” Configuration Rules + +### 15. Environment & Config +``` +config/ +โ”œโ”€โ”€ env.ts # Environment variables +โ”œโ”€โ”€ api.ts # API configuration +โ”œโ”€โ”€ websocket.ts # WebSocket config +โ”œโ”€โ”€ notifications.ts # Notification config +โ”œโ”€โ”€ security.ts # Security settings +โ””โ”€โ”€ index.ts +``` + +## ๐Ÿ“š Documentation Rules + +### 16. Documentation Structure +``` +docs/ +โ”œโ”€โ”€ README.md # Project overview +โ”œโ”€โ”€ ARCHITECTURE.md # Architecture documentation +โ”œโ”€โ”€ API.md # API documentation +โ”œโ”€โ”€ DEPLOYMENT.md # Deployment guide +โ”œโ”€โ”€ TESTING.md # Testing guidelines +โ”œโ”€โ”€ SECURITY.md # Security guidelines +โ””โ”€โ”€ wireframes/ # UI wireframes +``` + +## ๐Ÿงช Testing Rules + +### 17. Test Organization +- **Unit tests** alongside source files in `__tests__/` folders +- **Integration tests** in module-level `__tests__/` folders +- **E2E tests** in root-level `__tests__/` folder +- **Test utilities** in `shared/__tests__/` + +## ๐Ÿ“ฆ Package Management + +### 18. Dependencies Organization +- **Core dependencies** in root `package.json` +- **Platform-specific** dependencies in respective folders +- **Dev dependencies** clearly separated +- **Peer dependencies** explicitly declared + +## ๐Ÿ”„ Import/Export Rules + +### 19. Import Conventions +- **Absolute imports** for shared utilities +- **Relative imports** for module-internal files +- **Index files** for clean imports +- **Barrel exports** for module APIs + +### 20. Export Patterns +```typescript +// Module index.ts +export { default as ComponentName } from './components/ComponentName'; +export { useHookName } from './hooks/useHookName'; +export { actionName } from './redux/actions'; +export type { TypeName } from './types'; +``` + +## ๐Ÿšซ Naming Restrictions + +### 21. Forbidden Patterns +- โŒ No spaces in file names +- โŒ No special characters except `-` and `_` +- โŒ No uppercase in utility/service files +- โŒ No generic names like `utils.ts` or `helpers.ts` +- โŒ No abbreviations unless universally understood + +### 22. Required Patterns +- โœ… Descriptive, self-documenting names +- โœ… Consistent casing within categories +- โœ… Clear separation of concerns +- โœ… Meaningful directory structure +- โœ… Proper TypeScript extensions + +## ๐Ÿ“‹ File Size Guidelines + +### 23. Component Limits +- **Single component files**: Max 300 lines +- **Complex components**: Split into smaller components +- **Utility files**: Max 200 lines +- **Service files**: Max 150 lines per service + +### 24. Module Limits +- **Module components**: Max 10 files per category +- **Module services**: Max 5 files +- **Module hooks**: Max 8 files +- **Module redux**: Max 6 files + +## ๐Ÿ” Code Organization Principles + +### 25. Separation of Concerns +- **UI Logic** in components +- **Business Logic** in hooks/services +- **State Management** in Redux +- **Data Fetching** in services +- **Utilities** in shared/utils + +### 26. Reusability +- **Shared components** in shared/components +- **Common utilities** in shared/utils +- **Type definitions** in shared/types +- **Constants** in shared/constants + +This structure ensures maintainability, scalability, and consistency across the Physician App codebase. + + +and it should add proper comments in the each file for better understanding the flow. +should follow rule file while generating code.Each file should contain this as header +/* + * File: FILE_NAME.tsx + * Description: Main chat screen component + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + +file footer +/* + * End of File: ChatScreen.tsx + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +and it should add proper comments in the file for better understanding the flow. +should follow rule file while generating code. \ No newline at end of file diff --git a/.cursor/rules/themeflow.mdc b/.cursor/rules/themeflow.mdc new file mode 100644 index 0000000..ca7c425 --- /dev/null +++ b/.cursor/rules/themeflow.mdc @@ -0,0 +1,718 @@ +--- +alwaysApply: true +--- +# Physician App - Theme & UI Design Rules + +## ๐ŸŽจ Color Palette - "Modern Healthcare Blue" + +### 1. Primary Color Scheme +```typescript +// Primary Colors +Primary: '#2196F3' // Material Blue - Main brand color +Secondary: '#1976D2' // Darker Blue - Secondary actions +Tertiary: '#E3F2FD' // Very Light Blue - Backgrounds +Quaternary: '#0D47A1' // Deep Blue - Accents + +// Text Colors +TextPrimary: '#212121' // Dark Gray - Main text +TextSecondary: '#757575' // Medium Gray - Secondary text +TextMuted: '#9E9E9E' // Light Gray - Muted text + +// Background Colors +Background: '#FFFFFF' // White - Primary background +BackgroundAlt: '#FAFAFA' // Light Gray - Alternative background +BackgroundAccent: '#F5F5F5' // Soft Gray - Accent backgrounds +``` + +### 2. Status & Feedback Colors +```typescript +// Status Colors +Success: '#4CAF50' // Material Green - Success states +Warning: '#FF9800' // Material Orange - Warning states +Error: '#F44336' // Material Red - Error states +Info: '#2196F3' // Material Blue - Information states + +// UI Elements +Border: '#E0E0E0' // Light Gray Border +CardBackground: '#FFFFFF' // White - Card backgrounds +Shadow: 'rgba(0, 0, 0, 0.1)' // Subtle Gray Shadow +``` + +## ๐Ÿ—๏ธ Typography System + +### 3. Font Hierarchy +```typescript +// Font Families +PrimaryFont: 'Roboto' // Main font family +SecondaryFont: 'Medical-Icons' // Icon font + +// Font Weights +Light: 300 +Regular: 400 +Medium: 500 +Bold: 700 + +// Font Sizes +DisplayLarge: 32px // Main headings +DisplayMedium: 24px // Section headings +DisplaySmall: 20px // Subsection headings +BodyLarge: 16px // Body text +BodyMedium: 14px // Secondary text +BodySmall: 12px // Captions +Caption: 10px // Small labels +``` + +### 4. Line Heights & Spacing +```typescript +// Line Heights +Tight: 1.2 // Headings +Normal: 1.4 // Body text +Relaxed: 1.6 // Long text + +// Letter Spacing +Tight: -0.5px // Headings +Normal: 0px // Body text +Wide: 0.5px // Labels +``` + +## ๐Ÿ“ Spacing & Layout + +### 5. Spacing Scale +```typescript +// Base Spacing Unit: 4px +Spacing: { + xs: 4, // 4px + sm: 8, // 8px + md: 16, // 16px + lg: 24, // 24px + xl: 32, // 32px + xxl: 48, // 48px + xxxl: 64 // 64px +} +``` + +### 6. Layout Rules +```typescript +// Container Max Widths +Mobile: 375 +Tablet: 768 +Desktop: 1024 + +// Border Radius +Small: 4 +Medium: 8 +Large: 12 +XLarge: 16 +Round: 50 + +// Shadows (React Native StyleSheet) +Small: { + shadowColor: '#000000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.1, + shadowRadius: 4, + elevation: 2 +} + +Medium: { + shadowColor: '#000000', + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.15, + shadowRadius: 8, + elevation: 4 +} + +Large: { + shadowColor: '#000000', + shadowOffset: { width: 0, height: 8 }, + shadowOpacity: 0.2, + shadowRadius: 16, + elevation: 8 +} +``` + +## ๐ŸŽฏ Component Design Rules + +### 7. Button Design System +```typescript +// Button Variants +Primary: { + backgroundColor: '#2196F3', + borderColor: '#2196F3', + borderRadius: 8, + shadowColor: '#2196F3', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.3, + shadowRadius: 4, + elevation: 3 +} + +Secondary: { + backgroundColor: 'transparent', + borderColor: '#2196F3', + borderRadius: 8 +} + +Success: { + backgroundColor: '#4CAF50', + borderColor: '#4CAF50', + borderRadius: 8, + shadowColor: '#4CAF50', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.3, + shadowRadius: 4, + elevation: 3 +} + +Critical: { + backgroundColor: '#F44336', + borderColor: '#F44336', + borderRadius: 8, + shadowColor: '#F44336', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.3, + shadowRadius: 4, + elevation: 3 +} + +// Button Sizes +Small: { paddingHorizontal: 16, paddingVertical: 8, fontSize: 14, borderRadius: 6 } +Medium: { paddingHorizontal: 24, paddingVertical: 12, fontSize: 16, borderRadius: 8 } +Large: { paddingHorizontal: 32, paddingVertical: 16, fontSize: 18, borderRadius: 10 } +``` + +### 8. Card Design Rules +```typescript +// Card Variants +Default: { + backgroundColor: '#FFFFFF', + borderColor: '#E0E0E0', + borderRadius: 12, + padding: 16, + shadowColor: '#000000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.1, + shadowRadius: 4, + elevation: 2 +} + +Elevated: { + backgroundColor: '#FFFFFF', + borderColor: '#E0E0E0', + borderRadius: 12, + padding: 16, + shadowColor: '#000000', + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.15, + shadowRadius: 8, + elevation: 4 +} + +Critical: { + backgroundColor: '#FFEBEE', + borderColor: '#F44336', + borderRadius: 12, + padding: 16, + shadowColor: '#F44336', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.1, + shadowRadius: 4, + elevation: 2 +} + +PatientCard: { + backgroundColor: '#FFFFFF', + borderColor: '#E0E0E0', + borderRadius: 16, + padding: 20, + shadowColor: '#000000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.08, + shadowRadius: 4, + elevation: 2 +} +``` + +### 9. Input Field Design +```typescript +// Input States +Default: { + borderColor: '#E0E0E0', + backgroundColor: '#FFFFFF', + borderRadius: 8, + paddingHorizontal: 12, + paddingVertical: 12 +} + +Focused: { + borderColor: '#2196F3', + backgroundColor: '#FFFFFF', + borderRadius: 8, + paddingHorizontal: 12, + paddingVertical: 12 +} + +Error: { + borderColor: '#F44336', + backgroundColor: '#FFEBEE', + borderRadius: 8, + paddingHorizontal: 12, + paddingVertical: 12 +} + +Disabled: { + borderColor: '#E0E0E0', + backgroundColor: '#F5F5F5', + borderRadius: 8, + paddingHorizontal: 12, + paddingVertical: 12 +} +``` + +## ๐Ÿšจ Alert & Status Design (Following Screenshot UI) + +### 10. Alert Priority System +```typescript +// Critical Alerts +Critical: { + backgroundColor: '#FFEBEE', + borderColor: '#F44336', + borderRadius: 8, + padding: 16 +} + +// Warning Alerts +Warning: { + backgroundColor: '#FFF3E0', + borderColor: '#FF9800', + borderRadius: 8, + padding: 16 +} + +// Success Alerts +Success: { + backgroundColor: '#E8F5E8', + borderColor: '#4CAF50', + borderRadius: 8, + padding: 16 +} + +// Info Alerts +Info: { + backgroundColor: '#E3F2FD', + borderColor: '#2196F3', + borderRadius: 8, + padding: 16 +} + +// Patient Status +Active: { + backgroundColor: '#E3F2FD', + borderColor: '#2196F3', + borderRadius: 12, + paddingHorizontal: 8, + paddingVertical: 4 +} + +Pending: { + backgroundColor: '#FFF3E0', + borderColor: '#FF9800', + borderRadius: 12, + paddingHorizontal: 8, + paddingVertical: 4 +} + +Completed: { + backgroundColor: '#E8F5E8', + borderColor: '#4CAF50', + borderRadius: 12, + paddingHorizontal: 8, + paddingVertical: 4 +} +``` + +### 11. Badge & Tag Design (Following Screenshot UI) +```typescript +// Status Badges +StatusBadge: { + paddingHorizontal: 8, + paddingVertical: 4, + borderRadius: 12, + fontSize: 12, + fontWeight: '500' +} + +// Priority Badges +PriorityBadge: { + paddingHorizontal: 12, + paddingVertical: 6, + borderRadius: 16, + fontSize: 12, + fontWeight: '600' +} + +// Medical Badges +MedicalBadge: { + paddingHorizontal: 10, + paddingVertical: 5, + borderRadius: 14, + fontSize: 11, + fontWeight: '500' +} +``` + +## ๐Ÿ“ฑ Screen-Specific Design Rules + +### 12. ER Dashboard Screen Design (Following Screenshot UI) +```typescript +// Header Section +Header: { + backgroundColor: '#FFFFFF', + paddingHorizontal: 16, + paddingVertical: 12, + borderBottomColor: '#E0E0E0', + borderBottomWidth: 1 +} + +// Critical Alerts Section (Like promotional banner but for emergencies) +CriticalAlerts: { + backgroundColor: '#FFEBEE', + borderColor: '#F44336', + borderRadius: 16, + padding: 20, + marginHorizontal: 16, + marginVertical: 12, + shadowColor: '#F44336', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.15, + shadowRadius: 8, + elevation: 4 +} + +// Patient List Section (Like specialist filter) +PatientList: { + paddingHorizontal: 16, + paddingVertical: 8, + backgroundColor: '#FFFFFF' +} + +// Quick Actions (Like service icons but for emergency actions) +QuickActions: { + width: 60, + height: 60, + borderRadius: 30, + backgroundColor: '#F5F5F5', + justifyContent: 'center', + alignItems: 'center', + marginHorizontal: 8 +} + +// Patient Cards (Like doctor cards but for patients) +PatientCard: { + backgroundColor: '#FFFFFF', + borderRadius: 16, + padding: 16, + marginHorizontal: 8, + marginVertical: 4, + shadowColor: '#000000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.08, + shadowRadius: 4, + elevation: 2, + minWidth: 200 +} +``` + +### 13. Patient Details Screen Design (Following Screenshot UI) +```typescript +// Patient Header +PatientHeader: { + backgroundColor: '#FFFFFF', + padding: 20, + borderBottomColor: '#E0E0E0', + borderBottomWidth: 1 +} + +// Patient Info Card +PatientInfoCard: { + backgroundColor: '#FFFFFF', + borderRadius: 16, + padding: 20, + margin: 16, + shadowColor: '#000000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.1, + shadowRadius: 8, + elevation: 4 +} + +// Vital Signs Section (Like contact options but for medical data) +VitalSigns: { + flexDirection: 'row', + justifyContent: 'space-around', + paddingVertical: 16, + borderBottomColor: '#E0E0E0', + borderBottomWidth: 1 +} + +// Medical History Section (Like calendar section but for medical records) +MedicalHistory: { + backgroundColor: '#FFFFFF', + borderRadius: 12, + padding: 16, + margin: 16, + shadowColor: '#000000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.08, + shadowRadius: 4, + elevation: 2 +} + +// Action Buttons (Like booking button but for medical actions) +ActionButton: { + backgroundColor: '#2196F3', + borderRadius: 12, + padding: 16, + margin: 16, + alignItems: 'center', + shadowColor: '#2196F3', + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.3, + shadowRadius: 8, + elevation: 6 +} +``` + +### 14. Login Screen Design (Following Screenshot UI) +```typescript +// Login Container +LoginContainer: { + backgroundColor: '#F1FDFF', + padding: 24, + borderRadius: 12, + shadowColor: '#2196F3', + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.15, + shadowRadius: 8, + elevation: 4 +} + +// Hospital Logo +Logo: { + width: 120, + height: 120, + marginBottom: 32 +} + +// Form Fields +FormField: { + marginBottom: 16, + backgroundColor: '#FFFFFF', + borderColor: '#E0E0E0', + borderRadius: 8 +} + +// Login Button +LoginButton: { + backgroundColor: '#2196F3', + borderRadius: 8, + padding: 16, + fontSize: 16, + fontWeight: '600' +} +``` + +## ๐ŸŽจ Icon Design System + +### 15. Icon Specifications +```typescript +// Icon Sizes +IconSizes: { + xs: 12, // Extra small + sm: 16, // Small + md: 24, // Medium (default) + lg: 32, // Large + xl: 48 // Extra large +} + +// Icon Colors +IconColors: { + Primary: '#2196F3', + Secondary: '#1976D2', + Success: '#4CAF50', + Warning: '#FF9800', + Error: '#F44336', + Info: '#2196F3', + Muted: '#9E9E9E' +} + +// Medical Icons +MedicalIcons: { + Stethoscope: 'medical-stethoscope', + Heart: 'medical-heart', + Brain: 'medical-brain', + Emergency: 'medical-emergency', + Patient: 'medical-patient', + Bed: 'medical-bed', + Alert: 'medical-alert', + VitalSigns: 'medical-vital-signs', + Medication: 'medical-medication', + Lab: 'medical-lab', + Imaging: 'medical-imaging', + Surgery: 'medical-surgery' +} +``` + +## ๐Ÿ“Š Data Visualization + +### 16. Chart Colors +```typescript +// Chart Color Palette +ChartColors: { + Primary: '#2196F3', + Secondary: '#1976D2', + Tertiary: '#E3F2FD', + Quaternary: '#0D47A1', + Success: '#4CAF50', + Warning: '#FF9800', + Error: '#F44336' +} + +// Vital Signs Charts +VitalSignsChart: { + backgroundColor: '#FFFFFF', + gridColor: '#E0E0E0', + lineColor: '#2196F3', + pointColor: '#1976D2' +} + +// Patient Status Charts +PatientStatusChart: { + backgroundColor: '#FFFFFF', + gridColor: '#E0E0E0', + lineColor: '#4CAF50', + pointColor: '#2E7D32' +} +``` + +## ๐ŸŒ™ Dark Mode Support + +### 17. Dark Mode Colors +```typescript +// Dark Mode Palette +DarkMode: { + Background: '#121212', + BackgroundAlt: '#1E1E1E', + BackgroundAccent: '#2D2D2D', + TextPrimary: '#FFFFFF', + TextSecondary: '#B0B0B0', + TextMuted: '#808080', + Border: '#404040', + CardBackground: '#1E1E1E', + Primary: '#2196F3', + Secondary: '#1976D2' +} +``` + +## ๐Ÿ“ฑ Responsive Design + +### 18. Breakpoint System +```typescript +// Breakpoints +Breakpoints: { + Mobile: 375, + Tablet: 768, + Desktop: 1024, + LargeDesktop: 1440 +} + +// Responsive Spacing +ResponsiveSpacing: { + Mobile: { padding: 16, margin: 8 }, + Tablet: { padding: 24, margin: 16 }, + Desktop: { padding: 32, margin: 24 } +} +``` + +## ๐ŸŽฏ Accessibility Rules + +### 19. Accessibility Standards +```typescript +// Color Contrast +ContrastRatios: { + Normal: 4.5, // Minimum for normal text + Large: 3.0, // Minimum for large text + UI: 3.0 // Minimum for UI elements +} + +// Touch Targets +TouchTargets: { + Minimum: 44, // Minimum touch target size + Preferred: 48 // Preferred touch target size +} + +// Focus Indicators +FocusIndicator: { + Color: '#2196F3', + Width: 2, + Style: 'solid' +} +``` + +## ๐Ÿ”„ Animation & Transitions + +### 20. Animation Rules +```typescript +// Animation Durations +Durations: { + Fast: 150, // Quick interactions + Normal: 300, // Standard transitions + Slow: 500 // Complex animations +} + +// Easing Functions +Easing: { + Standard: 'cubic-bezier(0.4, 0.0, 0.2, 1)', + Deceleration: 'cubic-bezier(0.0, 0.0, 0.2, 1)', + Acceleration: 'cubic-bezier(0.4, 0.0, 1, 1)' +} + +// Transition Types +Transitions: { + Fade: { opacity: [0, 1], duration: 300 }, + Slide: { transform: [{ translateY: [20, 0] }], duration: 300 }, + Scale: { transform: [{ scale: [0.95, 1] }], duration: 200 } +} +``` + +## ๐ŸŽจ Design Tokens + +### 21. Design Token Structure +```typescript +// Theme Object Structure +Theme: { + colors: ColorPalette, + typography: TypographySystem, + spacing: SpacingScale, + borderRadius: BorderRadiusScale, + shadows: ShadowSystem, + breakpoints: BreakpointSystem, + animations: AnimationSystem +} + +// Usage Example +const styles = StyleSheet.create({ + container: { + backgroundColor: theme.colors.background, + padding: theme.spacing.md, + borderRadius: theme.borderRadius.medium, + shadowColor: '#000000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.1, + shadowRadius: 4, + elevation: 2 + } +}); +``` + +This comprehensive theme system ensures consistency, accessibility, and modern healthcare aesthetics across the Physician App, following the clean and professional design patterns shown in the reference screenshots while maintaining the ER workflow functionality. \ No newline at end of file diff --git a/.env b/.env new file mode 100644 index 0000000..aa3551d --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +BASE_URL='https://neoscan-backend.tech4bizsolutions.com' +# BASE_URL='http://192.168.1.87:3000' \ No newline at end of file diff --git a/PROJECT_STRUCTURE.md b/PROJECT_STRUCTURE.md new file mode 100644 index 0000000..2b0a6ca --- /dev/null +++ b/PROJECT_STRUCTURE.md @@ -0,0 +1,355 @@ +/* + * File: PROJECT_STRUCTURE.md + * Description: Complete project structure and architecture documentation + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +# NeoScan Physician App - Project Structure + +## ๐Ÿ“ Complete Directory Structure + +``` +NeoScan_Physician/ +โ”œโ”€โ”€ app/ # Main application code +โ”‚ โ”œโ”€โ”€ modules/ # Feature-based modules +โ”‚ โ”‚ โ”œโ”€โ”€ Auth/ # Authentication module +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ components/ # Auth-specific components +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ screens/ # Auth screens +โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ LoginScreen.tsx # Main login screen +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ hooks/ # Auth custom hooks +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ redux/ # Auth state management +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ services/ # Auth API services +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ index.ts # Auth module exports +โ”‚ โ”‚ โ”œโ”€โ”€ Dashboard/ # ER Dashboard module +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ components/ # Dashboard components +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ PatientCard.tsx # Patient information card +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ CriticalAlerts.tsx # Critical alerts display +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ DashboardHeader.tsx # Dashboard statistics header +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ QuickActions.tsx # Emergency quick actions +โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ DepartmentStats.tsx # Department statistics +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ screens/ # Dashboard screens +โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ ERDashboardScreen.tsx # Main ER dashboard +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ hooks/ # Dashboard custom hooks +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ redux/ # Dashboard state management +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ services/ # Dashboard API services +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ index.ts # Dashboard module exports +โ”‚ โ”‚ โ”œโ”€โ”€ PatientCare/ # Patient management module +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ components/ # Patient care components +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ screens/ # Patient care screens +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ hooks/ # Patient care hooks +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ redux/ # Patient care state +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ services/ # Patient care services +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ index.ts # Patient care exports +โ”‚ โ”‚ โ””โ”€โ”€ Settings/ # App settings module +โ”‚ โ”‚ โ”œโ”€โ”€ components/ # Settings components +โ”‚ โ”‚ โ”œโ”€โ”€ screens/ # Settings screens +โ”‚ โ”‚ โ”œโ”€โ”€ hooks/ # Settings hooks +โ”‚ โ”‚ โ”œโ”€โ”€ redux/ # Settings state +โ”‚ โ”‚ โ”œโ”€โ”€ services/ # Settings services +โ”‚ โ”‚ โ””โ”€โ”€ index.ts # Settings exports +โ”‚ โ”œโ”€โ”€ shared/ # Shared utilities & components +โ”‚ โ”‚ โ”œโ”€โ”€ components/ # Reusable UI components +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ UI/ # Basic UI components +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ Button.tsx # Button component +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ Input.tsx # Input field component +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ Card.tsx # Card component +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ Modal.tsx # Modal component +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ Badge.tsx # Badge component +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ Spinner.tsx # Loading spinner +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ Alert.tsx # Alert component +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ Dropdown.tsx # Dropdown component +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ Tabs.tsx # Tab component +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ ProgressBar.tsx # Progress bar +โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ index.ts # UI components export +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ Forms/ # Form-related components +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ FormField.tsx # Form field component +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ ValidationMessage.tsx # Validation message +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ FormContainer.tsx # Form container +โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ index.ts # Form components export +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ Icons/ # Icon components +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ MedicalIcons.tsx # Medical-specific icons +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ StatusIcons.tsx # Status indicators +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ NavigationIcons.tsx # Navigation icons +โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ index.ts # Icon components export +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ index.ts # Shared components export +โ”‚ โ”‚ โ”œโ”€โ”€ utils/ # Utility functions +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ api.ts # API utilities +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ constants.ts # App constants +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ helpers.ts # Helper functions +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ validators.ts # Validation functions +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ formatters.ts # Data formatting +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ dateUtils.ts # Date utilities +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ medicalUtils.ts # Medical-specific utilities +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ imageUtils.ts # Image processing +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ stringUtils.ts # String manipulation +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ index.ts # Utils export +โ”‚ โ”‚ โ”œโ”€โ”€ types/ # TypeScript type definitions +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ auth.ts # Authentication types +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ patient.ts # Patient-related types +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ dashboard.ts # Dashboard types +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ alerts.ts # Alert types +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ common.ts # Common types +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ index.ts # Types export +โ”‚ โ”‚ โ””โ”€โ”€ index.ts # Shared module export +โ”‚ โ”œโ”€โ”€ theme/ # Design system & theming +โ”‚ โ”‚ โ”œโ”€โ”€ colors.ts # Color palette +โ”‚ โ”‚ โ”œโ”€โ”€ typography.ts # Typography system +โ”‚ โ”‚ โ”œโ”€โ”€ spacing.ts # Spacing & layout +โ”‚ โ”‚ โ”œโ”€โ”€ shadows.ts # Shadow system +โ”‚ โ”‚ โ”œโ”€โ”€ animations.ts # Animation system +โ”‚ โ”‚ โ”œโ”€โ”€ theme.ts # Main theme object +โ”‚ โ”‚ โ””โ”€โ”€ index.ts # Theme export +โ”‚ โ”œโ”€โ”€ navigation/ # Navigation setup +โ”‚ โ”‚ โ”œโ”€โ”€ AppNavigator.tsx # Root navigator +โ”‚ โ”‚ โ”œโ”€โ”€ AuthNavigator.tsx # Authentication flow +โ”‚ โ”‚ โ”œโ”€โ”€ MainNavigator.tsx # Main app flow +โ”‚ โ”‚ โ”œโ”€โ”€ TabNavigator.tsx # Tab navigation +โ”‚ โ”‚ โ”œโ”€โ”€ navigationTypes.ts # Navigation types +โ”‚ โ”‚ โ”œโ”€โ”€ navigationUtils.ts # Navigation utilities +โ”‚ โ”‚ โ””โ”€โ”€ __tests__/ # Navigation tests +โ”‚ โ”œโ”€โ”€ store/ # Redux store configuration +โ”‚ โ”‚ โ”œโ”€โ”€ index.ts # Store configuration +โ”‚ โ”‚ โ”œโ”€โ”€ rootReducer.ts # Root reducer +โ”‚ โ”‚ โ”œโ”€โ”€ middleware.ts # Custom middleware +โ”‚ โ”‚ โ””โ”€โ”€ __tests__/ # Store tests +โ”‚ โ”œโ”€โ”€ config/ # Configuration files +โ”‚ โ”‚ โ”œโ”€โ”€ env.ts # Environment variables +โ”‚ โ”‚ โ”œโ”€โ”€ api.ts # API configuration +โ”‚ โ”‚ โ”œโ”€โ”€ websocket.ts # WebSocket config +โ”‚ โ”‚ โ”œโ”€โ”€ notifications.ts # Notification config +โ”‚ โ”‚ โ”œโ”€โ”€ security.ts # Security settings +โ”‚ โ”‚ โ””โ”€โ”€ index.ts # Config export +โ”‚ โ”œโ”€โ”€ assets/ # Static assets +โ”‚ โ”‚ โ”œโ”€โ”€ images/ # Image assets +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ logos/ # Hospital & app logos +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ icons/ # UI icons +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ medical/ # Medical-specific icons +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ ui/ # General UI icons +โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ status/ # Status indicators +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ backgrounds/ # Background images +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ placeholders/ # Placeholder images +โ”‚ โ”‚ โ”œโ”€โ”€ fonts/ # Font files +โ”‚ โ”‚ โ””โ”€โ”€ sounds/ # Audio assets +โ”‚ โ”œโ”€โ”€ localization/ # Internationalization +โ”‚ โ”‚ โ”œโ”€โ”€ en/ # English translations +โ”‚ โ”‚ โ”œโ”€โ”€ es/ # Spanish translations +โ”‚ โ”‚ โ”œโ”€โ”€ fr/ # French translations +โ”‚ โ”‚ โ””โ”€โ”€ index.ts # i18n configuration +โ”‚ โ”œโ”€โ”€ App.tsx # Root component +โ”‚ โ””โ”€โ”€ index.tsx # App entry point +โ”œโ”€โ”€ android/ # Android native code +โ”‚ โ”œโ”€โ”€ app/ # Android app module +โ”‚ โ”‚ โ”œโ”€โ”€ build.gradle # App build configuration +โ”‚ โ”‚ โ”œโ”€โ”€ debug.keystore # Debug keystore +โ”‚ โ”‚ โ”œโ”€โ”€ proguard-rules.pro # ProGuard rules +โ”‚ โ”‚ โ””โ”€โ”€ src/ # Source code +โ”‚ โ”‚ โ”œโ”€โ”€ debug/ # Debug configuration +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ AndroidManifest.xml # Debug manifest +โ”‚ โ”‚ โ””โ”€โ”€ main/ # Main source +โ”‚ โ”‚ โ”œโ”€โ”€ AndroidManifest.xml # Main manifest +โ”‚ โ”‚ โ”œโ”€โ”€ java/ # Java source +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ com/ # Package structure +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ neoscan_physician/ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ MainActivity.kt # Main activity +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ MainApplication.kt # Application class +โ”‚ โ”‚ โ””โ”€โ”€ res/ # Resources +โ”‚ โ”‚ โ”œโ”€โ”€ drawable/ # Drawable resources +โ”‚ โ”‚ โ”œโ”€โ”€ mipmap-*/ # App icons +โ”‚ โ”‚ โ””โ”€โ”€ values/ # Values +โ”‚ โ”‚ โ”œโ”€โ”€ strings.xml # String resources +โ”‚ โ”‚ โ””โ”€โ”€ styles.xml # Styles +โ”‚ โ”œโ”€โ”€ build.gradle # Project build configuration +โ”‚ โ”œโ”€โ”€ gradle/ # Gradle wrapper +โ”‚ โ”œโ”€โ”€ gradle.properties # Gradle properties +โ”‚ โ”œโ”€โ”€ gradlew # Gradle wrapper script +โ”‚ โ”œโ”€โ”€ gradlew.bat # Windows gradle wrapper +โ”‚ โ””โ”€โ”€ settings.gradle # Gradle settings +โ”œโ”€โ”€ ios/ # iOS native code +โ”‚ โ”œโ”€โ”€ NeoScan_Physician/ # iOS app +โ”‚ โ”‚ โ”œโ”€โ”€ AppDelegate.swift # App delegate +โ”‚ โ”‚ โ”œโ”€โ”€ Images.xcassets/ # Image assets +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ AppIcon.appiconset/ # App icons +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ Contents.json # Asset catalog +โ”‚ โ”‚ โ”œโ”€โ”€ Info.plist # App info +โ”‚ โ”‚ โ”œโ”€โ”€ LaunchScreen.storyboard # Launch screen +โ”‚ โ”‚ โ””โ”€โ”€ PrivacyInfo.xcprivacy # Privacy info +โ”‚ โ”œโ”€โ”€ NeoScan_Physician.xcodeproj/ # Xcode project +โ”‚ โ”‚ โ”œโ”€โ”€ project.pbxproj # Project file +โ”‚ โ”‚ โ””โ”€โ”€ xcshareddata/ # Shared data +โ”‚ โ”‚ โ””โ”€โ”€ xcschemes/ # Build schemes +โ”‚ โ”‚ โ””โ”€โ”€ NeoScan_Physician.xcscheme +โ”‚ โ””โ”€โ”€ Podfile # CocoaPods configuration +โ”œโ”€โ”€ __tests__/ # Test files +โ”‚ โ”œโ”€โ”€ App.test.tsx # App component tests +โ”‚ โ”œโ”€โ”€ components/ # Component tests +โ”‚ โ”œโ”€โ”€ utils/ # Utility tests +โ”‚ โ””โ”€โ”€ integration/ # Integration tests +โ”œโ”€โ”€ docs/ # Documentation +โ”‚ โ”œโ”€โ”€ README.md # Project overview +โ”‚ โ”œโ”€โ”€ ARCHITECTURE.md # Architecture documentation +โ”‚ โ”œโ”€โ”€ API.md # API documentation +โ”‚ โ”œโ”€โ”€ DEPLOYMENT.md # Deployment guide +โ”‚ โ”œโ”€โ”€ TESTING.md # Testing guidelines +โ”‚ โ”œโ”€โ”€ SECURITY.md # Security guidelines +โ”‚ โ””โ”€โ”€ wireframes/ # UI wireframes +โ”œโ”€โ”€ .gitignore # Git ignore rules +โ”œโ”€โ”€ .eslintrc.js # ESLint configuration +โ”œโ”€โ”€ .prettierrc.js # Prettier configuration +โ”œโ”€โ”€ .watchmanconfig # Watchman configuration +โ”œโ”€โ”€ app.json # App configuration +โ”œโ”€โ”€ babel.config.js # Babel configuration +โ”œโ”€โ”€ jest.config.js # Jest configuration +โ”œโ”€โ”€ metro.config.js # Metro bundler configuration +โ”œโ”€โ”€ package.json # Dependencies and scripts +โ”œโ”€โ”€ package-lock.json # Locked dependencies +โ”œโ”€โ”€ tsconfig.json # TypeScript configuration +โ”œโ”€โ”€ index.js # React Native entry point +โ”œโ”€โ”€ setup.sh # Unix setup script +โ”œโ”€โ”€ setup.bat # Windows setup script +โ”œโ”€โ”€ README.md # Project README +โ”œโ”€โ”€ PROJECT_STRUCTURE.md # This file +โ””โ”€โ”€ Gemfile # Ruby dependencies (iOS) +``` + +## ๐Ÿ—๏ธ Module Architecture + +### Auth Module +**Purpose**: Handles all authentication and authorization functionality +- **LoginScreen**: Hospital SSO, credential login, emergency access +- **Components**: Login forms, authentication modals +- **Services**: Authentication API, token management +- **Redux**: Auth state, user session management + +### Dashboard Module +**Purpose**: Main ER dashboard with patient monitoring and alerts +- **ERDashboardScreen**: Main dashboard with patient list and statistics +- **PatientCard**: Individual patient information display +- **CriticalAlerts**: High-priority alert notifications +- **QuickActions**: Emergency procedure shortcuts +- **DepartmentStats**: Real-time department overview + +### PatientCare Module +**Purpose**: Comprehensive patient management and medical records +- **PatientDetailsScreen**: Complete patient information +- **VitalSigns**: Real-time vital signs monitoring +- **MedicalHistory**: Patient medical records +- **Medications**: Current medication management +- **Allergies**: Allergy information and alerts + +### Settings Module +**Purpose**: App configuration and user preferences +- **SettingsScreen**: User preferences and app settings +- **NotificationSettings**: Alert and notification configuration +- **SecuritySettings**: Authentication and security options +- **AboutScreen**: App information and version details + +## ๐Ÿ“ File Naming Conventions + +### Components +- **PascalCase** for all component files +- **Suffix with type**: `.tsx` for components, `.ts` for utilities +- **Examples**: `LoginScreen.tsx`, `PatientCard.tsx`, `CriticalAlerts.tsx` + +### Hooks +- **camelCase** with `use` prefix +- **Examples**: `useAuth.ts`, `usePatientList.ts`, `useRealTimeAlerts.ts` + +### Services +- **camelCase** with descriptive names +- **Suffix with type**: `API.ts`, `Service.ts` +- **Examples**: `authAPI.ts`, `patientCareAPI.ts`, `notificationService.ts` + +### Redux +- **camelCase** with descriptive suffixes +- **Examples**: `authSlice.ts`, `erDashboardSlice.ts`, `patientCareActions.ts` + +### Tests +- **Same name as source file** + `.test.ts` or `.test.tsx` +- **Examples**: `LoginScreen.test.tsx`, `useAuth.test.ts`, `authSlice.test.ts` + +## ๐Ÿ”ง Configuration Files + +### Theme System +- **colors.ts**: Complete color palette with healthcare blue theme +- **typography.ts**: Font families, weights, sizes, and spacing +- **spacing.ts**: Spacing scale, border radius, breakpoints +- **shadows.ts**: Shadow system for elevation +- **animations.ts**: Animation durations and easing functions +- **theme.ts**: Main theme object combining all design tokens + +### Type Definitions +- **auth.ts**: Authentication types and interfaces +- **patient.ts**: Patient data and medical record types +- **dashboard.ts**: Dashboard and ER management types +- **alerts.ts**: Alert and notification types +- **common.ts**: Common utility types and interfaces + +### Utilities +- **constants.ts**: App constants, API configs, timeouts +- **helpers.ts**: Common utility functions and helpers +- **validators.ts**: Form validation and data validation +- **formatters.ts**: Data formatting and display utilities +- **medicalUtils.ts**: Medical-specific utility functions + +## ๐ŸŽจ Design System + +### Color Palette +- **Primary**: #2196F3 (Material Blue) +- **Secondary**: #1976D2 (Darker Blue) +- **Critical**: #F44336 (Material Red) +- **Warning**: #FF9800 (Material Orange) +- **Success**: #4CAF50 (Material Green) + +### Component Library +- **UI Components**: Basic building blocks (Button, Input, Card, etc.) +- **Form Components**: Form-specific components with validation +- **Icon Components**: Medical and UI icons +- **Layout Components**: Container and layout components + +### Responsive Design +- **Mobile First**: Optimized for mobile devices +- **Tablet Support**: Enhanced layouts for tablets +- **Breakpoints**: Mobile (375px), Tablet (768px), Desktop (1024px) + +## ๐Ÿš€ Getting Started + +1. **Clone the repository** +2. **Run setup script**: `./setup.sh` (Unix) or `setup.bat` (Windows) +3. **Start development server**: `npm start` +4. **Run on device**: `npm run android` or `npm run ios` + +## ๐Ÿ“ฑ Key Features Implemented + +### โœ… Completed +- **Theme System**: Complete design system with healthcare blue theme +- **Authentication**: Login screen with SSO and credential options +- **ER Dashboard**: Main dashboard with patient cards and statistics +- **Patient Cards**: Comprehensive patient information display +- **Critical Alerts**: High-priority alert system +- **Quick Actions**: Emergency procedure shortcuts +- **Department Stats**: Real-time department overview +- **Navigation**: Tab-based navigation structure +- **Type Safety**: Complete TypeScript implementation + +### ๐Ÿ”„ In Progress +- **Redux Store**: State management implementation +- **API Integration**: Backend service integration +- **Real-time Updates**: WebSocket implementation +- **Push Notifications**: Critical alert notifications + +### ๐Ÿ“‹ Planned +- **Patient Details**: Comprehensive patient management screens +- **Medical Records**: Complete medical history management +- **Settings**: App configuration and preferences +- **Offline Support**: Offline data access and sync +- **Testing**: Comprehensive test suite +- **Documentation**: Complete API and user documentation + +This structure provides a solid foundation for a comprehensive healthcare application with proper separation of concerns, type safety, and modern React Native best practices. + +/* + * End of File: PROJECT_STRUCTURE.md + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/README.md b/README.md index 3e2c3f8..5389453 100644 --- a/README.md +++ b/README.md @@ -1,97 +1,317 @@ -This is a new [**React Native**](https://reactnative.dev) project, bootstrapped using [`@react-native-community/cli`](https://github.com/react-native-community/cli). +/* + * File: README.md + * Description: Project documentation and setup instructions + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ -# Getting Started +# NeoScan Physician App -> **Note**: Make sure you have completed the [Set Up Your Environment](https://reactnative.dev/docs/set-up-your-environment) guide before proceeding. +A comprehensive React Native application designed for emergency department physicians to manage patient care, critical alerts, and medical workflows in real-time. -## Step 1: Start Metro +## ๐Ÿš€ Features -First, you will need to run **Metro**, the JavaScript build tool for React Native. +### Authentication & Security +- **Hospital SSO Integration** - Seamless login with hospital credentials +- **Multi-factor Authentication** - Enhanced security for medical data +- **Emergency Access** - Quick access codes for urgent situations +- **Session Management** - 8-hour sessions with 30-minute inactivity timeout +- **Device Remembering** - 30-day trusted device authentication -To start the Metro dev server, run the following command from the root of your React Native project: +### ER Dashboard +- **Real-time Patient Monitoring** - Live updates of patient status and vital signs +- **Critical Alert System** - Immediate notifications for life-threatening conditions +- **Department Statistics** - Overview of emergency, trauma, cardiac, neurology, pediatrics, and ICU +- **Quick Actions** - Emergency procedures, scan orders, medication, lab work, consultations +- **Patient Filtering** - Filter by status (All, Critical, Active) -```sh -# Using npm -npm start +### Patient Management +- **Comprehensive Patient Cards** - Vital signs, allergies, medications, diagnosis +- **Medical History** - Complete patient medical records +- **Real-time Vital Signs** - Blood pressure, heart rate, temperature, respiratory rate, oxygen saturation +- **Allergy Alerts** - Prominent display of patient allergies +- **Medication Tracking** - Current medications with dosages and schedules -# OR using Yarn -yarn start +### Critical Finding Response +- **AI-Powered Detection** - Automated critical finding identification +- **Immediate Alert System** - Push notifications for urgent cases +- **Response Timeline** - 0-30 seconds acknowledgment, 2-5 minutes action +- **Treatment Protocols** - Quick access to emergency procedures + +## ๐Ÿ—๏ธ Architecture + +### Project Structure +``` +NeoScan_Physician/ +โ”œโ”€โ”€ app/ # Main application code +โ”‚ โ”œโ”€โ”€ modules/ # Feature-based modules +โ”‚ โ”‚ โ”œโ”€โ”€ Auth/ # Authentication module +โ”‚ โ”‚ โ”œโ”€โ”€ Dashboard/ # ER Dashboard module +โ”‚ โ”‚ โ”œโ”€โ”€ PatientCare/ # Patient management module +โ”‚ โ”‚ โ””โ”€โ”€ Settings/ # App settings module +โ”‚ โ”œโ”€โ”€ shared/ # Shared utilities & components +โ”‚ โ”‚ โ”œโ”€โ”€ components/ # Reusable UI components +โ”‚ โ”‚ โ”œโ”€โ”€ utils/ # Utility functions +โ”‚ โ”‚ โ””โ”€โ”€ types/ # TypeScript type definitions +โ”‚ โ”œโ”€โ”€ theme/ # Design system & theming +โ”‚ โ”œโ”€โ”€ navigation/ # Navigation setup +โ”‚ โ”œโ”€โ”€ store/ # Redux state management +โ”‚ โ”œโ”€โ”€ config/ # Configuration files +โ”‚ โ””โ”€โ”€ assets/ # Static assets +โ”œโ”€โ”€ android/ # Android native code +โ”œโ”€โ”€ ios/ # iOS native code +โ””โ”€โ”€ docs/ # Documentation ``` -## Step 2: Build and run your app +### Technology Stack +- **React Native 0.79.0** - Cross-platform mobile development +- **TypeScript** - Type-safe development +- **React Navigation 6** - Navigation management +- **Redux Toolkit** - State management +- **React Native Vector Icons** - Icon library +- **React Native Push Notification** - Real-time notifications +- **React Native Keychain** - Secure credential storage -With Metro running, open a new terminal window/pane from the root of your React Native project, and use one of the following commands to build and run your Android or iOS app: +## ๐ŸŽจ Design System -### Android +### Color Palette - "Modern Healthcare Blue" +- **Primary**: #2196F3 (Material Blue) +- **Secondary**: #1976D2 (Darker Blue) +- **Critical**: #F44336 (Material Red) +- **Warning**: #FF9800 (Material Orange) +- **Success**: #4CAF50 (Material Green) -```sh -# Using npm -npm run android +### Typography +- **Primary Font**: Roboto +- **Font Weights**: Light (300), Regular (400), Medium (500), Bold (700) +- **Font Sizes**: Display (32px, 24px, 20px), Body (16px, 14px, 12px), Caption (10px) -# OR using Yarn -yarn android +### Components +- **Patient Cards** - Comprehensive patient information display +- **Critical Alerts** - High-priority notification system +- **Quick Actions** - Emergency procedure shortcuts +- **Department Stats** - Real-time department overview +- **Dashboard Header** - ER statistics and shift information + +## ๐Ÿ“ฑ Screens + +### Authentication Flow +1. **Splash Screen** - App initialization and authentication check +2. **Login Screen** - Hospital SSO, credential login, emergency access +3. **Main Dashboard** - ER overview with patient list and alerts + +### Main Application +1. **ER Dashboard** - Real-time patient monitoring and critical alerts +2. **Patient Details** - Comprehensive patient information and medical history +3. **Alerts Center** - Critical finding notifications and response +4. **Reports** - Medical reports and scan results +5. **Settings** - User preferences and app configuration + +## ๐Ÿ”ง Setup & Installation + +### Prerequisites +- Node.js >= 18 +- React Native CLI +- Android Studio (for Android development) +- Xcode (for iOS development) + +### Installation Steps + +1. **Clone the repository** + ```bash + git clone + cd NeoScan_Physician + ``` + +2. **Install dependencies** + ```bash + npm install + ``` + +3. **iOS Setup** (macOS only) + ```bash + cd ios + pod install + cd .. + ``` + +4. **Start the development server** + ```bash + npm start + ``` + +5. **Run on device/simulator** + ```bash + # Android + npm run android + + # iOS + npm run ios + ``` + +## ๐Ÿšจ Critical Alert Workflow + +### Alert Reception (0-30 seconds) +- Push notification received +- Alert screen displayed +- Patient context loaded +- AI summary generated + +### Assessment (30-60 seconds) +- Patient details reviewed +- Medical history checked +- Current status assessed +- Urgency level determined + +### Action Planning (1-2 minutes) +- Treatment protocol loaded +- Specialist consultation initiated +- Emergency procedures prepared +- Documentation started + +### Implementation (2-5 minutes) +- Actions executed +- Status updated +- Team notified +- Record documented + +## ๐Ÿ” Security Features + +### Authentication +- **Session Management**: 8-hour timeout +- **Auto-logout**: Inactivity after 30 minutes +- **Device Remembering**: 30-day trusted devices +- **Emergency Access**: Limited functionality +- **Audit Trail**: All actions logged + +### Data Protection +- **Encryption**: End-to-end data encryption +- **HIPAA Compliance**: Healthcare data protection +- **Secure Storage**: Encrypted local storage +- **Network Security**: HTTPS/TLS communication + +## ๐Ÿ“Š Performance Optimization + +### Loading States +- **Critical Alerts**: Immediate loading +- **Patient List**: Fast loading with caching +- **Patient Details**: Medium loading +- **Medical History**: Optimized loading +- **Full Reports**: Background loading + +### Offline Capabilities +- **Critical Alerts**: Always available +- **Patient List**: Cached data access +- **Recent Reports**: Offline viewing +- **Settings**: Local storage +- **Sync**: Automatic when online + +## ๐Ÿงช Testing + +### Test Structure +- **Unit Tests**: Component and utility testing +- **Integration Tests**: Module interaction testing +- **E2E Tests**: Complete workflow testing +- **Performance Tests**: Load and stress testing + +### Running Tests +```bash +# Unit tests +npm test + +# E2E tests +npm run test:e2e + +# Performance tests +npm run test:performance ``` -### iOS +## ๐Ÿ“ฑ Platform Support -For iOS, remember to install CocoaPods dependencies (this only needs to be run on first clone or after updating native deps). +### iOS Features +- Face ID authentication +- Apple Health integration +- Siri shortcuts +- iOS notifications -The first time you create a new project, run the Ruby bundler to install CocoaPods itself: +### Android Features +- Fingerprint authentication +- Google Fit integration +- Android Auto +- Android notifications -```sh -bundle install -``` +## ๐Ÿ”„ Real-time Updates -Then, and every time you update your native dependencies, run: +### WebSocket Integration +- **Patient Status**: Real-time updates +- **Vital Signs**: 30-second intervals +- **Alert Status**: Immediate updates +- **Bed Assignments**: Real-time changes +- **Report Status**: 5-minute intervals -```sh -bundle exec pod install -``` +### Data Synchronization +- **Server Priority**: Server data overrides local +- **Timestamp Comparison**: Latest data wins +- **User Confirmation**: Manual resolution for conflicts +- **Audit Trail**: All changes tracked -For more information, please visit [CocoaPods Getting Started guide](https://guides.cocoapods.org/using/getting-started.html). +## ๐Ÿ“‹ Development Guidelines -```sh -# Using npm -npm run ios +### Code Style +- **TypeScript**: Strict type checking +- **ESLint**: Code quality enforcement +- **Prettier**: Code formatting +- **Conventional Commits**: Git commit messages -# OR using Yarn -yarn ios -``` +### Component Guidelines +- **Single Responsibility**: One component, one purpose +- **Reusability**: Shared components in shared/ +- **Type Safety**: Full TypeScript coverage +- **Accessibility**: WCAG 2.1 compliance -If everything is set up correctly, you should see your new app running in the Android Emulator, iOS Simulator, or your connected device. +### Performance Guidelines +- **Lazy Loading**: Components loaded on demand +- **Memoization**: React.memo for expensive components +- **Image Optimization**: Compressed and cached images +- **Bundle Size**: Minimal dependencies -This is one way to run your app โ€” you can also build it directly from Android Studio or Xcode. +## ๐Ÿš€ Deployment -## Step 3: Modify your app +### Build Configuration +- **Environment Variables**: Separate configs for dev/staging/prod +- **Code Signing**: Proper certificate management +- **Bundle Optimization**: Minified and optimized builds +- **Asset Management**: Optimized images and fonts -Now that you have successfully run the app, let's make changes! +### Release Process +1. **Development**: Feature development and testing +2. **Staging**: Integration testing and QA +3. **Production**: Final testing and deployment +4. **Monitoring**: Performance and error tracking -Open `App.tsx` in your text editor of choice and make some changes. When you save, your app will automatically update and reflect these changes โ€”ย this is powered by [Fast Refresh](https://reactnative.dev/docs/fast-refresh). +## ๐Ÿ“ž Support -When you want to forcefully reload, for example to reset the state of your app, you can perform a full reload: +### Documentation +- **API Documentation**: Complete API reference +- **User Guide**: End-user documentation +- **Developer Guide**: Technical documentation +- **Troubleshooting**: Common issues and solutions -- **Android**: Press the R key twice or select **"Reload"** from the **Dev Menu**, accessed via Ctrl + M (Windows/Linux) or Cmd โŒ˜ + M (macOS). -- **iOS**: Press R in iOS Simulator. +### Contact +- **Technical Support**: dev-support@neoscan.com +- **Emergency Support**: emergency-support@neoscan.com +- **Feature Requests**: features@neoscan.com -## Congratulations! :tada: +## ๐Ÿ“„ License -You've successfully run and modified your React Native App. :partying_face: +This project is proprietary software developed for healthcare institutions. All rights reserved. -### Now what? +--- -- If you want to add this new React Native code to an existing application, check out the [Integration guide](https://reactnative.dev/docs/integration-with-existing-apps). -- If you're curious to learn more about React Native, check out the [docs](https://reactnative.dev/docs/getting-started). +**NeoScan Physician App** - Empowering emergency care with real-time intelligence and seamless workflows. -# Troubleshooting - -If you're having issues getting the above steps to work, see the [Troubleshooting](https://reactnative.dev/docs/troubleshooting) page. - -# Learn More - -To learn more about React Native, take a look at the following resources: - -- [React Native Website](https://reactnative.dev) - learn more about React Native. -- [Getting Started](https://reactnative.dev/docs/environment-setup) - an **overview** of React Native and how setup your environment. -- [Learn the Basics](https://reactnative.dev/docs/getting-started) - a **guided tour** of the React Native **basics**. -- [Blog](https://reactnative.dev/blog) - read the latest official React Native **Blog** posts. -- [`@facebook/react-native`](https://github.com/facebook/react-native) - the Open Source; GitHub **repository** for React Native. +/* + * End of File: README.md + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ diff --git a/THEME_FLOW.md b/THEME_FLOW.md new file mode 100644 index 0000000..39812e1 --- /dev/null +++ b/THEME_FLOW.md @@ -0,0 +1,618 @@ +# Physician App - Theme System & Design Flow + +## ๐ŸŽจ Design System Overview + +### **Project Theme Structure** +``` +app/theme/ +โ”œโ”€โ”€ theme.ts # Main theme configuration +โ”œโ”€โ”€ colors.ts # Color palette definitions +โ”œโ”€โ”€ typography.ts # Font families, sizes, weights +โ”œโ”€โ”€ spacing.ts # Spacing scale and breakpoints +โ”œโ”€โ”€ shadows.ts # Shadow and elevation system +โ””โ”€โ”€ animations.ts # Animation durations and easing +``` + +--- + +## ๐ŸŽฏ Color Palette - "Modern Healthcare Blue" + +### **Primary Colors** +```typescript +Primary: '#2196F3' // Material Blue - Main brand color +Secondary: '#1976D2' // Darker Blue - Secondary actions +Tertiary: '#E3F2FD' // Very Light Blue - Backgrounds +Quaternary: '#0D47A1' // Deep Blue - Accents +``` + +### **Text Colors** +```typescript +TextPrimary: '#212121' // Dark Gray - Main text +TextSecondary: '#757575' // Medium Gray - Secondary text +TextMuted: '#9E9E9E' // Light Gray - Muted text +``` + +### **Background Colors** +```typescript +Background: '#FFFFFF' // White - Primary background +BackgroundAlt: '#FAFAFA' // Light Gray - Alternative background +BackgroundAccent: '#F5F5F5' // Soft Gray - Accent backgrounds +``` + +### **Status & Feedback Colors** +```typescript +Success: '#4CAF50' // Material Green - Success states +Warning: '#FF9800' // Material Orange - Warning states +Error: '#F44336' // Material Red - Error states +Info: '#2196F3' // Material Blue - Information states +Critical: '#F44336' // Critical alerts and emergencies +``` + +### **UI Elements** +```typescript +Border: '#E0E0E0' // Light Gray Border +CardBackground: '#FFFFFF' // White - Card backgrounds +Shadow: 'rgba(0, 0, 0, 0.1)' // Subtle Gray Shadow +``` + +--- + +## ๐Ÿ“ Typography System + +### **Font Families (Roboto)** +```typescript +// Available Font Families +fontFamily: { + bold: 'Roboto-Bold', + medium: 'Roboto-Medium', + regular: 'Roboto-Regular', + light: 'Roboto-Light', + semibold: 'Roboto-SemiBold', + extrabold: 'Roboto-ExtraBold', +} +``` + +### **Font Weights** +```typescript +fontWeight: { + light: '300', + regular: '400', + medium: '500', + bold: '700', +} +``` + +### **Font Sizes** +```typescript +fontSize: { + displayLarge: 32, // Main headings + displayMedium: 24, // Section headings + displaySmall: 20, // Subsection headings + bodyLarge: 16, // Body text + bodyMedium: 14, // Secondary text + bodySmall: 12, // Captions + caption: 10, // Small labels +} +``` + +### **Line Heights & Spacing** +```typescript +lineHeight: { + tight: 1.2, // Headings + normal: 1.4, // Body text + relaxed: 1.6, // Long text +} + +letterSpacing: { + tight: -0.5, // Headings + normal: 0, // Body text + wide: 0.5, // Labels +} +``` + +--- + +## ๐Ÿ“ Spacing & Layout + +### **Spacing Scale (Base: 4px)** +```typescript +spacing: { + xs: 4, // 4px + sm: 8, // 8px + md: 16, // 16px + lg: 24, // 24px + xl: 32, // 32px + xxl: 48, // 48px + xxxl: 64 // 64px +} +``` + +### **Border Radius** +```typescript +borderRadius: { + small: 4, + medium: 8, + large: 12, + xlarge: 16, + round: 50, +} +``` + +### **Breakpoints** +```typescript +breakpoints: { + mobile: 375, + tablet: 768, + desktop: 1024, + largeDesktop: 1440, +} +``` + +--- + +## ๐ŸŽจ Icon System + +### **React Native Vector Icons** + +#### **Available Icon Sets** +```typescript +// Primary Icon Sets +import Icon from 'react-native-vector-icons/Feather'; // Clean, minimal icons +import Icon from 'react-native-vector-icons/MaterialIcons'; // Material Design icons +import Icon from 'react-native-vector-icons/Ionicons'; // iOS-style icons +``` + +#### **Icon Usage Guidelines** +```typescript +// Standard Icon Implementation + + +// Icon Sizes +IconSizes: { + xs: 12, // Extra small + sm: 16, // Small + md: 24, // Medium (default) + lg: 32, // Large + xl: 48 // Extra large +} +``` + +#### **Common Icon Names** +```typescript +// Navigation Icons +'home', 'menu', 'arrow-left', 'arrow-right', 'chevron-down', 'chevron-up' + +// Action Icons +'plus', 'minus', 'edit', 'delete', 'save', 'cancel', 'check', 'close' + +// Medical Icons +'heart', 'user', 'settings', 'bell', 'search', 'filter', 'calendar' + +// Status Icons +'check-circle', 'alert-circle', 'info', 'warning', 'error' +``` + +--- + +## ๐Ÿ—๏ธ Component Design Rules + +### **Button Design System** +```typescript +// Button Variants +Primary: { + backgroundColor: theme.colors.primary, + borderColor: theme.colors.primary, + borderRadius: theme.borderRadius.medium, + shadowColor: theme.colors.primary, + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.3, + shadowRadius: 4, + elevation: 3 +} + +Secondary: { + backgroundColor: 'transparent', + borderColor: theme.colors.primary, + borderRadius: theme.borderRadius.medium +} + +Success: { + backgroundColor: theme.colors.success, + borderColor: theme.colors.success, + borderRadius: theme.borderRadius.medium, + shadowColor: theme.colors.success, + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.3, + shadowRadius: 4, + elevation: 3 +} + +Critical: { + backgroundColor: theme.colors.critical, + borderColor: theme.colors.critical, + borderRadius: theme.borderRadius.medium, + shadowColor: theme.colors.critical, + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.3, + shadowRadius: 4, + elevation: 3 +} +``` + +### **Input Field Design** +```typescript +// Input States +Default: { + borderColor: theme.colors.border, + backgroundColor: theme.colors.background, + borderRadius: theme.borderRadius.medium, + paddingHorizontal: theme.spacing.md, + paddingVertical: theme.spacing.md +} + +Focused: { + borderColor: theme.colors.primary, + backgroundColor: theme.colors.background, + borderRadius: theme.borderRadius.medium, + paddingHorizontal: theme.spacing.md, + paddingVertical: theme.spacing.md +} + +Error: { + borderColor: theme.colors.critical, + backgroundColor: theme.colors.criticalBackground, + borderRadius: theme.borderRadius.medium, + paddingHorizontal: theme.spacing.md, + paddingVertical: theme.spacing.md +} +``` + +### **Card Design Rules** +```typescript +// Card Variants +Default: { + backgroundColor: theme.colors.cardBackground, + borderColor: theme.colors.border, + borderRadius: theme.borderRadius.large, + padding: theme.spacing.lg, + shadowColor: theme.colors.shadow, + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.1, + shadowRadius: 4, + elevation: 2 +} + +Elevated: { + backgroundColor: theme.colors.cardBackground, + borderColor: theme.colors.border, + borderRadius: theme.borderRadius.large, + padding: theme.spacing.lg, + shadowColor: theme.colors.shadow, + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.15, + shadowRadius: 8, + elevation: 4 +} +``` + +--- + +## ๐Ÿšจ Alert & Status Design + +### **Alert Priority System** +```typescript +// Critical Alerts +Critical: { + backgroundColor: theme.colors.criticalBackground, + borderColor: theme.colors.critical, + borderRadius: theme.borderRadius.medium, + padding: theme.spacing.md +} + +// Warning Alerts +Warning: { + backgroundColor: theme.colors.warningBackground, + borderColor: theme.colors.warning, + borderRadius: theme.borderRadius.medium, + padding: theme.spacing.md +} + +// Success Alerts +Success: { + backgroundColor: theme.colors.successBackground, + borderColor: theme.colors.success, + borderRadius: theme.borderRadius.medium, + padding: theme.spacing.md +} + +// Info Alerts +Info: { + backgroundColor: theme.colors.infoBackground, + borderColor: theme.colors.info, + borderRadius: theme.borderRadius.medium, + padding: theme.spacing.md +} +``` + +### **Status Badges** +```typescript +// Status Badges +StatusBadge: { + paddingHorizontal: theme.spacing.sm, + paddingVertical: theme.spacing.xs, + borderRadius: theme.borderRadius.round, + fontSize: theme.typography.fontSize.bodySmall, + fontWeight: theme.typography.fontWeight.medium +} + +// Priority Badges +PriorityBadge: { + paddingHorizontal: theme.spacing.md, + paddingVertical: theme.spacing.sm, + borderRadius: theme.borderRadius.round, + fontSize: theme.typography.fontSize.bodySmall, + fontWeight: theme.typography.fontWeight.bold +} +``` + +--- + +## ๐Ÿ“ฑ Screen-Specific Design Rules + +### **Login Screen Design** +```typescript +// Login Container +LoginContainer: { + backgroundColor: theme.colors.background, + padding: theme.spacing.lg, + borderRadius: theme.borderRadius.large, + shadowColor: theme.colors.shadow, + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.15, + shadowRadius: 8, + elevation: 4 +} + +// Input Container with Icons +InputContainer: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: theme.colors.backgroundAlt, + borderColor: theme.colors.border, + borderWidth: 1, + borderRadius: theme.borderRadius.medium, + paddingHorizontal: theme.spacing.md, + paddingVertical: 2, + marginBottom: theme.spacing.md +} + +// Input Icon +InputIcon: { + marginRight: theme.spacing.sm, + color: theme.colors.textSecondary +} +``` + +### **Dashboard Screen Design** +```typescript +// Header Section +Header: { + backgroundColor: theme.colors.background, + paddingHorizontal: theme.spacing.md, + paddingVertical: theme.spacing.sm, + borderBottomColor: theme.colors.border, + borderBottomWidth: 1 +} + +// Critical Alerts Section +CriticalAlerts: { + backgroundColor: theme.colors.criticalBackground, + borderColor: theme.colors.critical, + borderRadius: theme.borderRadius.large, + padding: theme.spacing.lg, + marginHorizontal: theme.spacing.md, + marginVertical: theme.spacing.md, + shadowColor: theme.colors.critical, + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.15, + shadowRadius: 8, + elevation: 4 +} +``` + +--- + +## ๐Ÿ”„ Animation & Transitions + +### **Animation Rules** +```typescript +// Animation Durations +Durations: { + Fast: 150, // Quick interactions + Normal: 300, // Standard transitions + Slow: 500 // Complex animations +} + +// Easing Functions +Easing: { + Standard: 'cubic-bezier(0.4, 0.0, 0.2, 1)', + Deceleration: 'cubic-bezier(0.0, 0.0, 0.2, 1)', + Acceleration: 'cubic-bezier(0.4, 0.0, 1, 1)' +} + +// Transition Types +Transitions: { + Fade: { opacity: [0, 1], duration: 300 }, + Slide: { transform: [{ translateY: [20, 0] }], duration: 300 }, + Scale: { transform: [{ scale: [0.95, 1] }], duration: 200 } +} +``` + +--- + +## ๐ŸŽฏ Usage Guidelines + +### **Theme Import** +```typescript +// Import theme in components +import { theme } from '../theme/theme'; + +// Usage in StyleSheet +const styles = StyleSheet.create({ + container: { + backgroundColor: theme.colors.background, + padding: theme.spacing.md, + borderRadius: theme.borderRadius.medium, + shadowColor: theme.colors.shadow, + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.1, + shadowRadius: 4, + elevation: 2 + }, + title: { + fontSize: theme.typography.fontSize.displayMedium, + fontWeight: theme.typography.fontWeight.bold, + color: theme.colors.textPrimary, + fontFamily: theme.typography.fontFamily.bold + } +}); +``` + +### **Icon Implementation** +```typescript +// Import icon +import Icon from 'react-native-vector-icons/Feather'; + +// Usage in components + +``` + +### **Font Usage** +```typescript +// Typography with font families +const styles = StyleSheet.create({ + heading: { + fontSize: theme.typography.fontSize.displayMedium, + fontWeight: theme.typography.fontWeight.bold, + fontFamily: theme.typography.fontFamily.bold, + color: theme.colors.textPrimary, + lineHeight: theme.typography.lineHeight.tight + }, + body: { + fontSize: theme.typography.fontSize.bodyLarge, + fontWeight: theme.typography.fontWeight.regular, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.textSecondary, + lineHeight: theme.typography.lineHeight.normal + } +}); +``` + +--- + +## ๐Ÿ”ง Configuration Files + +### **TypeScript Declaration** +```typescript +// app/types/react-native-vector-icons.d.ts +declare module 'react-native-vector-icons/Feather' { + import { Component } from 'react'; + import { TextProps } from 'react-native'; + + interface IconProps extends TextProps { + name: string; + size?: number; + color?: string; + } + + export default class Icon extends Component {} +} +``` + +### **Font Configuration** +```json +// react-native.config.js +module.exports = { + assets: ['./app/assets/fonts/'], +}; +``` + +--- + +## ๐Ÿ“‹ Best Practices + +### **Design Consistency** +- โœ… Always use theme colors instead of hardcoded values +- โœ… Use consistent spacing from the spacing scale +- โœ… Apply proper typography hierarchy +- โœ… Use appropriate icon sizes and colors +- โœ… Maintain consistent border radius values + +### **Performance** +- โœ… Use StyleSheet.create for all styles +- โœ… Minimize inline styles +- โœ… Use theme constants for repeated values +- โœ… Optimize icon usage with proper sizing + +### **Accessibility** +- โœ… Maintain proper color contrast ratios +- โœ… Use semantic color names +- โœ… Provide adequate touch targets (44px minimum) +- โœ… Support dynamic text sizing + +### **Maintainability** +- โœ… Keep theme centralized and well-documented +- โœ… Use semantic naming for colors and spacing +- โœ… Document any custom theme extensions +- โœ… Version control theme changes + +--- + +## ๐Ÿš€ Quick Reference + +### **Common Theme Values** +```typescript +// Colors +theme.colors.primary // #2196F3 +theme.colors.textPrimary // #212121 +theme.colors.background // #FFFFFF +theme.colors.critical // #F44336 + +// Spacing +theme.spacing.md // 16px +theme.spacing.lg // 24px +theme.spacing.xl // 32px + +// Typography +theme.typography.fontSize.bodyLarge // 16px +theme.typography.fontWeight.bold // 700 +theme.typography.fontFamily.bold // 'Roboto-Bold' + +// Border Radius +theme.borderRadius.medium // 8px +theme.borderRadius.large // 12px +theme.borderRadius.round // 50px +``` + +### **Icon Quick Reference** +```typescript +// Common Icons + + + + + + +``` + +This comprehensive theme system ensures consistency, accessibility, and maintainability across the Physician App while providing a modern healthcare-focused design experience. \ No newline at end of file diff --git a/android/app/build.gradle b/android/app/build.gradle index 278d477..0c8fa09 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,6 +1,7 @@ apply plugin: "com.android.application" apply plugin: "org.jetbrains.kotlin.android" apply plugin: "com.facebook.react" +apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle" /** * This is the configuration block to customize your React Native Android app. @@ -117,3 +118,5 @@ dependencies { implementation jscFlavor } } + +apply from: file("../../node_modules/react-native-vector-icons/fonts.gradle") diff --git a/android/app/src/main/assets/fonts/Roboto-Black.ttf b/android/app/src/main/assets/fonts/Roboto-Black.ttf new file mode 100644 index 0000000..d51221a Binary files /dev/null and b/android/app/src/main/assets/fonts/Roboto-Black.ttf differ diff --git a/android/app/src/main/assets/fonts/Roboto-Bold.ttf b/android/app/src/main/assets/fonts/Roboto-Bold.ttf new file mode 100644 index 0000000..9d7cf22 Binary files /dev/null and b/android/app/src/main/assets/fonts/Roboto-Bold.ttf differ diff --git a/android/app/src/main/assets/fonts/Roboto-ExtraBold.ttf b/android/app/src/main/assets/fonts/Roboto-ExtraBold.ttf new file mode 100644 index 0000000..7092a88 Binary files /dev/null and b/android/app/src/main/assets/fonts/Roboto-ExtraBold.ttf differ diff --git a/android/app/src/main/assets/fonts/Roboto-ExtraLight.ttf b/android/app/src/main/assets/fonts/Roboto-ExtraLight.ttf new file mode 100644 index 0000000..75608c6 Binary files /dev/null and b/android/app/src/main/assets/fonts/Roboto-ExtraLight.ttf differ diff --git a/android/app/src/main/assets/fonts/Roboto-Light.ttf b/android/app/src/main/assets/fonts/Roboto-Light.ttf new file mode 100644 index 0000000..6fcd5f9 Binary files /dev/null and b/android/app/src/main/assets/fonts/Roboto-Light.ttf differ diff --git a/android/app/src/main/assets/fonts/Roboto-Medium.ttf b/android/app/src/main/assets/fonts/Roboto-Medium.ttf new file mode 100644 index 0000000..d629e98 Binary files /dev/null and b/android/app/src/main/assets/fonts/Roboto-Medium.ttf differ diff --git a/android/app/src/main/assets/fonts/Roboto-Regular.ttf b/android/app/src/main/assets/fonts/Roboto-Regular.ttf new file mode 100644 index 0000000..7e3bb2f Binary files /dev/null and b/android/app/src/main/assets/fonts/Roboto-Regular.ttf differ diff --git a/android/app/src/main/assets/fonts/Roboto-SemiBold.ttf b/android/app/src/main/assets/fonts/Roboto-SemiBold.ttf new file mode 100644 index 0000000..3f34834 Binary files /dev/null and b/android/app/src/main/assets/fonts/Roboto-SemiBold.ttf differ diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 37f853b..df97d72 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/android/link-assets-manifest.json b/android/link-assets-manifest.json new file mode 100644 index 0000000..441aa21 --- /dev/null +++ b/android/link-assets-manifest.json @@ -0,0 +1,37 @@ +{ + "migIndex": 1, + "data": [ + { + "path": "app/assets/fonts/Roboto-Black.ttf", + "sha1": "d1678489a8d5645f16486ec52d77b651ff0bf327" + }, + { + "path": "app/assets/fonts/Roboto-Bold.ttf", + "sha1": "508c35dee818addce6cc6d1fb6e42f039da5a7cf" + }, + { + "path": "app/assets/fonts/Roboto-ExtraBold.ttf", + "sha1": "3dbfd71b6fbcfbd8e7ee8a8dd033dc5aaad63249" + }, + { + "path": "app/assets/fonts/Roboto-ExtraLight.ttf", + "sha1": "df556e64732e5c272349e13cb5f87591a1ae779b" + }, + { + "path": "app/assets/fonts/Roboto-Light.ttf", + "sha1": "318b44c0a32848f78bf11d4fbf3355d00647a796" + }, + { + "path": "app/assets/fonts/Roboto-Medium.ttf", + "sha1": "fa5192203f85ddb667579e1bdf26f12098bb873b" + }, + { + "path": "app/assets/fonts/Roboto-Regular.ttf", + "sha1": "3bff51436aa7eb995d84cfc592cc63e1316bb400" + }, + { + "path": "app/assets/fonts/Roboto-SemiBold.ttf", + "sha1": "9ca139684fe902c8310dd82991648376ac9838db" + } + ] +} diff --git a/app/App.tsx b/app/App.tsx new file mode 100644 index 0000000..a8d5695 --- /dev/null +++ b/app/App.tsx @@ -0,0 +1,224 @@ +/* + * File: App.tsx + * Description: Main application component with navigation setup + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import React, { useRef } from 'react'; +import { StatusBar } from 'react-native'; +import { SafeAreaProvider } from 'react-native-safe-area-context'; +import { NavigationContainer, NavigationContainerRef } from '@react-navigation/native'; +import { theme } from './theme/theme'; +import { RootStackNavigator, setNavigationRef } from './navigation'; +import { StoreProvider } from './store/Provider'; +import Toast from 'react-native-toast-message'; +import { useAppSelector } from './store/hooks'; +import { selectIsAuthenticated } from './modules/Auth/redux/authSelectors'; + +// ============================================================================ +// MOCK DATA SECTION - For demonstration and development purposes +// ============================================================================ + +// Mock dashboard data representing the current state of the ER +const mockDashboard = { + totalPatients: 24, // Total number of patients in ER + criticalPatients: 3, // Number of patients requiring immediate attention + pendingScans: 8, // Number of scans waiting for review + recentReports: 12, // Number of reports generated recently + bedOccupancy: 85, // Percentage of beds currently occupied + departmentStats: { + emergency: 8, // Patients in emergency department + trauma: 4, // Patients in trauma department + cardiac: 3, // Patients in cardiac department + neurology: 2, // Patients in neurology department + pediatrics: 5, // Patients in pediatrics department + icu: 2, // Patients in ICU + }, + shiftInfo: { + currentShift: 'DAY' as const, // Current shift (DAY/NIGHT) + startTime: new Date(), // Shift start time + endTime: new Date(), // Shift end time + attendingPhysician: 'Dr. Smith', // Lead physician on duty + residents: ['Dr. Johnson', 'Dr. Williams'], // Resident physicians + nurses: ['Nurse Brown', 'Nurse Davis'], // Nursing staff + }, + lastUpdated: new Date(), // Last time dashboard was updated +}; + +// Mock patient data representing real patients in the ER +const mockPatients = [ + { + id: '1', // Unique patient identifier + mrn: 'MRN001', // Medical Record Number + firstName: 'John', + lastName: 'Doe', + dateOfBirth: new Date('1985-03-15'), + gender: 'MALE' as const, + age: 38, + bedNumber: 'A1', // Assigned bed number + roomNumber: '101', // Room number + admissionDate: new Date('2024-01-15'), + status: 'ACTIVE' as const, // Current patient status + priority: 'CRITICAL' as const, // Priority level for treatment + department: 'Emergency', + attendingPhysician: 'Dr. Smith', + allergies: [ + { + id: '1', + name: 'Penicillin', + severity: 'SEVERE' as const, + reaction: 'Anaphylaxis' + }, + ], + medications: [ + { + id: '1', + name: 'Morphine', + dosage: '2mg', + frequency: 'Every 4 hours', + route: 'IV', // Administration route + startDate: new Date(), + status: 'ACTIVE' as const, + prescribedBy: 'Dr. Smith', + }, + ], + vitalSigns: { + bloodPressure: { systolic: 140, diastolic: 90, timestamp: new Date() }, + heartRate: { value: 95, timestamp: new Date() }, + temperature: { value: 37.2, timestamp: new Date() }, + respiratoryRate: { value: 18, timestamp: new Date() }, + oxygenSaturation: { value: 98, timestamp: new Date() }, + }, + medicalHistory: [], + currentDiagnosis: 'Chest pain, rule out MI', // Current medical diagnosis + lastUpdated: new Date(), + }, + { + id: '2', + mrn: 'MRN002', + firstName: 'Jane', + lastName: 'Smith', + dateOfBirth: new Date('1990-07-22'), + gender: 'FEMALE' as const, + age: 33, + bedNumber: 'B2', + roomNumber: '102', + admissionDate: new Date('2024-01-15'), + status: 'ACTIVE' as const, + priority: 'HIGH' as const, + department: 'Trauma', + attendingPhysician: 'Dr. Johnson', + allergies: [], + medications: [], + vitalSigns: { + bloodPressure: { systolic: 120, diastolic: 80, timestamp: new Date() }, + heartRate: { value: 88, timestamp: new Date() }, + temperature: { value: 36.8, timestamp: new Date() }, + respiratoryRate: { value: 16, timestamp: new Date() }, + oxygenSaturation: { value: 99, timestamp: new Date() }, + }, + medicalHistory: [], + currentDiagnosis: 'Multiple trauma from MVA', // MVA = Motor Vehicle Accident + lastUpdated: new Date(), + }, +]; + +// Mock alerts representing critical notifications that require immediate attention +const mockAlerts = [ + { + id: '1', + type: 'CRITICAL_FINDING' as const, // Type of alert + priority: 'CRITICAL' as const, // Priority level + title: 'Critical Finding Detected', + message: 'AI has detected a potential brain bleed in CT scan. Immediate review required.', + patientId: '1', // Associated patient + patientName: 'John Doe', + bedNumber: 'A1', + timestamp: new Date(), // When alert was generated + isRead: false, // Whether alert has been read + isAcknowledged: false, // Whether alert has been acknowledged + actionRequired: true, // Whether action is required + }, +]; + +/** + * AppContent Component (Inner Component) + * + * Purpose: Inner component that uses Redux hooks for authentication state + * + * Features: + * 1. Connect to Redux store for authentication state + * 2. Set up navigation container with global reference + * 3. Configure status bar appearance + * 4. Render the main app navigator based on authentication state + * + * Navigation Flow: + * 1. App starts โ†’ Check Redux authentication status + * 2. If not authenticated โ†’ Show LoginScreen + * 3. If authenticated โ†’ Show MainTabNavigator (dashboard) + */ +function AppContent() { + // ============================================================================ + // REDUX STATE + // ============================================================================ + + // Get authentication state from Redux + const isAuthenticated = useAppSelector(selectIsAuthenticated); + + // Navigation reference for programmatic navigation + const navigationRef = useRef | null>(null); + + // ============================================================================ + // EFFECTS + // ============================================================================ + + // Set up navigation reference for global access + React.useEffect(() => { + setNavigationRef(navigationRef); + }, []); + + // ============================================================================ + // RENDER SECTION + // ============================================================================ + + return ( + + + + + + + + ); +} + +/** + * App Component (Root Component) + * + * Purpose: Root component that wraps the entire application with Redux Provider + * + * Features: + * 1. Provide Redux store context + * 2. Wrap the main app content + * 3. Enable Redux state management throughout the app + */ +export default function App() { + return ( + + + + ); +} + +/* + * End of File: App.tsx + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/assets/fonts/Roboto-Black.ttf b/app/assets/fonts/Roboto-Black.ttf new file mode 100644 index 0000000..d51221a Binary files /dev/null and b/app/assets/fonts/Roboto-Black.ttf differ diff --git a/app/assets/fonts/Roboto-Bold.ttf b/app/assets/fonts/Roboto-Bold.ttf new file mode 100644 index 0000000..9d7cf22 Binary files /dev/null and b/app/assets/fonts/Roboto-Bold.ttf differ diff --git a/app/assets/fonts/Roboto-ExtraBold.ttf b/app/assets/fonts/Roboto-ExtraBold.ttf new file mode 100644 index 0000000..7092a88 Binary files /dev/null and b/app/assets/fonts/Roboto-ExtraBold.ttf differ diff --git a/app/assets/fonts/Roboto-ExtraLight.ttf b/app/assets/fonts/Roboto-ExtraLight.ttf new file mode 100644 index 0000000..75608c6 Binary files /dev/null and b/app/assets/fonts/Roboto-ExtraLight.ttf differ diff --git a/app/assets/fonts/Roboto-Light.ttf b/app/assets/fonts/Roboto-Light.ttf new file mode 100644 index 0000000..6fcd5f9 Binary files /dev/null and b/app/assets/fonts/Roboto-Light.ttf differ diff --git a/app/assets/fonts/Roboto-Medium.ttf b/app/assets/fonts/Roboto-Medium.ttf new file mode 100644 index 0000000..d629e98 Binary files /dev/null and b/app/assets/fonts/Roboto-Medium.ttf differ diff --git a/app/assets/fonts/Roboto-Regular.ttf b/app/assets/fonts/Roboto-Regular.ttf new file mode 100644 index 0000000..7e3bb2f Binary files /dev/null and b/app/assets/fonts/Roboto-Regular.ttf differ diff --git a/app/assets/fonts/Roboto-SemiBold.ttf b/app/assets/fonts/Roboto-SemiBold.ttf new file mode 100644 index 0000000..3f34834 Binary files /dev/null and b/app/assets/fonts/Roboto-SemiBold.ttf differ diff --git a/app/assets/images/default-avatar.png b/app/assets/images/default-avatar.png new file mode 100644 index 0000000..1c927a4 Binary files /dev/null and b/app/assets/images/default-avatar.png differ diff --git a/app/assets/images/hospital-logo.png b/app/assets/images/hospital-logo.png new file mode 100644 index 0000000..76c4ca4 Binary files /dev/null and b/app/assets/images/hospital-logo.png differ diff --git a/app/modules/Auth/components/signup/DocumentUploadStep.tsx b/app/modules/Auth/components/signup/DocumentUploadStep.tsx new file mode 100644 index 0000000..4d437eb --- /dev/null +++ b/app/modules/Auth/components/signup/DocumentUploadStep.tsx @@ -0,0 +1,634 @@ +/* + * File: DocumentUploadStep.tsx + * Description: Document upload step component for signup flow with image picker + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import React, { useState } from 'react'; +import { + View, + Text, + TouchableOpacity, + StyleSheet, + ScrollView, + Image, + Alert, + PermissionsAndroid, + Platform, + KeyboardAvoidingView, +} from 'react-native'; +import { + launchImageLibrary, + launchCamera, + ImagePickerResponse, + MediaType, +} from 'react-native-image-picker'; +import { theme } from '../../../../theme/theme'; +import { DocumentUploadStepProps } from '../../types/signup'; +import Icon from 'react-native-vector-icons/Feather'; +import { showError, showSuccess } from '../../../../shared/utils/toast'; + +// ============================================================================ +// INTERFACES +// ============================================================================ + +/** + * ImageData Interface + * + * Purpose: Defines the structure for image data + */ +interface ImageData { + uri: string; + name: string; + type: string; + size?: number; +} + +// ============================================================================ +// DOCUMENT UPLOAD STEP COMPONENT +// ============================================================================ + +/** + * DocumentUploadStep Component + * + * Purpose: Fourth step of signup flow - document upload with image picker + * + * Features: + * - Camera and gallery image selection + * - Image preview with file details + * - Real-time file size and type display + * - Permission handling for camera + * - Modern UI with proper header alignment + * - Continue button with loading state + * - Back navigation + */ +const DocumentUploadStep: React.FC = ({ + onContinue, + onBack, + data, + isLoading, +}) => { + // ============================================================================ + // STATE MANAGEMENT + // ============================================================================ + + const [selectedImage, setSelectedImage] = useState( + data.id_photo_url ? { + uri: data.id_photo_url, + name: 'uploaded_document.jpg', + type: 'image/jpeg', + } : null + ); + + // ============================================================================ + // PERMISSION HANDLERS + // ============================================================================ + + /** + * Request Camera Permission + * + * Purpose: Request camera permission for Android devices + * + * @returns Promise - Whether permission was granted + */ + const requestCameraPermission = async (): Promise => { + if (Platform.OS === 'android') { + try { + const granted = await PermissionsAndroid.request( + PermissionsAndroid.PERMISSIONS.CAMERA, + { + title: 'Camera Permission', + message: 'This app needs camera permission to capture images.', + buttonNeutral: 'Ask Me Later', + buttonNegative: 'Cancel', + buttonPositive: 'OK', + } + ); + return granted === PermissionsAndroid.RESULTS.GRANTED; + } catch (err) { + console.warn('Camera permission error:', err); + return false; + } + } + return true; + }; + + // ============================================================================ + // IMAGE PICKER HANDLERS + // ============================================================================ + + /** + * Handle Image Picker Selection + * + * Purpose: Show options for camera or gallery selection + */ + const handleImagePicker = () => { + Alert.alert( + 'Select Image', + 'Choose how you want to upload your document', + [ + { + text: 'Camera', + onPress: () => handleCameraCapture(), + }, + { + text: 'Gallery', + onPress: () => handleGalleryPicker(), + }, + { + text: 'Cancel', + style: 'cancel', + }, + ] + ); + }; + + /** + * Handle Camera Capture + * + * Purpose: Launch camera to capture image + */ + const handleCameraCapture = async () => { + const hasPermission = await requestCameraPermission(); + + if (!hasPermission) { + showError('Permission Error', 'Camera permission is required to capture images.'); + return; + } + + const options = { + mediaType: 'photo' as MediaType, + maxWidth: 2000, + maxHeight: 2000, + includeBase64: false, + }; + + launchCamera(options, (response: ImagePickerResponse) => { + if (response.didCancel) { + return; + } + + if (response.errorMessage) { + showError('Camera Error', response.errorMessage); + return; + } + + if (response.assets && response.assets[0]) { + const asset = response.assets[0]; + const imageData: ImageData = { + uri: asset.uri!, + name: asset.fileName || `document_${Date.now()}.jpg`, + type: asset.type || 'image/jpeg', + size: asset.fileSize, + }; + + setSelectedImage(imageData); + showSuccess('Success', 'Document captured successfully!'); + } + }); + }; + + /** + * Handle Gallery Picker + * + * Purpose: Launch image library to select image + */ + const handleGalleryPicker = () => { + const options = { + mediaType: 'photo' as MediaType, + maxWidth: 2000, + maxHeight: 2000, + includeBase64: false, + }; + + launchImageLibrary(options, (response: ImagePickerResponse) => { + if (response.didCancel) { + return; + } + + if (response.errorMessage) { + showError('Gallery Error', response.errorMessage); + return; + } + + if (response.assets && response.assets[0]) { + const asset = response.assets[0]; + const imageData: ImageData = { + uri: asset.uri!, + name: asset.fileName || `document_${Date.now()}.jpg`, + type: asset.type || 'image/jpeg', + size: asset.fileSize, + }; + + setSelectedImage(imageData); + showSuccess('Success', 'Document selected from gallery!'); + } + }); + }; + + // ============================================================================ + // UTILITY FUNCTIONS + // ============================================================================ + + /** + * Format File Size + * + * Purpose: Convert bytes to human readable format + * + * @param bytes - File size in bytes + * @returns Formatted file size string + */ + const formatFileSize = (bytes?: number): string => { + if (!bytes) return ''; + + const sizes = ['Bytes', 'KB', 'MB', 'GB']; + if (bytes === 0) return '0 Bytes'; + + const i = Math.floor(Math.log(bytes) / Math.log(1024)); + return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i]; + }; + + /** + * Get File Type Display + * + * Purpose: Get display name for file type + * + * @param type - MIME type + * @returns Display name for file type + */ + const getFileTypeDisplay = (type: string): string => { + if (type.includes('jpeg') || type.includes('jpg')) return 'JPEG'; + if (type.includes('png')) return 'PNG'; + if (type.includes('gif')) return 'GIF'; + if (type.includes('webp')) return 'WebP'; + return 'Image'; + }; + + // ============================================================================ + // EVENT HANDLERS + // ============================================================================ + + /** + * Handle Continue + * + * Purpose: Proceed to next step with selected image + */ + const handleContinue = () => { + if (!selectedImage) { + showError('Validation Error', 'Please upload a document to continue.'); + return; + } + + onContinue(selectedImage.uri); + }; + + /** + * Handle Remove Image + * + * Purpose: Remove selected image + */ + const handleRemoveImage = () => { + setSelectedImage(null); + }; + + // ============================================================================ + // RENDER + // ============================================================================ + + return ( + + + {/* Header */} + + + + + + + Upload Document + Step 4 of 5 + + + + + + {/* Content */} + + Upload your ID document + + Please upload a clear photo of your government-issued ID for verification. + + + {/* Document Upload Area */} + + + {selectedImage ? ( + + + + + + Document Uploaded + {selectedImage.name} + + {getFileTypeDisplay(selectedImage.type)} + {selectedImage.size && ( + โ€ข {formatFileSize(selectedImage.size)} + )} + + + ) : ( + <> + + Tap to upload document + JPG, PNG supported + + )} + + + + {selectedImage && ( + + Change Document + + )} + + {/* Continue Button */} + + + {isLoading ? 'Processing...' : 'Continue'} + + + + + + ); +}; + +// ============================================================================ +// STYLES +// ============================================================================ + +const styles = StyleSheet.create({ + // Main container + container: { + flex: 1, + backgroundColor: theme.colors.background, + }, + + // Scroll view + scrollView: { + flex: 1, + }, + + // Scroll content + scrollContent: { + flexGrow: 1, + paddingHorizontal: theme.spacing.sm, + }, + + // Header section + header: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + paddingTop: theme.spacing.xl, + paddingBottom: theme.spacing.lg, + marginBottom: theme.spacing.lg, + }, + + // Back button + backButton: { + padding: theme.spacing.sm, + borderRadius: theme.borderRadius.medium, + backgroundColor: theme.colors.backgroundAlt, + }, + + // Header content + headerContent: { + flex: 1, + alignItems: 'center', + }, + + // Header spacer + headerSpacer: { + width: 40, + }, + + // Title + title: { + fontSize: theme.typography.fontSize.displaySmall, + fontFamily: theme.typography.fontFamily.bold, + color: theme.colors.textPrimary, + marginBottom: theme.spacing.xs, + }, + + // Subtitle + subtitle: { + fontSize: theme.typography.fontSize.bodyMedium, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.textSecondary, + }, + + // Content section + content: { + flex: 1, + justifyContent: 'center', + paddingBottom: theme.spacing.xl, + }, + + // Section title + sectionTitle: { + fontSize: theme.typography.fontSize.displaySmall, + fontFamily: theme.typography.fontFamily.bold, + color: theme.colors.textPrimary, + marginBottom: theme.spacing.sm, + }, + + // Description + description: { + fontSize: theme.typography.fontSize.bodyMedium, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.textSecondary, + marginBottom: theme.spacing.xl, + }, + + // Upload container + uploadContainer: { + backgroundColor: theme.colors.backgroundAlt, + borderWidth: 2, + borderColor: theme.colors.border, + borderStyle: 'dashed', + borderRadius: theme.borderRadius.medium, + padding: theme.spacing.xl, + marginBottom: theme.spacing.md, + minHeight: 200, + }, + + // Upload content + uploadContent: { + alignItems: 'center', + }, + + // Image preview container + imagePreviewContainer: { + alignItems: 'center', + width: '100%', + }, + + // Image preview + imagePreview: { + width: '100%', + height: 150, + borderRadius: theme.borderRadius.medium, + marginBottom: theme.spacing.sm, + resizeMode: 'contain', + }, + + // Image overlay (remove button) + imageOverlay: { + position: 'absolute', + top: 0, + right: 20, + backgroundColor: 'rgba(0, 0, 0, 0.4)', + borderRadius: 17, + width: 34, + height: 34, + justifyContent: 'center', + alignItems: 'center', + }, + + // Upload text + uploadText: { + fontSize: theme.typography.fontSize.bodyLarge, + fontFamily: theme.typography.fontFamily.medium, + color: theme.colors.textPrimary, + marginTop: theme.spacing.sm, + }, + + // Upload subtext + uploadSubtext: { + fontSize: theme.typography.fontSize.bodySmall, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.textSecondary, + marginTop: theme.spacing.xs, + }, + + // Uploaded text + uploadedText: { + fontSize: theme.typography.fontSize.bodyLarge, + fontFamily: theme.typography.fontFamily.medium, + color: theme.colors.success, + marginTop: theme.spacing.sm, + }, + + // File name + fileName: { + fontSize: theme.typography.fontSize.bodySmall, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.textSecondary, + marginTop: theme.spacing.xs, + textAlign: 'center', + maxWidth: '80%', + }, + + // File details + fileDetails: { + flexDirection: 'row', + alignItems: 'center', + marginTop: theme.spacing.xs, + }, + + // File type + fileType: { + fontSize: theme.typography.fontSize.bodySmall, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.textMuted, + }, + + // File size + fileSize: { + fontSize: theme.typography.fontSize.bodySmall, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.textMuted, + }, + + // Change button + changeButton: { + alignSelf: 'center', + marginBottom: theme.spacing.xl, + }, + + // Change button text + changeButtonText: { + fontSize: theme.typography.fontSize.bodyMedium, + fontFamily: theme.typography.fontFamily.medium, + color: theme.colors.primary, + }, + + // Continue button + continueButton: { + backgroundColor: theme.colors.primary, + borderRadius: theme.borderRadius.medium, + paddingVertical: theme.spacing.md, + paddingHorizontal: theme.spacing.lg, + alignItems: 'center', + marginBottom: theme.spacing.lg, + ...theme.shadows.primary, + }, + + // Continue button disabled + continueButtonDisabled: { + backgroundColor: theme.colors.border, + opacity: 0.6, + }, + + // Continue button text + continueButtonText: { + fontSize: theme.typography.fontSize.bodyLarge, + fontFamily: theme.typography.fontFamily.bold, + color: theme.colors.background, + }, + + // Continue button text disabled + continueButtonTextDisabled: { + color: theme.colors.textMuted, + }, +}); + +export default DocumentUploadStep; + +/* + * End of File: DocumentUploadStep.tsx + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Auth/components/signup/EmailAlreadyRegisteredModal.tsx b/app/modules/Auth/components/signup/EmailAlreadyRegisteredModal.tsx new file mode 100644 index 0000000..51fbb3d --- /dev/null +++ b/app/modules/Auth/components/signup/EmailAlreadyRegisteredModal.tsx @@ -0,0 +1,232 @@ +/* + * File: EmailAlreadyRegisteredModal.tsx + * Description: Modal for when email is already registered + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import React from 'react'; +import { + View, + Text, + TouchableOpacity, + StyleSheet, + Modal, + Alert, +} from 'react-native'; +import { theme } from '../../../../theme/theme'; + +// ============================================================================ +// INTERFACES +// ============================================================================ + +interface EmailAlreadyRegisteredModalProps { + visible: boolean; + onClose: () => void; + onGoToLogin: () => void; +} + +// ============================================================================ +// EMAIL ALREADY REGISTERED MODAL COMPONENT +// ============================================================================ + +/** + * EmailAlreadyRegisteredModal Component + * + * Purpose: Modal shown when user tries to register with an existing email + * + * Features: + * - Informative message about existing email + * - Option to go to login + * - Option to close and try different email + */ +const EmailAlreadyRegisteredModal: React.FC = ({ + visible, + onClose, + onGoToLogin, +}) => { + // ============================================================================ + // HANDLERS + // ============================================================================ + + /** + * Handle Go To Login + * + * Purpose: Navigate to login screen + */ + const handleGoToLogin = () => { + onClose(); + onGoToLogin(); + }; + + /** + * Handle Try Different Email + * + * Purpose: Close modal and allow user to try different email + */ + const handleTryDifferentEmail = () => { + onClose(); + }; + + // ============================================================================ + // RENDER + // ============================================================================ + + return ( + + + + {/* Header */} + + Email Already Registered + + This email address is already associated with an account. + + + + {/* Content */} + + + It looks like you already have an account with us. You can either: + + + + + โ€ข Sign in to your existing account + + + โ€ข Try a different email address + + + + + {/* Actions */} + + + Try Different Email + + + + Go to Login + + + + + + ); +}; + +// ============================================================================ +// STYLES +// ============================================================================ + +const styles = StyleSheet.create({ + overlay: { + flex: 1, + backgroundColor: 'rgba(0, 0, 0, 0.5)', + justifyContent: 'center', + alignItems: 'center', + paddingHorizontal: theme.spacing.lg, + }, + modalContainer: { + backgroundColor: theme.colors.background, + borderRadius: theme.borderRadius.large, + padding: theme.spacing.xl, + width: '100%', + maxWidth: 400, + shadowColor: '#000000', + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.3, + shadowRadius: 8, + elevation: 8, + }, + header: { + alignItems: 'center', + marginBottom: theme.spacing.lg, + }, + title: { + fontSize: theme.typography.fontSize.displaySmall, + fontFamily: theme.typography.fontFamily.medium, + color: theme.colors.textPrimary, + marginBottom: theme.spacing.sm, + textAlign: 'center', + }, + subtitle: { + fontSize: theme.typography.bodyMedium, + color: theme.colors.textSecondary, + textAlign: 'center', + }, + content: { + marginBottom: theme.spacing.xl, + }, + message: { + fontSize: theme.typography.bodyMedium, + color: theme.colors.textPrimary, + marginBottom: theme.spacing.md, + }, + optionsContainer: { + marginLeft: theme.spacing.sm, + }, + optionText: { + fontSize: theme.typography.bodyMedium, + color: theme.colors.textSecondary, + marginBottom: theme.spacing.xs, + }, + actions: { + flexDirection: 'row', + justifyContent: 'space-between', + gap: theme.spacing.md, + }, + secondaryButton: { + flex: 1, + backgroundColor: theme.colors.background, + borderWidth: 1, + borderColor: theme.colors.border, + borderRadius: theme.borderRadius.medium, + paddingVertical: theme.spacing.md, + paddingHorizontal: theme.spacing.lg, + alignItems: 'center', + }, + secondaryButtonText: { + fontSize: theme.typography.bodyMedium, + fontFamily: theme.typography.fontFamily.bold, + color: theme.colors.textPrimary, + }, + primaryButton: { + flex: 1, + backgroundColor: theme.colors.primary, + borderRadius: theme.borderRadius.medium, + paddingVertical: theme.spacing.md, + paddingHorizontal: theme.spacing.lg, + alignItems: 'center', + shadowColor: theme.colors.primary, + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.3, + shadowRadius: 4, + elevation: 3, + }, + primaryButtonText: { + fontSize: theme.typography.bodyMedium, + fontFamily: theme.typography.fontFamily.bold, + color: theme.colors.background, + }, +}); + +export default EmailAlreadyRegisteredModal; + +/* + * End of File: EmailAlreadyRegisteredModal.tsx + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Auth/components/signup/EmailStep.tsx b/app/modules/Auth/components/signup/EmailStep.tsx new file mode 100644 index 0000000..b54af3f --- /dev/null +++ b/app/modules/Auth/components/signup/EmailStep.tsx @@ -0,0 +1,370 @@ +/* + * File: EmailStep.tsx + * Description: Email step component for signup flow + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import React, { useState } from 'react'; +import { + View, + Text, + TextInput, + TouchableOpacity, + StyleSheet, + Alert, + KeyboardAvoidingView, + Platform, + ScrollView, +} from 'react-native'; +import { theme } from '../../../../theme/theme'; +import { EmailStepProps } from '../../types/signup'; +import Icon from 'react-native-vector-icons/Feather'; + +// ============================================================================ +// EMAIL STEP COMPONENT +// ============================================================================ + +/** + * EmailStep Component + * + * Purpose: First step of signup flow - email validation + * + * Features: + * - Email input with validation + * - Real-time email format checking + * - Continue button with loading state + * - Back navigation + * - Modern header with proper alignment + */ +const EmailStep: React.FC = ({ + onContinue, + onBack, + data, + isLoading, +}) => { + // ============================================================================ + // STATE MANAGEMENT + // ============================================================================ + + const [email, setEmail] = useState(data.email || ''); + const [emailError, setEmailError] = useState(''); + + // ============================================================================ + // VALIDATION FUNCTIONS + // ============================================================================ + + /** + * Validate Email Format + * + * Purpose: Check if email format is valid + */ + const validateEmailFormat = (email: string): boolean => { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return emailRegex.test(email); + }; + + /** + * Handle Email Change + * + * Purpose: Update email and clear errors + */ + const handleEmailChange = (text: string) => { + setEmail(text); + setEmailError(''); + }; + + /** + * Handle Continue + * + * Purpose: Validate email and proceed to next step + */ + const handleContinue = () => { + // Clear previous errors + setEmailError(''); + + // Validate email format + if (!email.trim()) { + setEmailError('Email is required'); + return; + } + + if (!validateEmailFormat(email.trim())) { + setEmailError('Please enter a valid email address'); + return; + } + + // Call parent handler + onContinue(email.trim()); + }; + + // ============================================================================ + // RENDER + // ============================================================================ + + return ( + + + {/* Header */} + + + + + + + Create Account + Step 1 of 5 + + + + + + {/* Content */} + + Enter your email address + + We'll use this email to create your account and send you important updates. + + + {/* Email Input */} + + Email Address + + {emailError ? ( + {emailError} + ) : null} + + + {/* Continue Button */} + + + {isLoading ? 'Validating...' : 'Continue'} + + + + {/* Additional Info */} + + + By continuing, you agree to our Terms of Service and Privacy Policy. + + + + + + ); +}; + +// ============================================================================ +// STYLES +// ============================================================================ + +const styles = StyleSheet.create({ + // Main container + container: { + flex: 1, + backgroundColor: theme.colors.background, + }, + + // Scroll view + scrollView: { + flex: 1, + }, + + // Scroll content + scrollContent: { + flexGrow: 1, + paddingHorizontal: theme.spacing.sm, + }, + + // Header section + header: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + paddingTop: theme.spacing.xl, + paddingBottom: theme.spacing.lg, + marginBottom: theme.spacing.lg, + }, + + // Back button + backButton: { + padding: theme.spacing.sm, + borderRadius: theme.borderRadius.medium, + backgroundColor: theme.colors.backgroundAlt, + }, + + // Header content + headerContent: { + flex: 1, + alignItems: 'center', + }, + + // Header spacer + headerSpacer: { + width: 40, + }, + + // Title + title: { + fontSize: theme.typography.fontSize.displaySmall, + fontFamily: theme.typography.fontFamily.bold, + color: theme.colors.textPrimary, + marginBottom: theme.spacing.xs, + }, + + // Subtitle + subtitle: { + fontSize: theme.typography.fontSize.bodyMedium, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.textSecondary, + }, + + // Content section + content: { + flex: 1, + justifyContent: 'center', + paddingBottom: theme.spacing.xl, + }, + + // Section title + sectionTitle: { + fontSize: theme.typography.fontSize.displaySmall, + fontFamily: theme.typography.fontFamily.bold, + color: theme.colors.textPrimary, + marginBottom: theme.spacing.sm, + }, + + // Description + description: { + fontSize: theme.typography.fontSize.bodyMedium, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.textSecondary, + marginBottom: theme.spacing.xl, + }, + + // Input container + inputContainer: { + marginBottom: theme.spacing.xl, + }, + + // Input label + inputLabel: { + fontSize: theme.typography.fontSize.bodyMedium, + fontFamily: theme.typography.fontFamily.medium, + color: theme.colors.textPrimary, + marginBottom: theme.spacing.sm, + }, + + // Input field + input: { + borderWidth: 1, + borderColor: theme.colors.border, + borderRadius: theme.borderRadius.medium, + paddingHorizontal: theme.spacing.md, + paddingVertical: theme.spacing.md, + fontSize: theme.typography.fontSize.bodyLarge, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.textPrimary, + backgroundColor: theme.colors.background, + }, + + // Input error state + inputError: { + borderColor: theme.colors.error, + }, + + // Error text + errorText: { + fontSize: theme.typography.fontSize.bodySmall, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.error, + marginTop: theme.spacing.xs, + }, + + // Continue button + continueButton: { + backgroundColor: theme.colors.primary, + borderRadius: theme.borderRadius.medium, + paddingVertical: theme.spacing.md, + paddingHorizontal: theme.spacing.lg, + alignItems: 'center', + marginBottom: theme.spacing.lg, + ...theme.shadows.primary, + }, + + // Continue button disabled + continueButtonDisabled: { + backgroundColor: theme.colors.border, + opacity: 0.6, + }, + + // Continue button text + continueButtonText: { + fontSize: theme.typography.fontSize.bodyLarge, + fontFamily: theme.typography.fontFamily.bold, + color: theme.colors.background, + }, + + // Continue button text disabled + continueButtonTextDisabled: { + color: theme.colors.textMuted, + }, + + // Info container + infoContainer: { + alignItems: 'center', + }, + + // Info text + infoText: { + fontSize: theme.typography.fontSize.bodySmall, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.textMuted, + textAlign: 'center', + }, +}); + +export default EmailStep; + +/* + * End of File: EmailStep.tsx + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Auth/components/signup/HospitalSelectionStep.tsx b/app/modules/Auth/components/signup/HospitalSelectionStep.tsx new file mode 100644 index 0000000..5f40016 --- /dev/null +++ b/app/modules/Auth/components/signup/HospitalSelectionStep.tsx @@ -0,0 +1,592 @@ +/* + * File: HospitalSelectionStep.tsx + * Description: Hospital selection step component for signup flow + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import React, { useState, useMemo } from 'react'; +import { + View, + Text, + TouchableOpacity, + StyleSheet, + ScrollView, + FlatList, + ActivityIndicator, + TextInput, + KeyboardAvoidingView, + Platform, +} from 'react-native'; +import { theme } from '../../../../theme/theme'; +import { HospitalSelectionStepProps, Hospital } from '../../types/signup'; +import Icon from 'react-native-vector-icons/Feather'; + +// ============================================================================ +// HOSPITAL SELECTION STEP COMPONENT +// ============================================================================ + +/** + * HospitalSelectionStep Component + * + * Purpose: Fifth step of signup flow - hospital selection + * + * Features: + * - Hospital list display from Redux state + * - Hospital selection with visual feedback + * - Search functionality for hospitals + * - Loading states and error handling + * - Continue button with validation (sticky bottom) + * - Back navigation with modern header + * - Scrollable hospital list + */ +const HospitalSelectionStep: React.FC = ({ + onContinue, + onBack, + data, + isLoading, + hospitals, + hospitalLoading, +}) => { + // ============================================================================ + // STATE MANAGEMENT + // ============================================================================ + + const [selectedHospitalId, setSelectedHospitalId] = useState(data.hospital_id || ''); + const [searchQuery, setSearchQuery] = useState(''); + + // ============================================================================ + // COMPUTED VALUES + // ============================================================================ + + /** + * Filtered Hospitals + * + * Purpose: Filter hospitals based on search query + */ + const filteredHospitals = useMemo(() => { + if (!hospitals) return []; + + if (!searchQuery.trim()) return hospitals; + + const query = searchQuery.toLowerCase(); + return hospitals.filter(hospital => + hospital.hospital_name?.toLowerCase().includes(query) + ); + }, [hospitals, searchQuery]); + + // ============================================================================ + // HANDLERS + // ============================================================================ + + /** + * Handle Hospital Selection + * + * Purpose: Select a hospital + */ + const handleHospitalSelect = (hospitalId: string) => { + setSelectedHospitalId(hospitalId); + }; + + /** + * Handle Continue + * + * Purpose: Validate selection and proceed to next step + */ + const handleContinue = () => { + if (!selectedHospitalId) { + // Show error or alert + return; + } + onContinue(selectedHospitalId); + }; + + /** + * Handle Search Input Change + * + * Purpose: Update search query + */ + const handleSearchChange = (text: string) => { + setSearchQuery(text); + }; + + /** + * Clear Search + * + * Purpose: Clear search query + */ + const handleClearSearch = () => { + setSearchQuery(''); + }; + + // ============================================================================ + // RENDER FUNCTIONS + // ============================================================================ + + /** + * Render Hospital Item + * + * Purpose: Render individual hospital item + */ + const renderHospitalItem = ({ item }: { item: Hospital }) => { + const isSelected = selectedHospitalId === item.hospital_id; + + return ( + handleHospitalSelect(item.hospital_id || '')} + disabled={isLoading} + > + + + + {item.hospital_name || 'Unknown Hospital'} + + + + {isSelected && ( + + + + )} + + + ); + }; + + /** + * Render Search Input + * + * Purpose: Render search input field + */ + const renderSearchInput = () => ( + + + + + {searchQuery.length > 0 && ( + + + + )} + + + ); + + /** + * Render Loading State + * + * Purpose: Show loading indicator while fetching hospitals + */ + const renderLoadingState = () => ( + + + Loading hospitals... + + ); + + /** + * Render Empty State + * + * Purpose: Show message when no hospitals are available + */ + const renderEmptyState = () => ( + + + + {searchQuery ? 'No Hospitals Found' : 'No Hospitals Available'} + + + {searchQuery + ? 'Try adjusting your search terms or browse all hospitals.' + : 'There are no hospitals available at the moment. Please try again later.' + } + + + ); + + /** + * Render Search Results Info + * + * Purpose: Show search results count + */ + const renderSearchResultsInfo = () => { + if (!searchQuery.trim()) return null; + + return ( + + + {filteredHospitals.length} hospital{filteredHospitals.length !== 1 ? 's' : ''} found + + + ); + }; + + // ============================================================================ + // RENDER + // ============================================================================ + + return ( + + {/* Header */} + + + + + + + Create Account + Step 5 of 5 + + + + + + {/* Content */} + + Select Your Hospital + + Choose the hospital where you work or will be practicing. + + + {/* Search Input */} + {renderSearchInput()} + + {/* Search Results Info */} + {renderSearchResultsInfo()} + + {/* Hospital List */} + + {hospitalLoading ? ( + renderLoadingState() + ) : filteredHospitals.length > 0 ? ( + item.hospital_id || ''} + showsVerticalScrollIndicator={true} + contentContainerStyle={styles.hospitalList} + keyboardShouldPersistTaps="handled" + /> + ) : ( + renderEmptyState() + )} + + + + {/* Sticky Continue Button */} + + + + {isLoading ? 'Creating Account...' : 'Create Account'} + + + + + ); +}; + +// ============================================================================ +// STYLES +// ============================================================================ + +const styles = StyleSheet.create({ + // Main container + container: { + flex: 1, + backgroundColor: theme.colors.background, + }, + + // Header section + header: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + paddingTop: theme.spacing.xl, + paddingBottom: theme.spacing.lg, + paddingHorizontal: theme.spacing.lg, + borderBottomWidth: 1, + borderBottomColor: theme.colors.border, + }, + + // Back button + backButton: { + padding: theme.spacing.sm, + borderRadius: theme.borderRadius.medium, + backgroundColor: theme.colors.backgroundAlt, + }, + + // Header content + headerContent: { + flex: 1, + alignItems: 'center', + }, + + // Header spacer + headerSpacer: { + width: 40, + }, + + // Title + title: { + fontSize: theme.typography.fontSize.displaySmall, + fontFamily: theme.typography.fontFamily.bold, + color: theme.colors.textPrimary, + marginBottom: theme.spacing.xs, + }, + + // Subtitle + subtitle: { + fontSize: theme.typography.fontSize.bodyMedium, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.textSecondary, + }, + + // Content section + content: { + flex: 1, + paddingHorizontal: theme.spacing.lg, + paddingTop: theme.spacing.lg, + }, + + // Section title + sectionTitle: { + fontSize: theme.typography.fontSize.displaySmall, + fontFamily: theme.typography.fontFamily.bold, + color: theme.colors.textPrimary, + marginBottom: theme.spacing.sm, + }, + + // Description + description: { + fontSize: theme.typography.fontSize.bodyMedium, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.textSecondary, + marginBottom: theme.spacing.lg, + }, + + // Search container + searchContainer: { + marginBottom: theme.spacing.lg, + }, + + // Search input wrapper + searchInputWrapper: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: theme.colors.backgroundAlt, + borderWidth: 1, + borderColor: theme.colors.border, + borderRadius: theme.borderRadius.medium, + paddingHorizontal: theme.spacing.md, + }, + + // Search icon + searchIcon: { + marginRight: theme.spacing.sm, + }, + + // Search input + searchInput: { + flex: 1, + paddingVertical: theme.spacing.md, + fontSize: theme.typography.fontSize.bodyLarge, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.textPrimary, + }, + + // Clear button + clearButton: { + padding: theme.spacing.xs, + marginLeft: theme.spacing.sm, + }, + + // Search results info + searchResultsInfo: { + marginBottom: theme.spacing.md, + }, + + // Search results text + searchResultsText: { + fontSize: theme.typography.fontSize.bodySmall, + fontFamily: theme.typography.fontFamily.medium, + color: theme.colors.textSecondary, + }, + + // Hospital list container + hospitalListContainer: { + flex: 1, + }, + + // Hospital list + hospitalList: { + paddingBottom: theme.spacing.lg, + }, + + // Hospital item + hospitalItem: { + backgroundColor: theme.colors.background, + borderWidth: 1, + borderColor: theme.colors.border, + borderRadius: theme.borderRadius.medium, + padding: theme.spacing.md, + marginBottom: theme.spacing.sm, + }, + + // Hospital item selected + hospitalItemSelected: { + borderColor: theme.colors.primary, + backgroundColor: theme.colors.tertiary, + }, + + // Hospital content + hospitalContent: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + }, + + // Hospital info + hospitalInfo: { + flex: 1, + }, + + // Hospital name + hospitalName: { + fontSize: theme.typography.fontSize.bodyLarge, + fontFamily: theme.typography.fontFamily.medium, + color: theme.colors.textPrimary, + }, + + // Hospital name selected + hospitalNameSelected: { + color: theme.colors.primary, + fontFamily: theme.typography.fontFamily.bold, + }, + + // Selected icon + selectedIcon: { + width: 24, + height: 24, + borderRadius: 12, + backgroundColor: theme.colors.primary, + alignItems: 'center', + justifyContent: 'center', + }, + + // Loading container + loadingContainer: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + paddingVertical: theme.spacing.xxl, + }, + + // Loading text + loadingText: { + fontSize: theme.typography.fontSize.bodyMedium, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.textSecondary, + marginTop: theme.spacing.md, + }, + + // Empty container + emptyContainer: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + paddingVertical: theme.spacing.xxl, + }, + + // Empty title + emptyTitle: { + fontSize: theme.typography.fontSize.displaySmall, + fontFamily: theme.typography.fontFamily.bold, + color: theme.colors.textPrimary, + marginTop: theme.spacing.md, + marginBottom: theme.spacing.sm, + }, + + // Empty text + emptyText: { + fontSize: theme.typography.fontSize.bodyMedium, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.textSecondary, + textAlign: 'center', + paddingHorizontal: theme.spacing.lg, + }, + + // Sticky button container + stickyButtonContainer: { + paddingHorizontal: theme.spacing.lg, + paddingVertical: theme.spacing.lg, + backgroundColor: theme.colors.background, + borderTopWidth: 1, + borderTopColor: theme.colors.border, + }, + + // Continue button + continueButton: { + backgroundColor: theme.colors.primary, + borderRadius: theme.borderRadius.medium, + paddingVertical: theme.spacing.md, + paddingHorizontal: theme.spacing.lg, + alignItems: 'center', + ...theme.shadows.primary, + }, + + // Continue button disabled + continueButtonDisabled: { + backgroundColor: theme.colors.border, + opacity: 0.6, + }, + + // Continue button text + continueButtonText: { + fontSize: theme.typography.fontSize.bodyLarge, + fontFamily: theme.typography.fontFamily.bold, + color: theme.colors.background, + }, + + // Continue button text disabled + continueButtonTextDisabled: { + color: theme.colors.textMuted, + }, +}); + +export default HospitalSelectionStep; + +/* + * End of File: HospitalSelectionStep.tsx + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Auth/components/signup/NameStep.tsx b/app/modules/Auth/components/signup/NameStep.tsx new file mode 100644 index 0000000..11e74f4 --- /dev/null +++ b/app/modules/Auth/components/signup/NameStep.tsx @@ -0,0 +1,399 @@ +/* + * File: NameStep.tsx + * Description: Name step component for signup flow + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import React, { useState } from 'react'; +import { + View, + Text, + TextInput, + TouchableOpacity, + StyleSheet, + KeyboardAvoidingView, + Platform, + ScrollView, +} from 'react-native'; +import { theme } from '../../../../theme/theme'; +import { NameStepProps } from '../../types/signup'; +import Icon from 'react-native-vector-icons/Feather'; + +// ============================================================================ +// NAME STEP COMPONENT +// ============================================================================ + +/** + * NameStep Component + * + * Purpose: Third step of signup flow - personal information + * + * Features: + * - First name, last name, and username inputs + * - Real-time validation with error handling + * - Continue button with loading state + * - Back navigation with modern header + */ +const NameStep: React.FC = ({ + onContinue, + onBack, + data, + isLoading, +}) => { + // ============================================================================ + // STATE MANAGEMENT + // ============================================================================ + + const [firstName, setFirstName] = useState(data.first_name || ''); + const [lastName, setLastName] = useState(data.last_name || ''); + const [username, setUsername] = useState(data.username || ''); + const [errors, setErrors] = useState({ + firstName: '', + lastName: '', + username: '', + }); + + // ============================================================================ + // VALIDATION FUNCTIONS + // ============================================================================ + + /** + * Validate Input Fields + * + * Purpose: Check if all fields are valid + */ + const validateFields = (): boolean => { + const newErrors = { + firstName: '', + lastName: '', + username: '', + }; + + if (!firstName.trim()) { + newErrors.firstName = 'First name is required'; + } + + if (!lastName.trim()) { + newErrors.lastName = 'Last name is required'; + } + + if (!username.trim()) { + newErrors.username = 'Username is required'; + } else if (username.length < 3) { + newErrors.username = 'Username must be at least 3 characters'; + } + + setErrors(newErrors); + return !Object.values(newErrors).some(error => error !== ''); + }; + + /** + * Handle Continue + * + * Purpose: Validate fields and proceed to next step + */ + const handleContinue = () => { + if (validateFields()) { + onContinue(firstName.trim(), lastName.trim(), username.trim()); + } + }; + + // ============================================================================ + // RENDER + // ============================================================================ + + return ( + + + {/* Header */} + + + + + + + Create Account + Step 3 of 5 + + + + + + {/* Content */} + + Tell us about yourself + + Please provide your name and choose a username for your account. + + + {/* First Name Input */} + + First Name + { + setFirstName(text); + setErrors(prev => ({ ...prev, firstName: '' })); + }} + autoCapitalize="words" + autoCorrect={false} + editable={!isLoading} + /> + {errors.firstName ? ( + {errors.firstName} + ) : null} + + + {/* Last Name Input */} + + Last Name + { + setLastName(text); + setErrors(prev => ({ ...prev, lastName: '' })); + }} + autoCapitalize="words" + autoCorrect={false} + editable={!isLoading} + /> + {errors.lastName ? ( + {errors.lastName} + ) : null} + + + {/* Username Input */} + + Username + { + setUsername(text); + setErrors(prev => ({ ...prev, username: '' })); + }} + autoCapitalize="none" + autoCorrect={false} + editable={!isLoading} + /> + {errors.username ? ( + {errors.username} + ) : null} + + + {/* Continue Button */} + + + {isLoading ? 'Validating...' : 'Continue'} + + + + + + ); +}; + +// ============================================================================ +// STYLES +// ============================================================================ + +const styles = StyleSheet.create({ + // Main container + container: { + flex: 1, + backgroundColor: theme.colors.background, + }, + + // Scroll view + scrollView: { + flex: 1, + }, + + // Scroll content + scrollContent: { + flexGrow: 1, + paddingHorizontal: theme.spacing.sm, + }, + + // Header section + header: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + paddingTop: theme.spacing.xl, + paddingBottom: theme.spacing.lg, + marginBottom: theme.spacing.lg, + }, + + // Back button + backButton: { + padding: theme.spacing.sm, + borderRadius: theme.borderRadius.medium, + backgroundColor: theme.colors.backgroundAlt, + }, + + // Header content + headerContent: { + flex: 1, + alignItems: 'center', + }, + + // Header spacer + headerSpacer: { + width: 40, + }, + + // Title + title: { + fontSize: theme.typography.fontSize.displaySmall, + fontFamily: theme.typography.fontFamily.bold, + color: theme.colors.textPrimary, + marginBottom: theme.spacing.xs, + }, + + // Subtitle + subtitle: { + fontSize: theme.typography.fontSize.bodyMedium, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.textSecondary, + }, + + // Content section + content: { + flex: 1, + justifyContent: 'center', + paddingBottom: theme.spacing.xl, + }, + + // Section title + sectionTitle: { + fontSize: theme.typography.fontSize.displaySmall, + fontFamily: theme.typography.fontFamily.bold, + color: theme.colors.textPrimary, + marginBottom: theme.spacing.sm, + }, + + // Description + description: { + fontSize: theme.typography.fontSize.bodyMedium, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.textSecondary, + marginBottom: theme.spacing.xl, + }, + + // Input container + inputContainer: { + marginBottom: theme.spacing.xl, + }, + + // Input label + inputLabel: { + fontSize: theme.typography.fontSize.bodyMedium, + fontFamily: theme.typography.fontFamily.medium, + color: theme.colors.textPrimary, + marginBottom: theme.spacing.sm, + }, + + // Input field + input: { + borderWidth: 1, + borderColor: theme.colors.border, + borderRadius: theme.borderRadius.medium, + paddingHorizontal: theme.spacing.md, + paddingVertical: theme.spacing.md, + fontSize: theme.typography.fontSize.bodyLarge, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.textPrimary, + backgroundColor: theme.colors.background, + }, + + // Input error state + inputError: { + borderColor: theme.colors.error, + }, + + // Error text + errorText: { + fontSize: theme.typography.fontSize.bodySmall, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.error, + marginTop: theme.spacing.xs, + }, + + // Continue button + continueButton: { + backgroundColor: theme.colors.primary, + borderRadius: theme.borderRadius.medium, + paddingVertical: theme.spacing.md, + paddingHorizontal: theme.spacing.lg, + alignItems: 'center', + marginBottom: theme.spacing.lg, + ...theme.shadows.primary, + }, + + // Continue button disabled + continueButtonDisabled: { + backgroundColor: theme.colors.border, + opacity: 0.6, + }, + + // Continue button text + continueButtonText: { + fontSize: theme.typography.fontSize.bodyLarge, + fontFamily: theme.typography.fontFamily.bold, + color: theme.colors.background, + }, + + // Continue button text disabled + continueButtonTextDisabled: { + color: theme.colors.textMuted, + }, +}); + +export default NameStep; + +/* + * End of File: NameStep.tsx + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Auth/components/signup/PasswordStep.tsx b/app/modules/Auth/components/signup/PasswordStep.tsx new file mode 100644 index 0000000..da77d96 --- /dev/null +++ b/app/modules/Auth/components/signup/PasswordStep.tsx @@ -0,0 +1,628 @@ +/* + * File: PasswordStep.tsx + * Description: Password step component for signup flow with comprehensive validation + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import React, { useState, useEffect } from 'react'; +import { + View, + Text, + TextInput, + TouchableOpacity, + StyleSheet, + KeyboardAvoidingView, + Platform, + ScrollView, +} from 'react-native'; +import { theme } from '../../../../theme/theme'; +import { PasswordStepProps } from '../../types/signup'; +import Icon from 'react-native-vector-icons/Feather'; + +// ============================================================================ +// INTERFACES +// ============================================================================ + +/** + * PasswordRule Interface + * + * Purpose: Defines the structure for password validation rules + */ +interface PasswordRule { + id: string; + label: string; + validator: (password: string) => boolean; + isValid: boolean; +} + +// ============================================================================ +// PASSWORD STEP COMPONENT +// ============================================================================ + +/** + * PasswordStep Component + * + * Purpose: Second step of signup flow - password creation with comprehensive validation + * + * Features: + * - Password input with visibility toggle + * - Comprehensive password validation rules + * - Real-time password strength checking + * - Visual feedback for each requirement + * - Continue button with loading state + * - Back navigation with modern header + */ +const PasswordStep: React.FC = ({ + onContinue, + onBack, + data, + isLoading, +}) => { + // ============================================================================ + // STATE MANAGEMENT + // ============================================================================ + + const [password, setPassword] = useState(data.password || ''); + const [confirmPassword, setConfirmPassword] = useState(''); + const [passwordError, setPasswordError] = useState(''); + const [confirmPasswordError, setConfirmPasswordError] = useState(''); + const [isPasswordVisible, setPasswordVisible] = useState(false); + const [isConfirmPasswordVisible, setConfirmPasswordVisible] = useState(false); + + // Password validation rules + const [passwordRules, setPasswordRules] = useState([ + { + id: 'length', + label: 'At least 8 characters', + validator: (pwd: string) => pwd.length >= 8, + isValid: false, + }, + { + id: 'uppercase', + label: 'One uppercase letter', + validator: (pwd: string) => /[A-Z]/.test(pwd), + isValid: false, + }, + { + id: 'lowercase', + label: 'One lowercase letter', + validator: (pwd: string) => /[a-z]/.test(pwd), + isValid: false, + }, + { + id: 'number', + label: 'One number', + validator: (pwd: string) => /\d/.test(pwd), + isValid: false, + }, + { + id: 'special', + label: 'One special character', + validator: (pwd: string) => /[!@#$%^&*(),.?":{}|<>]/.test(pwd), + isValid: false, + }, + { + id: 'match', + label: 'Passwords match', + validator: (pwd: string) => pwd === confirmPassword && confirmPassword.length > 0, + isValid: false, + }, + ]); + + // ============================================================================ + // EFFECTS + // ============================================================================ + + /** + * useEffect for password validation + * + * Purpose: Update password rules when password or confirm password changes + */ + useEffect(() => { + updatePasswordRules(password); + }, [password, confirmPassword]); + + // ============================================================================ + // VALIDATION FUNCTIONS + // ============================================================================ + + /** + * Update Password Rules + * + * Purpose: Update password validation rules based on current password and confirm password + * + * @param pwd - Current password value + */ + const updatePasswordRules = (pwd: string) => { + setPasswordRules(prevRules => + prevRules.map(rule => { + if (rule.id === 'match') { + return { + ...rule, + isValid: pwd === confirmPassword && confirmPassword.length > 0, + }; + } + return { + ...rule, + isValid: rule.validator(pwd), + }; + }) + ); + }; + + /** + * Validate Password + * + * Purpose: Check if all password requirements are met + * + * @param pwd - Password to validate + * @returns boolean indicating if password meets all requirements + */ + const validatePassword = (pwd: string): boolean => { + return passwordRules.every(rule => rule.isValid); + }; + + /** + * Handle Password Change + * + * Purpose: Update password and clear errors + */ + const handlePasswordChange = (text: string) => { + setPassword(text); + setPasswordError(''); + // Clear confirm password error if passwords now match + if (confirmPassword && text === confirmPassword) { + setConfirmPasswordError(''); + } + // Update password rules to reflect the match status + updatePasswordRules(text); + }; + + /** + * Handle Confirm Password Change + * + * Purpose: Update confirm password and validate match + */ + const handleConfirmPasswordChange = (text: string) => { + setConfirmPassword(text); + setConfirmPasswordError(''); + + // Check if passwords match + if (text && text !== password) { + setConfirmPasswordError('Passwords do not match'); + } + + // Update password rules to reflect the match status + updatePasswordRules(password); + }; + + /** + * Handle Continue + * + * Purpose: Validate password and proceed to next step + */ + const handleContinue = () => { + // Clear previous errors + setPasswordError(''); + setConfirmPasswordError(''); + + // Validate password + if (!password.trim()) { + setPasswordError('Password is required'); + return; + } + + if (!validatePassword(password.trim())) { + setPasswordError('Please meet all password requirements'); + return; + } + + // Validate confirm password + if (!confirmPassword.trim()) { + setConfirmPasswordError('Please confirm your password'); + return; + } + + if (password.trim() !== confirmPassword.trim()) { + setConfirmPasswordError('Passwords do not match'); + return; + } + + // Call parent handler + onContinue(password.trim()); + }; + + /** + * Render Password Rule + * + * Purpose: Render individual password validation rule + * + * @param rule - Password rule to render + * @returns JSX element for the rule + */ + const renderPasswordRule = (rule: PasswordRule) => ( + + + {rule.isValid && ( + + )} + + + {rule.label} + + + ); + + // ============================================================================ + // RENDER + // ============================================================================ + + return ( + + + {/* Header */} + + + + + + + Create Account + Step 2 of 5 + + + + + + {/* Content */} + + Create a strong password + + Choose a password that meets all the security requirements below. + + + {/* Password Input */} + + Password + + + setPasswordVisible(!isPasswordVisible)} + style={styles.eyeIcon} + disabled={isLoading} + > + + + + {passwordError ? ( + {passwordError} + ) : null} + + + {/* Confirm Password Input */} + + Confirm Password + + + setConfirmPasswordVisible(!isConfirmPasswordVisible)} + style={styles.eyeIcon} + disabled={isLoading} + > + + + + {confirmPasswordError ? ( + {confirmPasswordError} + ) : null} + + + {/* Password Requirements */} + + Password Requirements: + + {passwordRules.map(renderPasswordRule)} + + + + {/* Continue Button */} + + + {isLoading ? 'Processing...' : 'Continue'} + + + + + + ); +}; + +// ============================================================================ +// STYLES +// ============================================================================ + +const styles = StyleSheet.create({ + // Main container + container: { + flex: 1, + backgroundColor: theme.colors.background, + }, + + // Scroll view + scrollView: { + flex: 1, + }, + + // Scroll content + scrollContent: { + flexGrow: 1, + paddingHorizontal: theme.spacing.sm, + }, + + // Header section + header: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + paddingTop: theme.spacing.xl, + paddingBottom: theme.spacing.lg, + marginBottom: theme.spacing.lg, + }, + + // Back button + backButton: { + padding: theme.spacing.sm, + borderRadius: theme.borderRadius.medium, + backgroundColor: theme.colors.backgroundAlt, + }, + + // Header content + headerContent: { + flex: 1, + alignItems: 'center', + }, + + // Header spacer + headerSpacer: { + width: 40, + }, + + // Title + title: { + fontSize: theme.typography.fontSize.displaySmall, + fontFamily: theme.typography.fontFamily.bold, + color: theme.colors.textPrimary, + marginBottom: theme.spacing.xs, + }, + + // Subtitle + subtitle: { + fontSize: theme.typography.fontSize.bodyMedium, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.textSecondary, + }, + + // Content section + content: { + flex: 1, + justifyContent: 'center', + paddingBottom: theme.spacing.xl, + }, + + // Section title + sectionTitle: { + fontSize: theme.typography.fontSize.displaySmall, + fontFamily: theme.typography.fontFamily.bold, + color: theme.colors.textPrimary, + marginBottom: theme.spacing.sm, + }, + + // Description + description: { + fontSize: theme.typography.fontSize.bodyMedium, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.textSecondary, + marginBottom: theme.spacing.xl, + }, + + // Input container + inputContainer: { + marginBottom: theme.spacing.xl, + }, + + // Input label + inputLabel: { + fontSize: theme.typography.fontSize.bodyMedium, + fontFamily: theme.typography.fontFamily.medium, + color: theme.colors.textPrimary, + marginBottom: theme.spacing.sm, + }, + + // Input wrapper + inputWrapper: { + flexDirection: 'row', + alignItems: 'center', + borderWidth: 1, + borderColor: theme.colors.border, + borderRadius: theme.borderRadius.medium, + backgroundColor: theme.colors.background, + }, + + // Input field + input: { + flex: 1, + paddingHorizontal: theme.spacing.md, + paddingVertical: theme.spacing.md, + fontSize: theme.typography.fontSize.bodyLarge, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.textPrimary, + }, + + // Eye icon + eyeIcon: { + padding: theme.spacing.sm, + }, + + // Input wrapper error state + inputWrapperError: { + borderColor: theme.colors.error, + }, + + // Error text + errorText: { + fontSize: theme.typography.fontSize.bodySmall, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.error, + marginTop: theme.spacing.xs, + }, + + // Requirements container + requirementsContainer: { + marginBottom: theme.spacing.xl, + padding: theme.spacing.md, + backgroundColor: theme.colors.backgroundAlt, + borderRadius: theme.borderRadius.medium, + }, + + // Requirements title + requirementsTitle: { + fontSize: theme.typography.fontSize.bodyMedium, + fontFamily: theme.typography.fontFamily.medium, + color: theme.colors.textPrimary, + marginBottom: theme.spacing.sm, + }, + + // Rules grid + rulesGrid: { + gap: theme.spacing.xs, + }, + + // Rule container + ruleContainer: { + flexDirection: 'row', + alignItems: 'center', + marginBottom: theme.spacing.xs, + }, + + // Checkbox + checkbox: { + width: 18, + height: 18, + borderRadius: 4, + borderWidth: 2, + borderColor: theme.colors.border, + backgroundColor: theme.colors.backgroundAlt, + marginRight: theme.spacing.sm, + alignItems: 'center', + justifyContent: 'center', + }, + + // Checkbox checked + checkboxChecked: { + backgroundColor: theme.colors.primary, + borderColor: theme.colors.primary, + }, + + // Rule text + ruleText: { + fontSize: theme.typography.fontSize.bodySmall, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.textSecondary, + flex: 1, + }, + + // Rule text valid + ruleTextValid: { + color: theme.colors.success, + }, + + // Continue button + continueButton: { + backgroundColor: theme.colors.primary, + borderRadius: theme.borderRadius.medium, + paddingVertical: theme.spacing.md, + paddingHorizontal: theme.spacing.lg, + alignItems: 'center', + marginBottom: theme.spacing.lg, + ...theme.shadows.primary, + }, + + // Continue button disabled + continueButtonDisabled: { + backgroundColor: theme.colors.border, + opacity: 0.6, + }, + + // Continue button text + continueButtonText: { + fontSize: theme.typography.fontSize.bodyLarge, + fontFamily: theme.typography.fontFamily.bold, + color: theme.colors.background, + }, + + // Continue button text disabled + continueButtonTextDisabled: { + color: theme.colors.textMuted, + }, +}); + +export default PasswordStep; + +/* + * End of File: PasswordStep.tsx + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Auth/components/signup/index.ts b/app/modules/Auth/components/signup/index.ts new file mode 100644 index 0000000..124a8ce --- /dev/null +++ b/app/modules/Auth/components/signup/index.ts @@ -0,0 +1,20 @@ +/* + * File: index.ts + * Description: Barrel exports for signup components + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +// Export all signup step components +export { default as EmailStep } from './EmailStep'; +export { default as PasswordStep } from './PasswordStep'; +export { default as NameStep } from './NameStep'; +export { default as DocumentUploadStep } from './DocumentUploadStep'; +export { default as HospitalSelectionStep } from './HospitalSelectionStep'; +export { default as EmailAlreadyRegisteredModal } from './EmailAlreadyRegisteredModal'; + +/* + * End of File: index.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Auth/index.ts b/app/modules/Auth/index.ts new file mode 100644 index 0000000..92afd0f --- /dev/null +++ b/app/modules/Auth/index.ts @@ -0,0 +1,79 @@ +/* + * File: index.ts + * Description: Main exports for Auth module + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +// Export screens +export { default as LoginScreen } from './screens/LoginScreen'; +export { default as SignUpScreen } from './screens/SignUpScreen'; + +// Export navigation +export { + AuthStackNavigator, + AuthStackParamList, + AuthNavigationProp, + AuthScreenProps, + LoginScreenProps, + SignUpScreenProps, + navigateToLogin, + navigateToSignUp, + goBack, + resetToLogin, + resetToSignUp, + replaceWithLogin, + replaceWithSignUp, + navigateToSignUpAndClearStack, + navigateToLoginAndClearStack, +} from './navigation'; + +// Export signup components +export { + EmailStep, + PasswordStep, + NameStep, + DocumentUploadStep, + HospitalSelectionStep, + EmailAlreadyRegisteredModal, +} from './components/signup'; + +// Export services +export { authAPI } from './services/signupAPI'; + +// Export types +export type { + SignUpData, + SignUpStep, + EmailStepProps, + PasswordStepProps, + NameStepProps, + DocumentUploadStepProps, + HospitalSelectionStepProps, + EmailValidationApiResponse, + UsernameValidationApiResponse, + HospitalListApiResponse, + SignUpApiResponse, + Hospital, +} from './types/signup'; + +// Export Redux +export { + loginUser, + ssoLogin, + emergencyAccess, + logoutUser, + clearError, + setBiometricEnabled, + setRememberDevice, + updateUserProfile, + setSessionToken, + clearSession, + setEmergencyAccess, +} from './redux/authSlice'; + +/* + * End of File: index.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Auth/navigation/AuthStackNavigator.tsx b/app/modules/Auth/navigation/AuthStackNavigator.tsx new file mode 100644 index 0000000..3ef65c3 --- /dev/null +++ b/app/modules/Auth/navigation/AuthStackNavigator.tsx @@ -0,0 +1,101 @@ +/* + * File: AuthStackNavigator.tsx + * Description: Stack navigator for authentication screens within the Auth module + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import React from 'react'; +import { createStackNavigator } from '@react-navigation/stack'; + +// Import authentication screens +import LoginScreen from '../screens/LoginScreen'; +import SignUpScreen from '../screens/SignUpScreen'; + +// Import navigation types +import { AuthStackParamList } from './navigationTypes'; +import { theme } from '../../../theme'; + +// Create stack navigator for Auth module +const Stack = createStackNavigator(); + +/** + * AuthStackNavigator - Manages navigation between authentication screens + * + * This navigator handles the flow between: + * - LoginScreen: Main authentication screen + * - SignUpScreen: Multi-step registration process + * + * Features: + * - Clean header styling + * - Smooth transitions between screens + * - Type-safe navigation parameters + */ +const AuthStackNavigator: React.FC = () => { + return ( + + {/* Login Screen - Main authentication entry point */} + + + {/* Sign Up Screen - Multi-step registration process */} + + + ); +}; + +export default AuthStackNavigator; + +/* + * End of File: AuthStackNavigator.tsx + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Auth/navigation/index.ts b/app/modules/Auth/navigation/index.ts new file mode 100644 index 0000000..64844b0 --- /dev/null +++ b/app/modules/Auth/navigation/index.ts @@ -0,0 +1,37 @@ +/* + * File: index.ts + * Description: Barrel exports for Auth module navigation + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +// Export main navigator +export { default as AuthStackNavigator } from './AuthStackNavigator'; + +// Export navigation types +export type { + AuthStackParamList, + AuthNavigationProp, + AuthScreenProps, + LoginScreenProps, + SignUpScreenProps, +} from './navigationTypes'; + +// Export navigation utilities +export { + navigateToLogin, + navigateToSignUp, + goBack, + resetToLogin, + resetToSignUp, + replaceWithLogin, + replaceWithSignUp, + navigateToSignUpAndClearStack, + navigateToLoginAndClearStack, +} from './navigationUtils'; + +/* + * End of File: index.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Auth/navigation/navigationTypes.ts b/app/modules/Auth/navigation/navigationTypes.ts new file mode 100644 index 0000000..66a4574 --- /dev/null +++ b/app/modules/Auth/navigation/navigationTypes.ts @@ -0,0 +1,63 @@ +/* + * File: navigationTypes.ts + * Description: TypeScript types for Auth module navigation + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import { StackNavigationProp } from '@react-navigation/stack'; + +/** + * AuthStackParamList - Defines the parameter list for Auth stack navigator + * + * This interface defines all the screens available in the Auth module + * and their associated navigation parameters. + */ +export type AuthStackParamList = { + // Login screen - Main authentication entry point + Login: undefined; + + // Sign Up screen - Multi-step registration process + SignUp: undefined; +}; + +/** + * AuthNavigationProp - Type for navigation prop in Auth screens + * + * This type provides type-safe navigation methods for screens + * within the Auth module. + */ +export type AuthNavigationProp = StackNavigationProp; + +/** + * AuthScreenProps - Base props interface for Auth screens + * + * This interface provides the common props that all Auth screens + * will receive, including navigation and route. + */ +export interface AuthScreenProps { + navigation: AuthNavigationProp; + route: { + key: string; + name: T; + params: AuthStackParamList[T]; + }; +} + +/** + * LoginScreenProps - Props for LoginScreen component + */ +export type LoginScreenProps = AuthScreenProps<'Login'>; + +/** + * SignUpScreenProps - Props for SignUpScreen component + */ +export type SignUpScreenProps = AuthScreenProps<'SignUp'>; + + + +/* + * End of File: navigationTypes.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Auth/navigation/navigationUtils.ts b/app/modules/Auth/navigation/navigationUtils.ts new file mode 100644 index 0000000..8d62466 --- /dev/null +++ b/app/modules/Auth/navigation/navigationUtils.ts @@ -0,0 +1,107 @@ +/* + * File: navigationUtils.ts + * Description: Navigation utilities for Auth module + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import { AuthNavigationProp } from './navigationTypes'; + +/** + * AuthNavigationUtils - Utility functions for Auth module navigation + * + * This module provides helper functions for common navigation patterns + * within the Auth module, ensuring consistent navigation behavior. + */ + +/** + * Navigate to Login screen + * @param navigation - Navigation prop from React Navigation + */ +export const navigateToLogin = (navigation: AuthNavigationProp): void => { + navigation.navigate('Login'); +}; + +/** + * Navigate to Sign Up screen + * @param navigation - Navigation prop from React Navigation + */ +export const navigateToSignUp = (navigation: AuthNavigationProp): void => { + navigation.navigate('SignUp'); +}; + +/** + * Go back to previous screen + * @param navigation - Navigation prop from React Navigation + */ +export const goBack = (navigation: AuthNavigationProp): void => { + if (navigation.canGoBack()) { + navigation.goBack(); + } +}; + +/** + * Reset navigation stack to Login screen + * @param navigation - Navigation prop from React Navigation + */ +export const resetToLogin = (navigation: AuthNavigationProp): void => { + navigation.reset({ + index: 0, + routes: [{ name: 'Login' }], + }); +}; + +/** + * Reset navigation stack to Sign Up screen + * @param navigation - Navigation prop from React Navigation + */ +export const resetToSignUp = (navigation: AuthNavigationProp): void => { + navigation.reset({ + index: 0, + routes: [{ name: 'SignUp' }], + }); +}; + +/** + * Replace current screen with Login screen + * @param navigation - Navigation prop from React Navigation + */ +export const replaceWithLogin = (navigation: AuthNavigationProp): void => { + navigation.replace('Login'); +}; + +/** + * Replace current screen with Sign Up screen + * @param navigation - Navigation prop from React Navigation + */ +export const replaceWithSignUp = (navigation: AuthNavigationProp): void => { + navigation.replace('SignUp'); +}; + +/** + * Navigate to Sign Up screen and clear back stack + * @param navigation - Navigation prop from React Navigation + */ +export const navigateToSignUpAndClearStack = (navigation: AuthNavigationProp): void => { + navigation.reset({ + index: 0, + routes: [{ name: 'SignUp' }], + }); +}; + +/** + * Navigate to Login screen and clear back stack + * @param navigation - Navigation prop from React Navigation + */ +export const navigateToLoginAndClearStack = (navigation: AuthNavigationProp): void => { + navigation.reset({ + index: 0, + routes: [{ name: 'Login' }], + }); +}; + +/* + * End of File: navigationUtils.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Auth/redux/authActions.ts b/app/modules/Auth/redux/authActions.ts new file mode 100644 index 0000000..d37ed74 --- /dev/null +++ b/app/modules/Auth/redux/authActions.ts @@ -0,0 +1,74 @@ +/* + * File: authActions.ts + * Description: Async actions (thunks) for Auth state + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import { createAsyncThunk } from '@reduxjs/toolkit'; +import { logout } from './authSlice'; +import { authAPI } from '../services/authAPI'; +import { showError, showSuccess } from '../../../shared/utils/toast'; + +/** + * Thunk to login user + */ +export const login = createAsyncThunk( + 'auth/login', + async (credentials: { email: string; password: string }, { rejectWithValue }) => { + try { + const response:any = await authAPI.login(credentials.email, credentials.password,'web'); + console.log('user response',response) + + if(response.data.message && !response.data.success){ + showError(response.data.message) + return rejectWithValue(response.data.message); + } + + if(response.data.message && response.data.success){ + showSuccess(response.data.message) + } + + if (response.ok && response.data && response.data.data) { + // Return the user data for the fulfilled case + return {...response.data.data.user,access_token:response.data.data.access_token}; + } else { + const errorMessage = response.data?.message || response.problem || 'Unknown error'; + return rejectWithValue(errorMessage); + } + } catch (error: any) { + return rejectWithValue(error.message); + } + } +); + + +/** + * Thunk to logout user + */ +export const logoutUser = createAsyncThunk( + 'auth/logout', + async (_, { dispatch, rejectWithValue }) => { + try { + // TODO: Add logout API call if needed + // const response = await authAPI.logout(); + + // For now, just dispatch the logout action + dispatch(logout()); + + // Show success message + showSuccess('Logged out successfully'); + + return true; + } catch (error: any) { + console.error('Logout error:', error); + return rejectWithValue(error.message); + } + } +); + +/* + * End of File: authActions.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Auth/redux/authSelectors.ts b/app/modules/Auth/redux/authSelectors.ts new file mode 100644 index 0000000..3221753 --- /dev/null +++ b/app/modules/Auth/redux/authSelectors.ts @@ -0,0 +1,48 @@ +/* + * File: authSelectors.ts + * Description: Selectors for Auth redux state + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import { RootState } from '../../../store'; + +export const selectUser = (state: RootState) => state.auth.user; +export const selectAuthLoading = (state: RootState) => state.auth.loading; +export const selectAuthError = (state: RootState) => state.auth.error; +export const selectIsAuthenticated = (state: RootState) => state.auth.isAuthenticated; +export const selectHospitals = (state: RootState) => state.hospital.hospitals; + +// User profile selectors +export const selectUserProfile = (state: RootState) => state.auth.user; +export const selectUserDisplayName = (state: RootState) => state.auth.user?.display_name; +export const selectUserEmail = (state: RootState) => state.auth.user?.email; +export const selectUserFirstName = (state: RootState) => state.auth.user?.first_name; +export const selectUserLastName = (state: RootState) => state.auth.user?.last_name; +export const selectUserHospitalId = (state: RootState) => state.auth.user?.hospital_id; +export const selectUserProfilePhoto = (state: RootState) => state.auth.user?.profile_photo_url; +export const selectUserThemeColor = (state: RootState) => state.auth.user?.theme_color; +export const selectUserAccentColor = (state: RootState) => state.auth.user?.accent_color; + +// Onboarding selectors +export const selectIsOnboarded = (state: RootState) => state.auth.user?.onboarded; +export const selectOnboardingCompleted = (state: RootState) => state.auth.user?.onboarding_completed; +export const selectOnboardingStep = (state: RootState) => state.auth.user?.onboarding_step; +export const selectOnboardingMessage = (state: RootState) => state.auth.user?.onboarding_message; + +// Dashboard settings selectors +export const selectDashboardSettings = (state: RootState) => state.auth.user?.dashboard_settings; +export const selectDashboardTheme = (state: RootState) => state.auth.user?.dashboard_settings?.theme; +export const selectDashboardLanguage = (state: RootState) => state.auth.user?.dashboard_settings?.language; +export const selectDashboardTimezone = (state: RootState) => state.auth.user?.dashboard_settings?.timezone; + +// Notification preferences selectors +export const selectNotificationPreferences = (state: RootState) => state.auth.user?.notification_preferences; +export const selectCriticalAlertsPreferences = (state: RootState) => state.auth.user?.notification_preferences?.critical_alerts; +export const selectSystemNotificationsPreferences = (state: RootState) => state.auth.user?.notification_preferences?.system_notifications; + +/* + * End of File: authSelectors.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Auth/redux/authSlice.ts b/app/modules/Auth/redux/authSlice.ts new file mode 100644 index 0000000..3c481d5 --- /dev/null +++ b/app/modules/Auth/redux/authSlice.ts @@ -0,0 +1,103 @@ +/* + * File: authSlice.ts + * Description: Redux slice for Auth state management + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { User, NotificationPreferences, DashboardSettings } from '../../../shared/types/auth'; +import { login } from './authActions'; + +// Use User type from shared types as UserProfile +type UserProfile = User; + +// Auth state type +interface AuthState { + user: UserProfile | null; + loading: boolean; + error: string | null; + isAuthenticated: boolean; +} + +const initialState: AuthState = { + user: null, + loading: false, + error: null, + isAuthenticated: false, +}; + +/** + * Auth slice for managing authentication state + */ +const authSlice = createSlice({ + name: 'auth', + initialState, + reducers: { + logout(state) { + state.user = null; + state.isAuthenticated = false; + state.loading = false; + state.error = null; + }, + updateOnboarded(state, action: PayloadAction) { + if (state.user) { + state.user.onboarded = action.payload; + } + }, + updateUserProfile(state, action: PayloadAction>) { + if (state.user) { + state.user = { ...state.user, ...action.payload }; + } + }, + updateNotificationPreferences(state, action: PayloadAction) { + if (state.user) { + state.user.notification_preferences = action.payload; + } + }, + updateDashboardSettings(state, action: PayloadAction) { + if (state.user) { + state.user.dashboard_settings = action.payload; + } + }, + clearError(state) { + state.error = null; + } + }, + extraReducers: (builder) => { + // Login thunk cases + builder + .addCase(login.pending, (state) => { + state.loading = true; + state.error = null; + }) + .addCase(login.fulfilled, (state, action) => { + state.loading = false; + state.user = action.payload as UserProfile; + state.isAuthenticated = true; + state.error = null; + }) + .addCase(login.rejected, (state, action) => { + state.loading = false; + state.error = action.payload as string || 'Login failed'; + state.isAuthenticated = false; + }); + }, +}); + +export const { + logout, + updateOnboarded, + updateUserProfile, + updateNotificationPreferences, + updateDashboardSettings, + clearError +} = authSlice.actions; + +export default authSlice.reducer; + +/* + * End of File: authSlice.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Auth/redux/hospitalSelectors.ts b/app/modules/Auth/redux/hospitalSelectors.ts new file mode 100644 index 0000000..3dadc7f --- /dev/null +++ b/app/modules/Auth/redux/hospitalSelectors.ts @@ -0,0 +1,129 @@ +/* + * File: hospitalSelectors.ts + * Description: Redux selectors for Hospital state + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import { createSelector } from '@reduxjs/toolkit'; +import { RootState } from '../../../store'; + +// ============================================================================ +// BASE SELECTORS +// ============================================================================ + +/** + * Select Hospital State + * + * Purpose: Get the entire hospital state from Redux store + */ +export const selectHospitalState = (state: RootState) => state.hospital; + +/** + * Select Hospitals List + * + * Purpose: Get the list of hospitals + */ +export const selectHospitals = (state: RootState) => state.hospital.hospitals; + +/** + * Select Hospital Loading State + * + * Purpose: Get the loading state for hospital operations + */ +export const selectHospitalLoading = (state: RootState) => state.hospital.loading; + +/** + * Select Hospital Error + * + * Purpose: Get any error from hospital operations + */ +export const selectHospitalError = (state: RootState) => state.hospital.error; + +// ============================================================================ +// DERIVED SELECTORS +// ============================================================================ + +/** + * Select Hospitals Count + * + * Purpose: Get the total number of hospitals + */ +export const selectHospitalsCount = createSelector( + [selectHospitals], + (hospitals) => hospitals?.length || 0 +); + +/** + * Select Hospital by ID + * + * Purpose: Get a specific hospital by its ID + */ +export const selectHospitalById = createSelector( + [selectHospitals, (_state: RootState, hospitalId: string) => hospitalId], + (hospitals, hospitalId) => + hospitals?.find(hospital => hospital.hospital_id === hospitalId) || null +); + +/** + * Select Hospital Names + * + * Purpose: Get an array of hospital names for display + */ +export const selectHospitalNames = createSelector( + [selectHospitals], + (hospitals) => + hospitals?.map(hospital => hospital.hospital_name).filter(Boolean) || [] +); + +/** + * Select Hospitals for Dropdown + * + * Purpose: Get hospitals formatted for dropdown/select components + */ +export const selectHospitalsForDropdown = createSelector( + [selectHospitals], + (hospitals) => + hospitals?.map(hospital => ({ + label: hospital.hospital_name || 'Unknown Hospital', + value: hospital.hospital_id || '', + })).filter(item => item.value) || [] +); + +/** + * Select Filtered Hospitals + * + * Purpose: Get hospitals filtered by search term + */ +export const selectFilteredHospitals = createSelector( + [selectHospitals, (_state: RootState, searchTerm: string) => searchTerm], + (hospitals, searchTerm) => { + if (!searchTerm.trim()) return hospitals || []; + + const term = searchTerm.toLowerCase(); + return hospitals?.filter(hospital => + hospital.hospital_name?.toLowerCase().includes(term) + ) || []; + } +); + +/** + * Select Hospital Loading Status + * + * Purpose: Get comprehensive loading status for hospital operations + */ +export const selectHospitalStatus = createSelector( + [selectHospitalLoading, selectHospitalError, selectHospitalsCount], + (loading, error, count) => ({ + loading, + error, + hasData: count > 0, + isEmpty: count === 0, + }) +); + +/* + * End of File: hospitalSelectors.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Auth/redux/hospitalSlice.ts b/app/modules/Auth/redux/hospitalSlice.ts new file mode 100644 index 0000000..7ea2c08 --- /dev/null +++ b/app/modules/Auth/redux/hospitalSlice.ts @@ -0,0 +1,170 @@ +/* + * File: hospitalSlice.ts + * Description: Redux slice for Hospital state management (non-persisted) + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; +import { authAPI } from '../services/authAPI'; +import { showError } from '../../../shared/utils/toast'; + +// ============================================================================ +// INTERFACES +// ============================================================================ + +/** + * Hospital Interface + * + * Purpose: Defines the structure for hospital data + */ +interface Hospital { + hospital_id: string | null; + hospital_name: string | null; +} + +/** + * Hospital State Interface + * + * Purpose: Defines the structure for hospital state + */ +interface HospitalState { + hospitals: Hospital[] | null; + loading: boolean; + error: string | null; +} + +// ============================================================================ +// ASYNC THUNKS +// ============================================================================ + +/** + * Fetch Hospitals Async Thunk + * + * Purpose: Fetch hospital list from API + * + * Features: + * - API integration with error handling + * - Loading state management + * - Toast notifications for errors + * - Automatic state updates + */ +export const fetchHospitals = createAsyncThunk( + 'hospital/fetchHospitals', + async (_, { rejectWithValue }) => { + try { + const response: any = await authAPI.gethospitals(); + console.log('hospital response', response); + + if (response.ok && response.data && response.data.data) { + return response.data.data; + } else { + showError('Error while fetching hospital list'); + return rejectWithValue('Failed to fetch hospitals'); + } + } catch (error: any) { + console.log('Hospital fetch error:', error); + showError('Error while fetching hospital list'); + return rejectWithValue(error.message || 'Network error'); + } + } +); + +// ============================================================================ +// INITIAL STATE +// ============================================================================ + +const initialState: HospitalState = { + hospitals: [], + loading: false, + error: null, +}; + +// ============================================================================ +// HOSPITAL SLICE +// ============================================================================ + +/** + * Hospital Slice + * + * Purpose: Manages hospital-related state + * + * Features: + * - Hospital list management + * - Loading states for API calls + * - Error handling and display + * - Non-persisted state (not stored in AsyncStorage) + */ +const hospitalSlice = createSlice({ + name: 'hospital', + initialState, + reducers: { + /** + * Set Hospitals Action + * + * Purpose: Manually set hospital list + */ + setHospitals(state, action: PayloadAction) { + state.hospitals = action.payload; + state.loading = false; + state.error = null; + }, + + /** + * Clear Hospitals Action + * + * Purpose: Clear hospital list and reset state + */ + clearHospitals(state) { + state.hospitals = []; + state.loading = false; + state.error = null; + }, + + /** + * Clear Error Action + * + * Purpose: Clear error state + */ + clearError(state) { + state.error = null; + }, + }, + extraReducers: (builder) => { + builder + // Fetch Hospitals - Pending + .addCase(fetchHospitals.pending, (state) => { + state.loading = true; + state.error = null; + }) + // Fetch Hospitals - Fulfilled + .addCase(fetchHospitals.fulfilled, (state, action) => { + state.loading = false; + state.hospitals = action.payload; + state.error = null; + }) + // Fetch Hospitals - Rejected + .addCase(fetchHospitals.rejected, (state, action) => { + state.loading = false; + state.error = action.payload as string || 'Failed to fetch hospitals'; + }); + }, +}); + +// ============================================================================ +// EXPORTS +// ============================================================================ + +export const { + setHospitals, + clearHospitals, + clearError, +} = hospitalSlice.actions; + +export default hospitalSlice.reducer; + +/* + * End of File: hospitalSlice.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Auth/redux/index.ts b/app/modules/Auth/redux/index.ts new file mode 100644 index 0000000..37b349e --- /dev/null +++ b/app/modules/Auth/redux/index.ts @@ -0,0 +1,16 @@ +/* + * File: index.ts + * Description: Barrel export for Auth redux + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +export { default as authReducer } from './authSlice'; +export * from './authActions'; +export * from './authSelectors'; + +/* + * End of File: index.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Auth/screens/LoginScreen.tsx b/app/modules/Auth/screens/LoginScreen.tsx new file mode 100644 index 0000000..f7720e8 --- /dev/null +++ b/app/modules/Auth/screens/LoginScreen.tsx @@ -0,0 +1,420 @@ +/* + * File: LoginScreen.tsx + * Description: Login screen with credential-based authentication + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import React, { useState } from 'react'; +import { + View, + Text, + StyleSheet, + TouchableWithoutFeedback, + Keyboard, + TouchableOpacity, + TextInput, + ScrollView, + KeyboardAvoidingView, + Alert, + Platform, + Image, +} from 'react-native'; +import { useAppDispatch, useAppSelector } from '../../../store/hooks'; +import { login } from '../redux/authActions'; +import { selectAuthLoading } from '../redux/authSelectors'; +import { theme } from '../../../theme/theme'; +import { validateEmail } from '../../../shared/utils/validators'; +import Icon from 'react-native-vector-icons/Feather'; +import { AuthNavigationProp } from '../navigation'; + +/** + * LoginScreenProps Interface + * + * Purpose: Defines the props required by the LoginScreen component + * + * Props: + * - navigation: React Navigation object for screen navigation + */ +interface LoginScreenProps { + navigation: AuthNavigationProp; +} + +/** + * LoginScreen Component + * + * Purpose: Main authentication screen for credential-based login + * + * Authentication Flow: + * 1. Email/Password validation + * 2. Redux action dispatch for login + * 3. Loading state management + * 4. Error handling and user feedback + * + * Features: + * - Keyboard-aware layout for better UX + * - Form validation and error handling + * - Loading states during authentication + * - Password visibility toggle + * - Navigation to sign up screen + * - Responsive design for different screen sizes + */ +const LoginScreen: React.FC = ({ navigation }) => { + // ============================================================================ + // STATE MANAGEMENT + // ============================================================================ + + // Form input states + const [email, setEmail] = useState(''); // User's email address + const [password, setPassword] = useState(''); // User's password + const [isPasswordVisible, setPasswordVisible] = useState(false); // Password visibility toggle + + // Redux state + const dispatch = useAppDispatch(); + const loading = useAppSelector(selectAuthLoading); + + // ============================================================================ + // EVENT HANDLERS + // ============================================================================ + + /** + * handleLogin Function + * + * Purpose: Process credential-based login with email and password + * + * Flow: + * 1. Validate that both email and password are provided + * 2. Validate email format + * 3. Show error alert if validation fails + * 4. Dispatch Redux login action with credentials + */ + const handleLogin = () => { + // Validate required fields + if (!email.trim() || !password.trim()) { + Alert.alert('Validation Error', 'Email and password are required.'); + return; + } + + // Validate email format + if (!validateEmail(email)) { + Alert.alert('Validation Error', 'Please enter a valid email address.'); + return; + } + + // Dispatch login action + dispatch(login({ email, password })); + }; + + /** + * handleSignUp Function + * + * Purpose: Navigate to the SignUpScreen for new user registration + * + * Flow: Navigate to SignUp screen using React Navigation + */ + const handleSignUp = () => { + navigation.navigate('SignUp'); + }; + + /** + * togglePasswordVisibility Function + * + * Purpose: Toggle password field visibility for better UX + */ + const togglePasswordVisibility = () => { + setPasswordVisible(!isPasswordVisible); + }; + + // ============================================================================ + // RENDER SECTION + // ============================================================================ + + return ( + + + + + {/* ======================================================================== + * HEADER SECTION - App branding and title + * ======================================================================== */} + + Physician + Emergency Department Access + + + + + + {/* ======================================================================== + * LOGIN FORM - Main authentication interface + * ======================================================================== */} + + {/* Email Input */} + + + + + + {/* Password Input */} + + + + + + + + + + + {/* Login Button */} + + {loading ? ( + + Logging in... + + ) : ( + Login + )} + + + {/* Divider */} + + + OR + + + + {/* Sign Up Button */} + + Sign Up + + + + {/* ======================================================================== + * FOOTER - Security and information message + * ======================================================================== */} + + + Secure access to patient information and critical alerts + + + + + + + ); +}; + +// ============================================================================ +// STYLES SECTION +// ============================================================================ + +const styles = StyleSheet.create({ + // Main container + container: { + flex: 1, + backgroundColor: theme.colors.background, + }, + + // Content wrapper + content: { + flex: 1, + justifyContent: 'center', + padding: theme.spacing.lg, + }, + + // Header section + header: { + alignItems: 'center', + marginBottom: theme.spacing.xxl, + }, + + // App title + title: { + fontSize: theme.typography.fontSize.displayMedium, + fontFamily: theme.typography.fontFamily.bold, + color: theme.colors.primary, + marginBottom: theme.spacing.sm, + textAlign: 'center', + }, + + // App subtitle + subtitle: { + fontSize: theme.typography.fontSize.bodyMedium, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.textSecondary, + textAlign: 'center', + }, + + // Form container + formContainer: { + marginBottom: theme.spacing.xl, + }, + + // Input container + inputContainer: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: theme.colors.backgroundAlt, + borderWidth: 1, + borderColor: theme.colors.border, + borderRadius: theme.borderRadius.medium, + marginBottom: theme.spacing.md, + paddingHorizontal: theme.spacing.md, + paddingVertical: 2, + }, + + // Input icon + inputIcon: { + marginRight: theme.spacing.sm, + }, + + // Input field + inputField: { + flex: 1, + paddingVertical: theme.spacing.md, + fontSize: theme.typography.fontSize.bodyLarge, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.textPrimary, + }, + + // Eye icon for password visibility + eyeIcon: { + paddingLeft: theme.spacing.sm, + }, + + // Base button styling + button: { + paddingVertical: theme.spacing.md, + paddingHorizontal: theme.spacing.lg, + borderRadius: theme.borderRadius.medium, + alignItems: 'center', + marginBottom: theme.spacing.md, + }, + + // Login button + loginButton: { + backgroundColor: theme.colors.primary, + ...theme.shadows.primary, + }, + + // Sign up button + signUpButton: { + backgroundColor: theme.colors.background, + borderWidth: 1, + borderColor: theme.colors.primary, + }, + + // Disabled button + buttonDisabled: { + opacity: 0.6, + }, + + // Button text + buttonText: { + color: theme.colors.background, + fontSize: theme.typography.fontSize.bodyLarge, + fontFamily: theme.typography.fontFamily.medium, + }, + + // Sign up button text + signUpButtonText: { + color: theme.colors.primary, + fontSize: theme.typography.fontSize.bodyLarge, + fontFamily: theme.typography.fontFamily.medium, + }, + + // Loading container + loadingContainer: { + flexDirection: 'row', + alignItems: 'center', + }, + + // Divider + divider: { + flexDirection: 'row', + alignItems: 'center', + marginVertical: theme.spacing.lg, + }, + + // Divider line + dividerLine: { + flex: 1, + height: 1, + backgroundColor: theme.colors.border, + }, + + // Divider text + dividerText: { + marginHorizontal: theme.spacing.md, + color: theme.colors.textSecondary, + fontSize: theme.typography.fontSize.bodyMedium, + fontFamily: theme.typography.fontFamily.medium, + }, + + // Footer + footer: { + alignItems: 'center', + }, + + // Footer text + footerText: { + fontSize: theme.typography.fontSize.bodySmall, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.textMuted, + textAlign: 'center', + }, + + // Image container + imageContainer: { + alignItems: 'center', + marginBottom: theme.spacing.xl, + }, + + // Image + image: { + width: '100%', + height: 150, + }, +}); + +export default LoginScreen; + +/* + * End of File: LoginScreen.tsx + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Auth/screens/ResetPasswordScreen.tsx b/app/modules/Auth/screens/ResetPasswordScreen.tsx new file mode 100644 index 0000000..227d9d1 --- /dev/null +++ b/app/modules/Auth/screens/ResetPasswordScreen.tsx @@ -0,0 +1,641 @@ +/* + * File: ResetPasswordScreen.tsx + * Description: Password reset screen for onboarding flow + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import React, { useState, useEffect } from 'react'; +import { + View, + Text, + StyleSheet, + TextInput, + TouchableOpacity, + ScrollView, + KeyboardAvoidingView, + Platform, + Alert, +} from 'react-native'; +import { useAppDispatch, useAppSelector } from '../../../store/hooks'; +import { updateOnboarded, logout } from '../redux/authSlice'; +import { authAPI } from '../services/authAPI'; +import { showError, showSuccess } from '../../../shared/utils/toast'; +import { theme } from '../../../theme/theme'; +import Icon from 'react-native-vector-icons/Feather'; +import { AuthNavigationProp } from '../navigation/navigationTypes'; + +/** + * ResetPasswordScreenProps Interface + * + * Purpose: Defines the props required by the ResetPasswordScreen component + * + * Props: + * - navigation: React Navigation object for screen navigation (optional when used in root stack) + */ +interface ResetPasswordScreenProps { + navigation?: AuthNavigationProp; +} + +/** + * PasswordRule Interface + * + * Purpose: Defines the structure for password validation rules + */ +interface PasswordRule { + id: string; + label: string; + validator: (password: string) => boolean; + isValid: boolean; +} + +/** + * ResetPasswordScreen Component + * + * Purpose: Password reset screen for users who need to set their initial password + * + * Features: + * 1. Password and confirm password input fields + * 2. Real-time password validation with visual feedback + * 3. Password visibility toggles + * 4. Password strength requirements display + * 5. Integration with Redux for onboarding status + * 6. API integration for password change + * + * Password Requirements: + * - At least 8 characters + * - One uppercase letter + * - One lowercase letter + * - One number + * - One special character + * - Passwords must match + */ +export const ResetPasswordScreen: React.FC = ({ + navigation, +}) => { + // ============================================================================ + // STATE MANAGEMENT + // ============================================================================ + + // Form input states + const [password, setPassword] = useState(''); + const [confirmPassword, setConfirmPassword] = useState(''); + const [isPasswordVisible, setPasswordVisible] = useState(false); + const [isConfirmPasswordVisible, setConfirmPasswordVisible] = useState(false); + const [loading, setLoading] = useState(false); + + // Redux state + const dispatch = useAppDispatch(); + const user = useAppSelector((state) => state.auth.user); + + // Password validation rules + const [passwordRules, setPasswordRules] = useState([ + { + id: 'length', + label: 'At least 8 characters', + validator: (pwd: string) => pwd.length >= 8, + isValid: false, + }, + { + id: 'uppercase', + label: 'One uppercase letter', + validator: (pwd: string) => /[A-Z]/.test(pwd), + isValid: false, + }, + { + id: 'lowercase', + label: 'One lowercase letter', + validator: (pwd: string) => /[a-z]/.test(pwd), + isValid: false, + }, + { + id: 'number', + label: 'One number', + validator: (pwd: string) => /\d/.test(pwd), + isValid: false, + }, + { + id: 'special', + label: 'One special character', + validator: (pwd: string) => /[!@#$%^&*(),.?":{}|<>]/.test(pwd), + isValid: false, + }, + { + id: 'match', + label: 'Passwords match', + validator: (pwd: string) => pwd.length > 0 && confirmPassword.length > 0 && pwd === confirmPassword, + isValid: false, + }, + ]); + + // ============================================================================ + // EFFECTS + // ============================================================================ + + /** + * useEffect for password validation + * + * Purpose: Update password rules when password or confirm password changes + */ + useEffect(() => { + updatePasswordRules(password); + }, [password, confirmPassword]); + + // ============================================================================ + // HELPER FUNCTIONS + // ============================================================================ + + /** + * validatePassword Function + * + * Purpose: Check if all password requirements are met + * + * @param pwd - Password to validate + * @returns boolean indicating if password meets all requirements + */ + const validatePassword = (pwd: string): boolean => { + return passwordRules.every(rule => rule.isValid); + }; + + /** + * updatePasswordRules Function + * + * Purpose: Update password validation rules based on current password + * + * @param pwd - Current password value + */ + const updatePasswordRules = (pwd: string) => { + setPasswordRules(prevRules => + prevRules.map(rule => { + if (rule.id === 'match') { + return { + ...rule, + isValid: pwd.length > 0 && confirmPassword.length > 0 && pwd === confirmPassword, + }; + } + return { + ...rule, + isValid: rule.validator(pwd), + }; + }) + ); + }; + + // ============================================================================ + // EVENT HANDLERS + // ============================================================================ + + /** + * handlePasswordChange Function + * + * Purpose: Handle password input changes + * + * @param pwd - New password value + */ + const handlePasswordChange = (pwd: string) => { + setPassword(pwd); + }; + + /** + * handleConfirmPasswordChange Function + * + * Purpose: Handle confirm password input changes + * + * @param pwd - New confirm password value + */ + const handleConfirmPasswordChange = (pwd: string) => { + setConfirmPassword(pwd); + }; + + /** + * handleReset Function + * + * Purpose: Handle password reset submission + * + * Flow: + * 1. Validate required fields + * 2. Validate password requirements + * 3. Check password match + * 4. Call API to change password + * 5. Update onboarding status on success + */ + const handleReset = async () => { + // Validate required fields + if (!password.trim() || !confirmPassword.trim()) { + Alert.alert('Validation Error', 'Both password fields are required.'); + return; + } + + // Validate password requirements + if (!validatePassword(password)) { + Alert.alert('Validation Error', 'Please meet all password requirements.'); + return; + } + + // Check password match + if (password !== confirmPassword) { + Alert.alert('Validation Error', 'Passwords do not match.'); + return; + } + + setLoading(true); + + try { + // Call API to change password + const response: any = await authAPI.changepassword({ + password, + token: user?.access_token, + }); + + console.log('reset response', response); + + if (response.data && response.data.message) { + if (response.data.success) { + showSuccess(response.data.message); + + // Update onboarding status + dispatch(updateOnboarded(true)); + + // Navigate to main app + // The app will automatically navigate to MainTabNavigator due to Redux state change + } else { + showError(response.data.message); + } + } else { + showError('Error while changing password'); + } + } catch (error: any) { + console.error('Password reset error:', error); + showError('Failed to reset password. Please try again.'); + } finally { + setLoading(false); + } + }; + + /** + * handleBack Function + * + * Purpose: Handle back navigation - logs out user since they can't go back to login + */ + const handleBack = () => { + Alert.alert( + 'Sign Out', + 'Are you sure you want to sign out? You will need to log in again.', + [ + { + text: 'Cancel', + style: 'cancel', + }, + { + text: 'Sign Out', + style: 'destructive', + onPress: () => { + // Dispatch logout action + dispatch(logout()); + }, + }, + ] + ); + }; + + /** + * renderPasswordRule Function + * + * Purpose: Render individual password validation rule + * + * @param rule - Password rule to render + * @returns JSX element for the rule + */ + const renderPasswordRule = (rule: PasswordRule) => ( + + + {rule.isValid && ( + + )} + + + {rule.label} + + + ); + + // ============================================================================ + // RENDER SECTION + // ============================================================================ + + return ( + + + {/* Header */} + + + + + Set Your Password + + + + {/* Icon */} + + + + + {/* Title and Subtitle */} + Set your password + + Create a strong password to complete your account setup + + + {/* Password Input */} + + + + setPasswordVisible(!isPasswordVisible)} + style={styles.eyeIcon} + disabled={loading} + > + + + + + {/* Confirm Password Input */} + + + + setConfirmPasswordVisible(!isConfirmPasswordVisible)} + style={styles.eyeIcon} + disabled={loading} + > + + + + + {/* Password Rules Section */} + + Password Requirements: + + {passwordRules.map(renderPasswordRule)} + + + + {/* Reset Button */} + + {loading ? ( + + Setting Password... + + ) : ( + Set Password + )} + + + + ); +}; + +// ============================================================================ +// STYLES SECTION +// ============================================================================ + +const styles = StyleSheet.create({ + // Main container + container: { + flex: 1, + backgroundColor: theme.colors.background, + }, + + // Scroll view + scrollView: { + flex: 1, + }, + + // Scroll content + scrollContent: { + flexGrow: 1, + padding: theme.spacing.lg, + }, + + // Header section + header: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + marginBottom: theme.spacing.xl, + }, + + // Back button + backButton: { + padding: theme.spacing.sm, + borderRadius: theme.borderRadius.medium, + backgroundColor: theme.colors.backgroundAlt, + }, + + // Header title + headerTitle: { + fontSize: theme.typography.fontSize.displaySmall, + fontFamily: theme.typography.fontFamily.bold, + color: theme.colors.textPrimary, + }, + + // Header spacer + headerSpacer: { + width: 40, + }, + + // Icon container + iconContainer: { + alignItems: 'center', + marginBottom: theme.spacing.xl, + }, + + // Title + title: { + fontSize: theme.typography.fontSize.displayMedium, + fontFamily: theme.typography.fontFamily.bold, + color: theme.colors.textPrimary, + textAlign: 'center', + marginBottom: theme.spacing.sm, + }, + + // Subtitle + subtitle: { + fontSize: theme.typography.fontSize.bodyLarge, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.textSecondary, + textAlign: 'center', + marginBottom: theme.spacing.xl, + }, + + // Input container + inputContainer: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: theme.colors.backgroundAlt, + borderWidth: 1, + borderColor: theme.colors.border, + borderRadius: theme.borderRadius.medium, + marginBottom: theme.spacing.md, + paddingHorizontal: theme.spacing.md, + paddingVertical: 2, + }, + + // Input icon + inputIcon: { + marginRight: theme.spacing.sm, + }, + + // Input field + inputField: { + flex: 1, + paddingVertical: theme.spacing.md, + fontSize: theme.typography.fontSize.bodyLarge, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.textPrimary, + }, + + // Eye icon + eyeIcon: { + padding: theme.spacing.sm, + }, + + // Rules container + rulesContainer: { + marginTop: theme.spacing.sm, + marginBottom: theme.spacing.xl, + paddingHorizontal: theme.spacing.sm, + }, + + // Rules title + rulesTitle: { + fontSize: theme.typography.fontSize.bodyMedium, + fontFamily: theme.typography.fontFamily.medium, + color: theme.colors.textPrimary, + marginBottom: theme.spacing.sm, + }, + + // Rules grid + rulesGrid: { + gap: theme.spacing.xs, + }, + + // Rule container + ruleContainer: { + flexDirection: 'row', + alignItems: 'center', + marginBottom: theme.spacing.xs, + }, + + // Checkbox + checkbox: { + width: 18, + height: 18, + borderRadius: 4, + borderWidth: 2, + borderColor: theme.colors.border, + backgroundColor: theme.colors.backgroundAlt, + marginRight: theme.spacing.sm, + alignItems: 'center', + justifyContent: 'center', + }, + + // Checkbox checked + checkboxChecked: { + backgroundColor: theme.colors.primary, + borderColor: theme.colors.primary, + }, + + // Rule text + ruleText: { + fontSize: theme.typography.fontSize.bodySmall, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.textSecondary, + flex: 1, + }, + + // Rule text valid + ruleTextValid: { + color: theme.colors.success, + }, + + // Base button + button: { + paddingVertical: theme.spacing.md, + paddingHorizontal: theme.spacing.lg, + borderRadius: theme.borderRadius.medium, + alignItems: 'center', + marginBottom: theme.spacing.md, + }, + + // Reset button + resetButton: { + backgroundColor: theme.colors.primary, + ...theme.shadows.primary, + }, + + // Disabled button + buttonDisabled: { + opacity: 0.6, + }, + + // Button text + buttonText: { + color: theme.colors.background, + fontSize: theme.typography.fontSize.bodyLarge, + fontFamily: theme.typography.fontFamily.medium, + }, + + // Loading container + loadingContainer: { + flexDirection: 'row', + alignItems: 'center', + }, +}); + +export default ResetPasswordScreen; + +/* + * End of File: ResetPasswordScreen.tsx + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Auth/screens/SignUpScreen.tsx b/app/modules/Auth/screens/SignUpScreen.tsx new file mode 100644 index 0000000..ef99a87 --- /dev/null +++ b/app/modules/Auth/screens/SignUpScreen.tsx @@ -0,0 +1,503 @@ +/* + * File: SignUpScreen.tsx + * Description: Multi-step signup screen with validation and Redux integration + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import React, { useEffect, useState } from 'react'; +import { + View, + StyleSheet, + StatusBar, + Alert, + Text, + TouchableOpacity, + ScrollView, + KeyboardAvoidingView, + Platform +} from 'react-native'; +import { theme } from '../../../theme/theme'; +import { useAppDispatch, useAppSelector } from '../../../store/hooks'; +import Icon from 'react-native-vector-icons/Feather'; + +// Import signup step components +import EmailStep from '../components/signup/EmailStep'; +import PasswordStep from '../components/signup/PasswordStep'; +import NameStep from '../components/signup/NameStep'; +import DocumentUploadStep from '../components/signup/DocumentUploadStep'; +import HospitalSelectionStep from '../components/signup/HospitalSelectionStep'; +import EmailAlreadyRegisteredModal from '../components/signup/EmailAlreadyRegisteredModal'; + +// Import API service + + +// Import hospital Redux functionality +import { fetchHospitals } from '../redux/hospitalSlice'; +import { selectHospitalLoading, selectHospitals } from '../redux/hospitalSelectors'; + +// Import types +import { SignUpData, SignUpStep } from '../types/signup'; +import { authAPI } from '../services/authAPI'; +import { showError, showSuccess } from '../../../shared/utils/toast'; + +// ============================================================================ +// INTERFACES +// ============================================================================ + +interface SignUpScreenProps { + navigation: any; +} + +// ============================================================================ +// SIGNUP SCREEN COMPONENT +// ============================================================================ + +/** + * SignUpScreen Component + * + * Purpose: Multi-step signup flow with validation and Redux integration + * + * Features: + * - Step-by-step signup process (email โ†’ password โ†’ name โ†’ document โ†’ hospital) + * - Real-time validation with visual feedback + * - Email and username availability checks + * - Hospital selection with search + * - Document upload with preview + * - Progress tracking with visual progress bar + * - Modern UI with icons and proper typography + * - Keyboard-aware layout for better UX + * - Redux state management + * - Loading states and error handling + */ +const SignUpScreen: React.FC = ({ navigation }) => { + // ============================================================================ + // STATE MANAGEMENT + // ============================================================================ + + const [currentStep, setCurrentStep] = useState('email'); + const [showEmailRegisteredModal, setShowEmailRegisteredModal] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [signUpData, setSignUpData] = useState>({ + email: '', + password: '', + first_name: '', + last_name: '', + username: '', + id_photo_url: null, + hospital_id: '', + }); + + const dispatch = useAppDispatch(); + + // ============================================================================ + // REDUX STATE + // ============================================================================ + + const hospitals = useAppSelector(selectHospitals); + const hospitalLoading = useAppSelector(selectHospitalLoading); + + // ============================================================================ + // STEP CONFIGURATION + // ============================================================================ + + const steps: SignUpStep[] = ['email', 'password', 'name', 'document', 'hospital']; + const currentStepIndex = steps.indexOf(currentStep); + + // ============================================================================ + // EFFECTS + // ============================================================================ + + useEffect(() => { + // Fetch hospitals on component mount + dispatch(fetchHospitals()); + }, [dispatch]); + + + + // ============================================================================ + // STEP HANDLERS + // ============================================================================ + + /** + * Handle Email Step Continue + * + * Purpose: Validate email and proceed to next step + */ + const handleEmailContinue = async (email: string) => { + setIsLoading(true); + + try { + const response :any = await authAPI.validatemail({email}); + console.log('response', response); + + if(response.status==409&&response.data.message){ + // Show modal instead of toast for already registered email + setShowEmailRegisteredModal(true) + } + if(response.status==200&&response.data.message){ + setSignUpData(prev => ({ ...prev, email })); + setCurrentStep('password'); + } + + } catch (error) { + Alert.alert('Error', 'Failed to validate email. Please try again.'); + } finally { + setIsLoading(false); + } + }; + + /** + * Handle Password Step Continue + * + * Purpose: Validate password and proceed to next step + */ + const handlePasswordContinue = (password: string) => { + setSignUpData(prev => ({ ...prev, password })); + setCurrentStep('name'); + }; + + /** + * Handle Name Step Continue + * + * Purpose: Validate name and username, then proceed to next step + */ + const handleNameContinue = async (firstName: string, lastName: string, username: string) => { + setIsLoading(true); + + try { + const response:any = await authAPI.validateusername(username); + console.log('response', response); + + if(response.status==409&&response.data.message){ + showError(response.data.message); + } + if(response.status==200&&response.data.message){ + setSignUpData(prev => ({ + ...prev, + first_name: firstName, + last_name: lastName, + username: username + })); + setCurrentStep('document'); + } + } catch (error) { + Alert.alert('Error', 'Failed to validate username. Please try again.'); + } finally { + setIsLoading(false); + } + }; + + /** + * Handle Document Upload Step Continue + * + * Purpose: Save document and proceed to next step + */ + const handleDocumentContinue = (documentUri: string) => { + setSignUpData(prev => ({ ...prev, id_photo_url: documentUri })); + setCurrentStep('hospital'); + }; + + /** + * Handle Hospital Selection Step Continue + * + * Purpose: Complete signup process + */ + const handleHospitalContinue = async (hospitalId: string) => { + const finalData: SignUpData = { + ...signUpData, + hospital_id: hospitalId, + } as SignUpData; + + setSignUpData(finalData); + + // Call completion handler + await onSignUpComplete(finalData); + }; + + /** + * Complete Signup Process + * + * Purpose: Submit final signup data to API + */ + const onSignUpComplete = async (payload: SignUpData) => { + console.log('final payload', payload); + setIsLoading(true); + + try { + const formData = new FormData(); + let role = 'er_physician'; + + formData.append('email', payload.email); + formData.append('password', payload.password); + formData.append('first_name', payload.first_name); + formData.append('last_name', payload.last_name); + formData.append('username', payload.username); + formData.append('dashboard_role', role); + formData.append('hospital_id', payload.hospital_id); + + // Attach file if exists + if (payload.id_photo_url) { + const filePath = payload.id_photo_url; + const file = { + uri: filePath, + name: 'id_photo', + type: 'image/jpg', + }; + formData.append('id_photo_url', file as any); + } + + console.log('payload prepared', formData); + const response :any = await authAPI.signup(formData); + console.log('signup response', response); + + if(response.ok && response.data && response.data.success ) { + //@ts-ignore + showSuccess('Sign Up Successfully') + navigation.navigate('Login'); + // dispatch(setHospitals(response.data.data)) + } else { + showError('error while signup'); + if( response.data && response.data.message ) { + //@ts-ignore + showError(response.data.message) + } + + } + } catch (error: any) { + console.log('error', error); + Alert.alert('Error', 'Failed to create account. Please try again.'); + } finally { + setIsLoading(false); + } + }; + + // ============================================================================ + // NAVIGATION HANDLERS + // ============================================================================ + + /** + * Handle Back Navigation + * + * Purpose: Navigate to previous step or go back to previous screen + */ + const handleBack = () => { + if (currentStepIndex > 0) { + const previousStep = steps[currentStepIndex - 1]; + setCurrentStep(previousStep); + } else { + navigation.goBack(); + } + }; + + /** + * Handle Modal Close + * + * Purpose: Close email already registered modal + */ + const handleCloseModal = () => { + setShowEmailRegisteredModal(false); + }; + + /** + * Handle Go To Login + * + * Purpose: Navigate to login screen + */ + const handleGoToLogin = () => { + setShowEmailRegisteredModal(false); + navigation.navigate('Login'); + }; + + // ============================================================================ + // RENDER FUNCTIONS + // ============================================================================ + + /** + * Render Current Step + * + * Purpose: Render the appropriate step component based on current step + */ + const renderCurrentStep = () => { + console.log('signupdate', signUpData); + + const commonProps = { + onBack: handleBack, + data: signUpData, + isLoading, + }; + + switch (currentStep) { + case 'email': + return ( + + ); + + case 'password': + return ( + + ); + + case 'name': + return ( + + ); + + case 'document': + return ( + + ); + + case 'hospital': + return ( + + ); + + default: + return ( + + ); + } + }; + + // ============================================================================ + // RENDER + // ============================================================================ + + return ( + + + + {/* Conditional Content Rendering */} + {currentStep === 'hospital' ? ( + // For hospital step, render without ScrollView to avoid conflicts + + {renderCurrentStep()} + + ) : ( + // For other steps, use ScrollView for proper scrolling + + {renderCurrentStep()} + + )} + + {/* Progress Bar - Bottom */} + + + + + + {Math.round(((currentStepIndex + 1) / steps.length) * 100)}% Complete + + + + + + ); +}; + +// ============================================================================ +// STYLES +// ============================================================================ + +const styles = StyleSheet.create({ + // Main container + container: { + flex: 1, + backgroundColor: theme.colors.background, + }, + + + + // Progress container + progressContainer: { + paddingHorizontal: theme.spacing.lg, + paddingVertical: theme.spacing.md, + backgroundColor: theme.colors.background, + borderTopWidth: 1, + borderTopColor: theme.colors.border, + }, + + // Progress bar + progressBar: { + height: 4, + backgroundColor: theme.colors.border, + borderRadius: theme.borderRadius.round, + marginBottom: theme.spacing.sm, + overflow: 'hidden', + }, + + // Progress fill + progressFill: { + height: '100%', + backgroundColor: theme.colors.primary, + borderRadius: theme.borderRadius.round, + }, + + // Progress text + progressText: { + fontSize: theme.typography.fontSize.bodySmall, + fontFamily: theme.typography.fontFamily.medium, + color: theme.colors.textSecondary, + textAlign: 'center', + }, + + // Content area + content: { + flex: 1, + }, + + // Content container + contentContainer: { + flexGrow: 1, + padding: theme.spacing.lg, + paddingBottom: theme.spacing.xl, + }, +}); + +export default SignUpScreen; + +/* + * End of File: SignUpScreen.tsx + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Auth/services/authAPI.ts b/app/modules/Auth/services/authAPI.ts new file mode 100644 index 0000000..1cea369 --- /dev/null +++ b/app/modules/Auth/services/authAPI.ts @@ -0,0 +1,38 @@ +/* + * File: authAPI.ts + * Description: API service for authentication using apisauce + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import { create } from 'apisauce'; +import { API_CONFIG } from '../../../shared/utils/constants'; +import { buildHeaders } from '../../../shared/utils/api'; + +const api = create({ + baseURL: API_CONFIG.BASE_URL, // TODO: Replace with actual endpoint +}); + +/** + * login - authenticates user with email and password + */ +export const authAPI = { + login: (email: string, password: string,platform:string) => api.post('/api/auth/auth/login', { email, password,platform },buildHeaders()), + //fetch hospital list + gethospitals: () => api.get('/api/hospitals/hospitals/app_user/hospitals', {},buildHeaders()), + //user signup + signup: (formData:any) => api.post('/api/auth/auth/admin/create-user-fromapp', formData,buildHeaders({ contentType: 'multipart/form-data' })), + //validate email + validatemail: (payload:{email:string}) => api.post('/api/auth/auth/check-email', payload,buildHeaders()), + //change password + changepassword: (payload:{password:string,token:string | undefined}) => api.post('/api/auth/onboarding/change-password', {password:payload.password},buildHeaders({token:payload.token})), + //validate username + validateusername: (username:string|undefined) => api.post('/api/auth/auth/check-username', {username},buildHeaders()) + // Add more endpoints as needed +}; + +/* + * End of File: authAPI.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Auth/services/biometricService.ts b/app/modules/Auth/services/biometricService.ts new file mode 100644 index 0000000..746d76a --- /dev/null +++ b/app/modules/Auth/services/biometricService.ts @@ -0,0 +1,23 @@ +/* + * File: biometricService.ts + * Description: Service for biometric authentication (stub) + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +export const biometricService = { + authenticate: async () => { + // TODO: Implement biometric authentication + return true; + }, + isAvailable: async () => { + // TODO: Check if biometric is available + return true; + }, +}; + +/* + * End of File: biometricService.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Auth/services/index.ts b/app/modules/Auth/services/index.ts new file mode 100644 index 0000000..c4028b5 --- /dev/null +++ b/app/modules/Auth/services/index.ts @@ -0,0 +1,15 @@ +/* + * File: index.ts + * Description: Barrel export for Auth services + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +export * from './'; +export * from './biometricService'; + +/* + * End of File: index.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Auth/services/signupAPI.ts b/app/modules/Auth/services/signupAPI.ts new file mode 100644 index 0000000..aba2b12 --- /dev/null +++ b/app/modules/Auth/services/signupAPI.ts @@ -0,0 +1,392 @@ +/* + * File: signupAPI.ts + * Description: Signup API service with validation and hospital endpoints + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import { create } from 'apisauce'; +import { SignUpData, EmailValidationApiResponse, UsernameValidationApiResponse, HospitalListApiResponse, SignUpApiResponse } from '../types/signup'; + +// ============================================================================ +// API CONFIGURATION +// ============================================================================ + +/** + * API Base Configuration + * + * Purpose: Configure the base API client for signup operations + * + * Features: + * - Base URL configuration + * - Request/response interceptors + * - Error handling + * - Timeout settings + */ +const API_BASE_URL = 'https://api.neoscan-physician.com/v1'; // TODO: Replace with actual API URL + +const api = create({ + baseURL: API_BASE_URL, + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + timeout: 30000, // 30 seconds +}); + +// ============================================================================ +// REQUEST INTERCEPTORS +// ============================================================================ + +/** + * Request Interceptor + * + * Purpose: Add authentication headers and logging + */ +api.addRequestTransform((request) => { + // Add any common headers here + console.log('API Request:', { + method: request.method, + url: request.url, + data: request.data, + }); +}); + +// ============================================================================ +// RESPONSE INTERCEPTORS +// ============================================================================ + +/** + * Response Interceptor + * + * Purpose: Handle common response patterns and errors + */ +api.addResponseTransform((response) => { + console.log('API Response:', { + status: response.status, + url: response.config?.url, + data: response.data, + }); + + // Handle common error patterns + if (response.status === 401) { + // Handle unauthorized access + console.error('Unauthorized access'); + } + + if (response.status === 500) { + // Handle server errors + console.error('Server error occurred'); + } +}); + +// ============================================================================ +// EMAIL VALIDATION API +// ============================================================================ + +/** + * Validate Email + * + * Purpose: Check if email is available for registration + * + * @param email - Email address to validate + * @returns Promise with validation result + */ +export const validatemail = async (email: string): Promise => { + try { + const response = await api.post('/auth/validate-email', { + email, + }); + + if (response.ok && response.data) { + return response.data; + } else { + throw new Error(response.problem || 'Failed to validate email'); + } + } catch (error) { + console.error('Email validation error:', error); + throw error; + } +}; + +// ============================================================================ +// USERNAME VALIDATION API +// ============================================================================ + +/** + * Validate Username + * + * Purpose: Check if username is available for registration + * + * @param username - Username to validate + * @returns Promise with validation result + */ +export const validateusername = async (username: string): Promise => { + try { + const response = await api.post('/auth/validate-username', { + username, + }); + + if (response.ok && response.data) { + return response.data; + } else { + throw new Error(response.problem || 'Failed to validate username'); + } + } catch (error) { + console.error('Username validation error:', error); + throw error; + } +}; + +// ============================================================================ +// HOSPITAL API +// ============================================================================ + +/** + * Get Hospitals List + * + * Purpose: Fetch list of available hospitals + * + * @param params - Query parameters for filtering + * @returns Promise with hospital list + */ +export const gethospitals = async (params?: { + page?: number; + limit?: number; + search?: string; + type?: string; + city?: string; + state?: string; +}): Promise => { + try { + const response = await api.get('/hospitals', params); + + if (response.ok && response.data) { + return response.data; + } else { + throw new Error(response.problem || 'Failed to fetch hospitals'); + } + } catch (error) { + console.error('Get hospitals error:', error); + throw error; + } +}; + +/** + * Get Hospital by ID + * + * Purpose: Fetch specific hospital details + * + * @param hospitalId - Hospital ID + * @returns Promise with hospital details + */ +export const getHospitalById = async (hospitalId: string) => { + try { + const response = await api.get(`/hospitals/${hospitalId}`); + + if (response.ok && response.data) { + return response.data; + } else { + throw new Error(response.problem || 'Failed to fetch hospital details'); + } + } catch (error) { + console.error('Get hospital by ID error:', error); + throw error; + } +}; + +// ============================================================================ +// SIGNUP API +// ============================================================================ + +/** + * Complete Signup + * + * Purpose: Submit complete signup data + * + * @param formData - FormData with signup information + * @returns Promise with signup result + */ +export const signup = async (formData: FormData): Promise => { + try { + const response = await api.post('/auth/signup', formData, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }); +console.log('actual response ',response) + if (response.ok && response.data) { + return response.data; + } else { + throw new Error(response.problem || 'Failed to complete signup'); + } + } catch (error) { + console.error('Complete signup error:', error); + throw error; + } +}; + +// ============================================================================ +// MOCK API FUNCTIONS (FOR DEVELOPMENT) +// ============================================================================ + +/** + * Mock Validate Email + * + * Purpose: Mock email validation for development + * + * @param email - Email address to validate + * @returns Promise with mock validation result + */ +export const mockValidatemail = async (email: string): Promise => { + // Simulate API delay + await new Promise(resolve => setTimeout(resolve, 1000)); + + // Mock validation logic + const isAvailable = email !== 'existing@hospital.com'; + + return { + success: true, + isAvailable, + message: isAvailable ? 'Email is available' : 'Email is already registered', + suggestions: isAvailable ? undefined : ['Try a different email address'], + }; +}; + +/** + * Mock Validate Username + * + * Purpose: Mock username validation for development + * + * @param username - Username to validate + * @returns Promise with mock validation result + */ +export const mockValidateusername = async (username: string): Promise => { + // Simulate API delay + await new Promise(resolve => setTimeout(resolve, 800)); + + // Mock validation logic + const isAvailable = username !== 'existinguser'; + + return { + success: true, + isAvailable, + message: isAvailable ? 'Username is available' : 'Username is already taken', + suggestions: isAvailable ? undefined : ['Try adding numbers or special characters'], + }; +}; + +/** + * Mock Get Hospitals + * + * Purpose: Mock hospital list for development + * + * @returns Promise with mock hospital list + */ +export const mockGethospitals = async (): Promise => { + // Simulate API delay + await new Promise(resolve => setTimeout(resolve, 1500)); + + // Mock hospital data + const mockHospitals = [ + { + id: '1', + name: 'General Hospital', + address: '123 Main Street', + city: 'New York', + state: 'NY', + country: 'USA', + phoneNumber: '+1-555-0123', + email: 'info@generalhospital.com', + website: 'https://generalhospital.com', + type: 'GENERAL' as const, + specialties: ['Emergency Medicine', 'Cardiology', 'Neurology'], + isActive: true, + }, + { + id: '2', + name: 'University Medical Center', + address: '456 University Ave', + city: 'Boston', + state: 'MA', + country: 'USA', + phoneNumber: '+1-555-0456', + email: 'info@umc.edu', + website: 'https://umc.edu', + type: 'UNIVERSITY' as const, + specialties: ['Emergency Medicine', 'Trauma', 'Research'], + isActive: true, + }, + { + id: '3', + name: 'Specialty Medical Center', + address: '789 Specialty Blvd', + city: 'Los Angeles', + state: 'CA', + country: 'USA', + phoneNumber: '+1-555-0789', + email: 'info@specialtycenter.com', + website: 'https://specialtycenter.com', + type: 'SPECIALTY' as const, + specialties: ['Cardiology', 'Neurology', 'Oncology'], + isActive: true, + }, + ]; + + return { + success: true, + data: mockHospitals, + total: mockHospitals.length, + page: 1, + limit: 10, + }; +}; + +/** + * Mock Complete Signup + * + * Purpose: Mock signup completion for development + * + * @param signUpData - Complete signup data + * @returns Promise with mock signup result + */ +export const mockSignup = async (signUpData: SignUpData): Promise => { + // Simulate API delay + await new Promise(resolve => setTimeout(resolve, 2000)); + + // Mock successful signup + return { + success: true, + message: 'Account created successfully', + data: { + userId: 'user_' + Date.now(), + email: signUpData.email, + token: 'mock_token_' + Date.now(), + }, + }; +}; + +// ============================================================================ +// EXPORTS +// ============================================================================ + +export const authAPI = { + // Real API functions + validatemail, + validateusername, + gethospitals, + getHospitalById, + signup, + + // Mock API functions (for development) + mockValidatemail, + mockValidateusername, + mockGethospitals, + mockSignup, +}; + +/* + * End of File: signupAPI.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Auth/types/signup.ts b/app/modules/Auth/types/signup.ts new file mode 100644 index 0000000..107d615 --- /dev/null +++ b/app/modules/Auth/types/signup.ts @@ -0,0 +1,256 @@ +/* + * File: signup.ts + * Description: Type definitions for signup flow + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +// ============================================================================ +// SIGNUP STEP TYPES +// ============================================================================ + +/** + * SignUpStep Type + * + * Purpose: Define the different steps in the signup process + */ +export type SignUpStep = 'email' | 'password' | 'name' | 'document' | 'hospital'; + +// ============================================================================ +// SIGNUP DATA INTERFACES +// ============================================================================ + +/** + * SignUpData Interface + * + * Purpose: Complete signup data structure matching reference code + */ +export interface SignUpData { + // Email and Password + email: string; + password: string; + + // Personal Information + first_name: string; + last_name: string; + username: string; + + // Document + id_photo_url: string | null; + + // Hospital Information + hospital_id: string; +} + +// ============================================================================ +// STEP COMPONENT PROPS INTERFACES +// ============================================================================ + +/** + * Base Step Props Interface + * + * Purpose: Common props for all step components + */ +export interface BaseStepProps { + onBack: () => void; + data: Partial; + isLoading: boolean; +} + +/** + * Email Step Props Interface + * + * Purpose: Props for email step component + */ +export interface EmailStepProps extends BaseStepProps { + onContinue: (email: string) => void; +} + +/** + * Password Step Props Interface + * + * Purpose: Props for password step component + */ +export interface PasswordStepProps extends BaseStepProps { + onContinue: (password: string) => void; +} + +/** + * Name Step Props Interface + * + * Purpose: Props for name step component + */ +export interface NameStepProps extends BaseStepProps { + onContinue: (firstName: string, lastName: string, username: string) => void; +} + +/** + * Document Upload Step Props Interface + * + * Purpose: Props for document upload step component + */ +export interface DocumentUploadStepProps extends BaseStepProps { + onContinue: (documentUri: string) => void; +} + +/** + * Hospital Selection Step Props Interface + * + * Purpose: Props for hospital selection step component + */ +export interface HospitalSelectionStepProps extends BaseStepProps { + onContinue: (hospitalId: string) => void; + hospitals: Hospital[] | null; + hospitalLoading: boolean; +} + +// ============================================================================ +// VALIDATION INTERFACES +// ============================================================================ + +/** + * Validation Result Interface + * + * Purpose: Result of validation operations + */ +export interface ValidationResult { + isValid: boolean; + message?: string; +} + +/** + * Email Validation Result Interface + * + * Purpose: Result of email validation + */ +export interface EmailValidationResult extends ValidationResult { + isAvailable: boolean; + suggestions?: string[]; +} + +/** + * Username Validation Result Interface + * + * Purpose: Result of username validation + */ +export interface UsernameValidationResult extends ValidationResult { + isAvailable: boolean; + suggestions?: string[]; +} + +// ============================================================================ +// API RESPONSE INTERFACES +// ============================================================================ + +/** + * SignUp API Response Interface + * + * Purpose: Response from signup API + */ +export interface SignUpApiResponse { + success: boolean; + message: string; + data?: { + userId: string; + email: string; + token?: string; + }; + errors?: { + [key: string]: string[]; + }; +} + +/** + * Email Validation API Response Interface + * + * Purpose: Response from email validation API + */ +export interface EmailValidationApiResponse { + success: boolean; + isAvailable: boolean; + message: string; + suggestions?: string[]; +} + +/** + * Username Validation API Response Interface + * + * Purpose: Response from username validation API + */ +export interface UsernameValidationApiResponse { + success: boolean; + isAvailable: boolean; + message: string; + suggestions?: string[]; +} + +// ============================================================================ +// HOSPITAL INTERFACES +// ============================================================================ + +/** + * Hospital Interface + * + * Purpose: Hospital information for selection + */ +export interface Hospital { + hospital_id: string | null; + hospital_name: string | null; +} + +/** + * Hospital List API Response Interface + * + * Purpose: Response from hospital list API + */ +export interface HospitalListApiResponse { + success: boolean; + data: Hospital[]; + total: number; + page: number; + limit: number; +} + +// ============================================================================ +// FORM VALIDATION INTERFACES +// ============================================================================ + +/** + * Form Validation Rules Interface + * + * Purpose: Validation rules for form fields + */ +export interface FormValidationRules { + required?: boolean; + minLength?: number; + maxLength?: number; + pattern?: RegExp; + custom?: (value: any) => boolean; +} + +/** + * Form Field Validation Interface + * + * Purpose: Validation state for form fields + */ +export interface FormFieldValidation { + isValid: boolean; + message: string; + isDirty: boolean; + isTouched: boolean; +} + +/** + * Form Validation State Interface + * + * Purpose: Overall form validation state + */ +export interface FormValidationState { + [key: string]: FormFieldValidation; +} + +/* + * End of File: signup.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Dashboard/components/CriticalAlerts.tsx b/app/modules/Dashboard/components/CriticalAlerts.tsx new file mode 100644 index 0000000..157b4f9 --- /dev/null +++ b/app/modules/Dashboard/components/CriticalAlerts.tsx @@ -0,0 +1,188 @@ +/* + * File: CriticalAlerts.tsx + * Description: Critical alerts component displaying emergency notifications + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import React from 'react'; +import { + View, + Text, + TouchableOpacity, + StyleSheet, + ScrollView, +} from 'react-native'; +import { theme } from '../../../theme/theme'; +import { Alert } from '../../../shared/types/alerts'; + +interface CriticalAlertsProps { + alerts: Alert[]; + onAlertPress: (alert: Alert) => void; +} + +export const CriticalAlerts: React.FC = ({ + alerts, + onAlertPress, +}) => { + if (alerts.length === 0) { + return null; + } + + return ( + + + ๐Ÿšจ Critical Alerts + {alerts.length} + + + + {alerts.map((alert) => ( + onAlertPress(alert)} + activeOpacity={0.7} + > + + {alert.type.replace('_', ' ')} + + {new Date(alert.timestamp).toLocaleTimeString()} + + + + {alert.title} + + {alert.message} + + + {alert.patientName && ( + + {alert.patientName} + {alert.bedNumber && ( + Bed {alert.bedNumber} + )} + + )} + + {alert.actionRequired && ( + + Action Required + + )} + + ))} + + + ); +}; + +const styles = StyleSheet.create({ + container: { + backgroundColor: theme.colors.criticalBackground, + borderColor: theme.colors.critical, + borderWidth: 1, + borderRadius: theme.borderRadius.large, + padding: theme.spacing.md, + marginBottom: theme.spacing.lg, + ...theme.shadows.critical, + }, + header: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: theme.spacing.md, + }, + title: { + fontSize: theme.typography.fontSize.displaySmall, + fontFamily: theme.typography.fontFamily.bold, + color: theme.colors.critical, + }, + count: { + fontSize: theme.typography.fontSize.bodyLarge, + fontFamily: theme.typography.fontFamily.bold, + color: theme.colors.critical, + backgroundColor: theme.colors.background, + paddingHorizontal: theme.spacing.sm, + paddingVertical: theme.spacing.xs, + borderRadius: theme.borderRadius.small, + }, + scrollContainer: { + paddingRight: theme.spacing.md, + }, + alertCard: { + backgroundColor: theme.colors.background, + borderRadius: theme.borderRadius.medium, + padding: theme.spacing.md, + marginRight: theme.spacing.md, + minWidth: 280, + ...theme.shadows.small, + }, + alertHeader: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: theme.spacing.sm, + }, + alertType: { + fontSize: theme.typography.fontSize.bodySmall, + color: theme.colors.critical, + textTransform: 'uppercase', + }, + alertTime: { + fontSize: theme.typography.fontSize.caption, + color: theme.colors.textMuted, + }, + alertTitle: { + fontSize: theme.typography.fontSize.bodyLarge, + fontFamily: theme.typography.fontFamily.bold, + color: theme.colors.textPrimary, + marginBottom: theme.spacing.xs, + }, + alertMessage: { + fontSize: theme.typography.fontSize.bodyMedium, + color: theme.colors.textSecondary, + marginBottom: theme.spacing.sm, + lineHeight: theme.typography.lineHeight.normal * theme.typography.fontSize.bodyMedium, + }, + patientInfo: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: theme.spacing.sm, + }, + patientName: { + fontSize: theme.typography.fontSize.bodyMedium, + color: theme.colors.textPrimary, + }, + bedNumber: { + fontSize: theme.typography.fontSize.bodySmall, + color: theme.colors.textSecondary, + backgroundColor: theme.colors.backgroundAccent, + paddingHorizontal: theme.spacing.sm, + paddingVertical: theme.spacing.xs, + borderRadius: theme.borderRadius.small, + }, + actionRequired: { + backgroundColor: theme.colors.critical, + borderRadius: theme.borderRadius.small, + paddingHorizontal: theme.spacing.sm, + paddingVertical: theme.spacing.xs, + alignSelf: 'flex-start', + }, + actionText: { + fontSize: theme.typography.fontSize.caption, + color: theme.colors.background, + textTransform: 'uppercase', + }, +}); + +/* + * End of File: CriticalAlerts.tsx + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Dashboard/components/DashboardHeader.tsx b/app/modules/Dashboard/components/DashboardHeader.tsx new file mode 100644 index 0000000..06ae15c --- /dev/null +++ b/app/modules/Dashboard/components/DashboardHeader.tsx @@ -0,0 +1,120 @@ +/* + * File: DashboardHeader.tsx + * Description: Dashboard header component displaying ER department overview and statistics + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import React from 'react'; +import { + View, + Text, + StyleSheet, +} from 'react-native'; +import { theme } from '../../../theme/theme'; +import { ERDashboard } from '../../../shared/types/dashboard'; + +interface DashboardHeaderProps { + dashboard: ERDashboard; +} + +export const DashboardHeader: React.FC = ({ + dashboard, +}) => { + return ( + + + Emergency Department + + {dashboard.shiftInfo.currentShift} Shift โ€ข {dashboard.shiftInfo.attendingPhysician} + + + + + + {dashboard.totalPatients} + Total Patients + + + + {dashboard.criticalPatients} + + Critical + + + {dashboard.pendingScans} + Pending Scans + + + {dashboard.bedOccupancy}% + Bed Occupancy + + + + + + Last updated: {dashboard.lastUpdated.toLocaleTimeString()} + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + backgroundColor: theme.colors.background, + borderRadius: theme.borderRadius.large, + padding: theme.spacing.lg, + marginBottom: theme.spacing.lg, + ...theme.shadows.medium, + }, + header: { + marginBottom: theme.spacing.lg, + }, + title: { + fontSize: theme.typography.fontSize.displayMedium, + fontFamily: theme.typography.fontFamily.bold, + color: theme.colors.textPrimary, + marginBottom: theme.spacing.xs, + }, + subtitle: { + fontSize: theme.typography.fontSize.bodyMedium, + color: theme.colors.textSecondary, + }, + statsContainer: { + flexDirection: 'row', + justifyContent: 'space-between', + marginBottom: theme.spacing.lg, + }, + statItem: { + alignItems: 'center', + flex: 1, + }, + statValue: { + fontSize: theme.typography.fontSize.displaySmall, + fontFamily: theme.typography.fontFamily.bold, + color: theme.colors.primary, + marginBottom: theme.spacing.xs, + }, + criticalValue: { + color: theme.colors.critical, + }, + statLabel: { + fontSize: theme.typography.fontSize.bodySmall, + color: theme.colors.textSecondary, + textAlign: 'center', + }, + lastUpdated: { + alignItems: 'center', + }, + lastUpdatedText: { + fontSize: theme.typography.fontSize.caption, + color: theme.colors.textMuted, + }, +}); + +/* + * End of File: DashboardHeader.tsx + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Dashboard/components/DepartmentStats.tsx b/app/modules/Dashboard/components/DepartmentStats.tsx new file mode 100644 index 0000000..6e684da --- /dev/null +++ b/app/modules/Dashboard/components/DepartmentStats.tsx @@ -0,0 +1,102 @@ +/* + * File: DepartmentStats.tsx + * Description: Department statistics component displaying patient counts per department + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import React from 'react'; +import { + View, + Text, + StyleSheet, +} from 'react-native'; +import { theme } from '../../../theme/theme'; +import { DepartmentStats as DepartmentStatsType } from '../../../shared/types/dashboard'; + +interface DepartmentStatsProps { + stats: DepartmentStatsType; +} + +export const DepartmentStats: React.FC = ({ + stats, +}) => { + const departments = [ + { key: 'emergency', label: 'Emergency', color: theme.colors.primary }, + { key: 'trauma', label: 'Trauma', color: theme.colors.critical }, + { key: 'cardiac', label: 'Cardiac', color: theme.colors.warning }, + { key: 'neurology', label: 'Neurology', color: theme.colors.info }, + { key: 'pediatrics', label: 'Pediatrics', color: theme.colors.success }, + { key: 'icu', label: 'ICU', color: theme.colors.secondary }, + ]; + + return ( + + Department Overview + + {departments.map((dept) => ( + + + + + {stats[dept.key as keyof DepartmentStatsType]} + + {dept.label} + + + ))} + + + ); +}; + +const styles = StyleSheet.create({ + container: { + backgroundColor: theme.colors.background, + borderRadius: theme.borderRadius.large, + padding: theme.spacing.lg, + marginBottom: theme.spacing.lg, + ...theme.shadows.medium, + }, + title: { + fontSize: theme.typography.fontSize.displaySmall, + fontFamily: theme.typography.fontFamily.bold, + color: theme.colors.textPrimary, + marginBottom: theme.spacing.lg, + }, + statsGrid: { + flexDirection: 'row', + flexWrap: 'wrap', + justifyContent: 'space-between', + }, + statItem: { + flexDirection: 'row', + alignItems: 'center', + width: '48%', + marginBottom: theme.spacing.md, + }, + colorIndicator: { + width: 12, + height: 12, + borderRadius: 6, + marginRight: theme.spacing.sm, + }, + statContent: { + flex: 1, + }, + statValue: { + fontSize: theme.typography.fontSize.bodyLarge, + fontFamily: theme.typography.fontFamily.bold, + color: theme.colors.textPrimary, + }, + statLabel: { + fontSize: theme.typography.fontSize.bodySmall, + color: theme.colors.textSecondary, + }, +}); + +/* + * End of File: DepartmentStats.tsx + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Dashboard/components/PatientCard.tsx b/app/modules/Dashboard/components/PatientCard.tsx new file mode 100644 index 0000000..1a04ec6 --- /dev/null +++ b/app/modules/Dashboard/components/PatientCard.tsx @@ -0,0 +1,362 @@ +/* + * File: PatientCard.tsx + * Description: Patient card component displaying patient information and status + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import React from 'react'; +import { + View, + Text, + TouchableOpacity, + StyleSheet, +} from 'react-native'; +import { theme } from '../../../theme/theme'; +import { Patient } from '../../../shared/types/patient'; +import { getPriorityColor, getStatusColor, calculateAge } from '../../../shared/utils/helpers'; + +/** + * PatientCardProps Interface + * + * Purpose: Defines the props required by the PatientCard component + * + * Props: + * - patient: Patient data object containing all patient information + * - onPress: Callback function triggered when the card is pressed + */ +interface PatientCardProps { + patient: Patient; + onPress: (patient: Patient) => void; +} + +/** + * PatientCard Component + * + * Purpose: Display comprehensive patient information in a compact, touchable card format + * + * Features: + * 1. Patient identification (name, age, gender, MRN) + * 2. Priority and status badges with color coding + * 3. Location information (bed, room, attending physician) + * 4. Real-time vital signs display + * 5. Allergy warnings (if any) + * 6. Current diagnosis and last update time + * + * Data Display: + * - Header: Patient name, demographics, and status badges + * - Location: Bed/room assignment and attending physician + * - Vital Signs: BP, HR, Temperature, Oxygen saturation + * - Allergies: Warning display for known allergies + * - Footer: Current diagnosis and timestamp + * + * Interaction: + * - Touchable card that navigates to detailed patient view + * - Visual feedback with activeOpacity for better UX + */ +export const PatientCard: React.FC = ({ + patient, + onPress, +}) => { + // ============================================================================ + // DATA PROCESSING + // ============================================================================ + + // Calculate patient age from date of birth + const age = calculateAge(patient.dateOfBirth); + + // Get color coding for priority and status badges + const priorityColor = getPriorityColor(patient.priority); // Color based on priority level + const statusColor = getStatusColor(patient.status); // Color based on patient status + + /** + * formatVitalSigns Function + * + * Purpose: Format vital signs data for display in the card + * + * Returns: + * - bp: Blood pressure in systolic/diastolic format + * - hr: Heart rate value + * - temp: Temperature in Celsius + * - o2: Oxygen saturation percentage + */ + const formatVitalSigns = () => { + const { vitalSigns } = patient; + return { + bp: `${vitalSigns.bloodPressure.systolic}/${vitalSigns.bloodPressure.diastolic}`, // Format: 120/80 + hr: vitalSigns.heartRate.value, // Heart rate: 75 + temp: vitalSigns.temperature.value, // Temperature: 37.2 + o2: vitalSigns.oxygenSaturation.value, // Oxygen: 98 + }; + }; + + // Format vital signs for display + const vitals = formatVitalSigns(); + + // ============================================================================ + // RENDER SECTION + // ============================================================================ + + return ( + onPress(patient)} // Navigate to patient details + activeOpacity={0.7} // Visual feedback on touch + > + {/* ======================================================================== + * HEADER SECTION - Patient identification and status badges + * ======================================================================== */} + + {/* Patient information section */} + + {/* Patient full name */} + + {patient.firstName} {patient.lastName} + + {/* Patient demographics and MRN */} + + {age} years โ€ข {patient.gender} โ€ข MRN: {patient.mrn} + + + + {/* Status badges section */} + + {/* Priority badge with color coding */} + + + {patient.priority} + + + {/* Status badge with color coding */} + + + {patient.status} + + + + + + {/* ======================================================================== + * LOCATION SECTION - Bed assignment and attending physician + * ======================================================================== */} + + {/* Bed and room location */} + + Bed {patient.bedNumber} โ€ข Room {patient.roomNumber} + + {/* Attending physician */} + + Dr. {patient.attendingPhysician} + + + + {/* ======================================================================== + * VITAL SIGNS SECTION - Real-time patient vitals + * ======================================================================== */} + + {/* Blood Pressure */} + + BP + {vitals.bp} + + {/* Heart Rate */} + + HR + {vitals.hr} + + {/* Temperature */} + + Temp + {vitals.temp}ยฐC + + {/* Oxygen Saturation */} + + Oโ‚‚ + {vitals.o2}% + + + + {/* ======================================================================== + * ALLERGIES SECTION - Warning display for known allergies + * ======================================================================== */} + {patient.allergies.length > 0 && ( + + Allergies: + + {patient.allergies.map(a => a.name).join(', ')} + + + )} + + {/* ======================================================================== + * FOOTER SECTION - Diagnosis and last update time + * ======================================================================== */} + + {/* Current diagnosis */} + + {patient.currentDiagnosis} + + {/* Last update timestamp */} + + Updated {new Date(patient.lastUpdated).toLocaleTimeString()} + + + + ); +}; + +// ============================================================================ +// STYLES SECTION +// ============================================================================ + +const styles = StyleSheet.create({ + // Main card container with shadow and rounded corners + container: { + backgroundColor: theme.colors.cardBackground, + borderRadius: theme.borderRadius.large, + padding: theme.spacing.md, + marginHorizontal: theme.spacing.md, + marginVertical: theme.spacing.sm, + ...theme.shadows.small, // Add subtle shadow for elevation + }, + + // Header section with patient info and badges + header: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'flex-start', + marginBottom: theme.spacing.sm, + }, + + // Patient information container + patientInfo: { + flex: 1, // Take available space + }, + + // Patient name styling + patientName: { + fontSize: theme.typography.fontSize.bodyLarge, + fontFamily: theme.typography.fontFamily.bold, + color: theme.colors.textPrimary, + marginBottom: theme.spacing.xs, + }, + + // Patient details styling (age, gender, MRN) + patientDetails: { + fontSize: theme.typography.fontSize.bodySmall, + color: theme.colors.textSecondary, + }, + + // Badges container for priority and status + badges: { + flexDirection: 'row', + gap: theme.spacing.xs, // Space between badges + }, + + // Individual badge styling + badge: { + paddingHorizontal: theme.spacing.sm, + paddingVertical: theme.spacing.xs, + borderRadius: theme.borderRadius.small, + }, + + // Badge text styling + badgeText: { + fontSize: theme.typography.fontSize.caption, + fontFamily: theme.typography.fontFamily.medium, + }, + + // Location information container + locationInfo: { + marginBottom: theme.spacing.sm, + }, + + // Bed and room location text + locationText: { + fontSize: theme.typography.fontSize.bodyMedium, + color: theme.colors.textPrimary, + fontFamily: theme.typography.fontFamily.medium, + }, + + // Attending physician text + attendingText: { + fontSize: theme.typography.fontSize.bodySmall, + color: theme.colors.textSecondary, + }, + + // Vital signs container with background + vitalSigns: { + flexDirection: 'row', + justifyContent: 'space-between', + backgroundColor: theme.colors.backgroundAccent, // Light background for vitals + borderRadius: theme.borderRadius.medium, + padding: theme.spacing.sm, + marginBottom: theme.spacing.sm, + }, + + // Individual vital sign item + vitalItem: { + alignItems: 'center', // Center align label and value + }, + + // Vital sign label (BP, HR, Temp, Oโ‚‚) + vitalLabel: { + fontSize: theme.typography.fontSize.caption, + color: theme.colors.textSecondary, + fontFamily: theme.typography.fontFamily.medium, + marginBottom: theme.spacing.xs, + }, + + // Vital sign value styling + vitalValue: { + fontSize: theme.typography.fontSize.bodyMedium, + color: theme.colors.textPrimary, + fontFamily: theme.typography.fontFamily.bold, + }, + + // Allergies container + allergiesContainer: { + marginBottom: theme.spacing.sm, + }, + + // Allergies label with warning color + allergiesLabel: { + fontSize: theme.typography.fontSize.bodySmall, + color: theme.colors.warning, // Warning color for allergies + fontFamily: theme.typography.fontFamily.medium, + marginBottom: theme.spacing.xs, + }, + + // Allergies text listing + allergiesText: { + fontSize: theme.typography.fontSize.bodySmall, + color: theme.colors.textSecondary, + }, + + // Footer container with diagnosis and timestamp + footer: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + }, + + // Current diagnosis text + diagnosisText: { + fontSize: theme.typography.fontSize.bodyMedium, + color: theme.colors.textPrimary, + fontFamily: theme.typography.fontFamily.medium, + flex: 1, // Take available space + }, + + // Last update timestamp + timeText: { + fontSize: theme.typography.fontSize.caption, + color: theme.colors.textMuted, + }, +}); + +/* + * End of File: PatientCard.tsx + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Dashboard/components/QuickActions.tsx b/app/modules/Dashboard/components/QuickActions.tsx new file mode 100644 index 0000000..5d029a1 --- /dev/null +++ b/app/modules/Dashboard/components/QuickActions.tsx @@ -0,0 +1,139 @@ +/* + * File: QuickActions.tsx + * Description: Quick actions component providing common emergency actions + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import React from 'react'; +import { + View, + Text, + TouchableOpacity, + StyleSheet, + ScrollView, +} from 'react-native'; +import { theme } from '../../../theme/theme'; + +interface QuickAction { + id: string; + title: string; + icon: string; + color: string; +} + +interface QuickActionsProps { + onQuickAction: (action: QuickAction) => void; +} + +const quickActions: QuickAction[] = [ + { + id: 'emergency', + title: 'Emergency', + icon: '๐Ÿšจ', + color: theme.colors.critical, + }, + { + id: 'scan', + title: 'Order Scan', + icon: '๐Ÿ“ท', + color: theme.colors.primary, + }, + { + id: 'medication', + title: 'Medication', + icon: '๐Ÿ’Š', + color: theme.colors.warning, + }, + { + id: 'lab', + title: 'Lab Work', + icon: '๐Ÿงช', + color: theme.colors.info, + }, + { + id: 'consult', + title: 'Consult', + icon: '๐Ÿ‘จโ€โš•๏ธ', + color: theme.colors.success, + }, + { + id: 'transfer', + title: 'Transfer', + icon: '๐Ÿš‘', + color: theme.colors.secondary, + }, +]; + +export const QuickActions: React.FC = ({ + onQuickAction, +}) => { + return ( + + Quick Actions + + {quickActions.map((action) => ( + onQuickAction(action)} + activeOpacity={0.7} + > + + {action.icon} + + {action.title} + + ))} + + + ); +}; + +const styles = StyleSheet.create({ + container: { + marginBottom: theme.spacing.lg, + }, + title: { + fontSize: theme.typography.fontSize.displaySmall, + fontFamily: theme.typography.fontFamily.bold, + color: theme.colors.textPrimary, + marginBottom: theme.spacing.md, + }, + scrollContainer: { + paddingRight: theme.spacing.md, + }, + actionButton: { + alignItems: 'center', + marginRight: theme.spacing.lg, + minWidth: 80, + }, + iconContainer: { + width: 60, + height: 60, + borderRadius: 30, + justifyContent: 'center', + alignItems: 'center', + marginBottom: theme.spacing.sm, + ...theme.shadows.small, + }, + icon: { + fontSize: 24, + }, + actionTitle: { + fontSize: theme.typography.fontSize.bodySmall, + color: theme.colors.textPrimary, + fontFamily: theme.typography.fontFamily.medium, + textAlign: 'center', + }, +}); + +/* + * End of File: QuickActions.tsx + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Dashboard/index.ts b/app/modules/Dashboard/index.ts new file mode 100644 index 0000000..53cb34d --- /dev/null +++ b/app/modules/Dashboard/index.ts @@ -0,0 +1,91 @@ +/* + * File: index.ts + * Description: Main exports for Dashboard module + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +// Export screens +export { default as ERDashboardScreen } from './screens/ERDashboardScreen'; + +// Export navigation +export { + DashboardStackNavigator, + DashboardStackParamList, + DashboardNavigationProp, + DashboardScreenProps, + ERDashboardScreenProps, + PatientDetailsScreenProps, + AlertDetailsScreenProps, + DepartmentStatsScreenProps, + QuickActionsScreenProps, + navigateToERDashboard, + navigateToPatientDetails, + navigateToAlertDetails, + navigateToDepartmentStats, + navigateToQuickActions, + goBack, + resetToERDashboard, + replaceWithERDashboard, + navigateToERDashboardAndClearStack, + navigateToPatientDetailsAndClearStack, + navigateToAlertDetailsAndClearStack, +} from './navigation'; + +// Export components +export { default as PatientCard } from './components/PatientCard'; +export { default as CriticalAlerts } from './components/CriticalAlerts'; +export { default as DashboardHeader } from './components/DashboardHeader'; +export { default as QuickActions } from './components/QuickActions'; +export { default as DepartmentStats } from './components/DepartmentStats'; + +// Export Redux +export { + fetchDashboardData, + refreshDashboardData, + clearError, + setFilter, + setSort, + updateConnectionStatus, + updateLastUpdated, + updateDashboardData, +} from './redux/dashboardSlice'; + +export { + fetchAlerts, + acknowledgeAlert, + markAlertAsRead, + clearError as clearAlertsError, + setFilter as setAlertsFilter, + setSort as setAlertsSort, + addAlert, + removeAlert, + updateAlert, + clearAllAlerts, + markAllAsRead, +} from './redux/alertsSlice'; + +export { + setLoading, + showModal, + hideModal, + showOverlay, + hideOverlay, + setCurrentScreen, + clearNavigationStack, + toggleDarkMode, + setFontSize, + toggleHighContrast, + setRefreshing, + setScrolling, + updateLastInteraction, + showError, + clearError as clearUIError, + resetUIState, +} from './redux/uiSlice'; + +/* + * End of File: index.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Dashboard/navigation/DashboardStackNavigator.tsx b/app/modules/Dashboard/navigation/DashboardStackNavigator.tsx new file mode 100644 index 0000000..f430b35 --- /dev/null +++ b/app/modules/Dashboard/navigation/DashboardStackNavigator.tsx @@ -0,0 +1,91 @@ +/* + * File: DashboardStackNavigator.tsx + * Description: Stack navigator for dashboard screens within the Dashboard module + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import React from 'react'; +import { createStackNavigator } from '@react-navigation/stack'; + +// Import dashboard screens +import { ERDashboardScreen } from '../screens/ERDashboardScreen'; + +// Import navigation types +import { DashboardStackParamList } from './navigationTypes'; +import { theme } from '../../../theme'; + +// Create stack navigator for Dashboard module +const Stack = createStackNavigator(); + +/** + * DashboardStackNavigator - Manages navigation between dashboard screens + * + * This navigator handles the flow between: + * - ERDashboardScreen: Main ER dashboard with patient overview + * - Future screens: Patient details, alerts, reports, etc. + * + * Features: + * - Clean header styling + * - Smooth transitions between screens + * - Type-safe navigation parameters + * - Healthcare-focused design + */ +const DashboardStackNavigator: React.FC = () => { + return ( + + {/* ER Dashboard Screen - Main dashboard entry point */} + + + ); +}; + +export default DashboardStackNavigator; + +/* + * End of File: DashboardStackNavigator.tsx + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Dashboard/navigation/index.ts b/app/modules/Dashboard/navigation/index.ts new file mode 100644 index 0000000..1cf5ff9 --- /dev/null +++ b/app/modules/Dashboard/navigation/index.ts @@ -0,0 +1,47 @@ +/* + * File: index.ts + * Description: Barrel exports for Dashboard module navigation + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +// Export main navigator +export { default as DashboardStackNavigator } from './DashboardStackNavigator'; + +// Export navigation types +export type { + DashboardStackParamList, + DashboardNavigationProp, + DashboardScreenProps, + ERDashboardScreenProps, + PatientDetailsScreenProps, + AlertDetailsScreenProps, + DepartmentStatsScreenProps, + QuickActionsScreenProps, + ERDashboardScreenParams, + PatientDetailsScreenParams, + AlertDetailsScreenParams, + DepartmentStatsScreenParams, + QuickActionsScreenParams, +} from './navigationTypes'; + +// Export navigation utilities +export { + navigateToERDashboard, + navigateToPatientDetails, + navigateToAlertDetails, + navigateToDepartmentStats, + navigateToQuickActions, + goBack, + resetToERDashboard, + replaceWithERDashboard, + navigateToERDashboardAndClearStack, + navigateToPatientDetailsAndClearStack, + navigateToAlertDetailsAndClearStack, +} from './navigationUtils'; + +/* + * End of File: index.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Dashboard/navigation/navigationTypes.ts b/app/modules/Dashboard/navigation/navigationTypes.ts new file mode 100644 index 0000000..f1f281c --- /dev/null +++ b/app/modules/Dashboard/navigation/navigationTypes.ts @@ -0,0 +1,171 @@ +/* + * File: navigationTypes.ts + * Description: TypeScript types for Dashboard module navigation + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import { StackNavigationProp } from '@react-navigation/stack'; +import { Patient, Alert as AlertType, ERDashboard } from '../../../shared/types'; + +/** + * DashboardStackParamList - Defines the parameter list for Dashboard stack navigator + * + * This interface defines all the screens available in the Dashboard module + * and their associated navigation parameters. + */ +export type DashboardStackParamList = { + // ER Dashboard screen - Main dashboard with patient overview + ERDashboard: ERDashboardScreenParams; + + // Patient Details screen - Detailed patient information + PatientDetails: PatientDetailsScreenParams; + + // Alert Details screen - Detailed alert information + AlertDetails: AlertDetailsScreenParams; + + // Department Stats screen - Department-specific statistics + DepartmentStats: DepartmentStatsScreenParams; + + // Quick Actions screen - Quick action menu + QuickActions: QuickActionsScreenParams; +}; + +/** + * DashboardNavigationProp - Type for navigation prop in Dashboard screens + * + * This type provides type-safe navigation methods for screens + * within the Dashboard module. + */ +export type DashboardNavigationProp = StackNavigationProp; + +/** + * DashboardScreenProps - Base props interface for Dashboard screens + * + * This interface provides the common props that all Dashboard screens + * will receive, including navigation and route. + */ +export interface DashboardScreenProps { + navigation: DashboardNavigationProp; + route: { + key: string; + name: T; + params: DashboardStackParamList[T]; + }; +} + +// ============================================================================ +// SCREEN PARAMETER TYPES +// ============================================================================ + +/** + * ERDashboardScreenParams + * + * Purpose: Parameters passed to the ER dashboard screen + * + * Parameters: + * - filter: Optional filter to apply to dashboard data + * - refresh: Optional flag to force refresh + */ +export interface ERDashboardScreenParams { + filter?: 'all' | 'critical' | 'active' | 'pending'; + refresh?: boolean; +} + +/** + * PatientDetailsScreenParams + * + * Purpose: Parameters for the patient details screen + * + * Parameters: + * - patientId: Required patient ID to display details + * - patient: Optional patient data to pre-populate + * - fromScreen: Optional source screen for back navigation + */ +export interface PatientDetailsScreenParams { + patientId: string; + patient?: Patient; + fromScreen?: keyof DashboardStackParamList; +} + +/** + * AlertDetailsScreenParams + * + * Purpose: Parameters for the alert details screen + * + * Parameters: + * - alertId: Required alert ID to display details + * - alert: Optional alert data to pre-populate + * - fromScreen: Optional source screen for back navigation + */ +export interface AlertDetailsScreenParams { + alertId: string; + alert?: AlertType; + fromScreen?: keyof DashboardStackParamList; +} + +/** + * DepartmentStatsScreenParams + * + * Purpose: Parameters for the department stats screen + * + * Parameters: + * - department: Required department name + * - dateRange: Optional date range for statistics + */ +export interface DepartmentStatsScreenParams { + department: string; + dateRange?: { + start: Date; + end: Date; + }; +} + +/** + * QuickActionsScreenParams + * + * Purpose: Parameters for the quick actions screen + * + * Parameters: + * - actionType: Optional action type to pre-select + * - patientId: Optional patient ID for patient-specific actions + */ +export interface QuickActionsScreenParams { + actionType?: 'emergency' | 'scan' | 'report' | 'consultation'; + patientId?: string; +} + +// ============================================================================ +// SCREEN-SPECIFIC PROPS TYPES +// ============================================================================ + +/** + * ERDashboardScreenProps - Props for ERDashboardScreen component + */ +export type ERDashboardScreenProps = DashboardScreenProps<'ERDashboard'>; + +/** + * PatientDetailsScreenProps - Props for PatientDetailsScreen component + */ +export type PatientDetailsScreenProps = DashboardScreenProps<'PatientDetails'>; + +/** + * AlertDetailsScreenProps - Props for AlertDetailsScreen component + */ +export type AlertDetailsScreenProps = DashboardScreenProps<'AlertDetails'>; + +/** + * DepartmentStatsScreenProps - Props for DepartmentStatsScreen component + */ +export type DepartmentStatsScreenProps = DashboardScreenProps<'DepartmentStats'>; + +/** + * QuickActionsScreenProps - Props for QuickActionsScreen component + */ +export type QuickActionsScreenProps = DashboardScreenProps<'QuickActions'>; + +/* + * End of File: navigationTypes.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Dashboard/navigation/navigationUtils.ts b/app/modules/Dashboard/navigation/navigationUtils.ts new file mode 100644 index 0000000..0c9f0c9 --- /dev/null +++ b/app/modules/Dashboard/navigation/navigationUtils.ts @@ -0,0 +1,209 @@ +/* + * File: navigationUtils.ts + * Description: Navigation utilities for Dashboard module + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import { DashboardNavigationProp } from './navigationTypes'; +import { Patient, Alert as AlertType } from '../../../shared/types'; + +/** + * DashboardNavigationUtils - Utility functions for Dashboard module navigation + * + * This module provides helper functions for common navigation patterns + * within the Dashboard module, ensuring consistent navigation behavior. + */ + +/** + * Navigate to ER Dashboard screen + * @param navigation - Navigation prop from React Navigation + * @param params - Optional parameters for the dashboard + */ +export const navigateToERDashboard = ( + navigation: DashboardNavigationProp, + params?: { + filter?: 'all' | 'critical' | 'active' | 'pending'; + refresh?: boolean; + } +): void => { + navigation.navigate('ERDashboard', params); +}; + +/** + * Navigate to Patient Details screen + * @param navigation - Navigation prop from React Navigation + * @param patientId - Required patient ID + * @param patient - Optional patient data + * @param fromScreen - Optional source screen + */ +export const navigateToPatientDetails = ( + navigation: DashboardNavigationProp, + patientId: string, + patient?: Patient, + fromScreen?: keyof import('./navigationTypes').DashboardStackParamList +): void => { + navigation.navigate('PatientDetails', { + patientId, + patient, + fromScreen, + }); +}; + +/** + * Navigate to Alert Details screen + * @param navigation - Navigation prop from React Navigation + * @param alertId - Required alert ID + * @param alert - Optional alert data + * @param fromScreen - Optional source screen + */ +export const navigateToAlertDetails = ( + navigation: DashboardNavigationProp, + alertId: string, + alert?: AlertType, + fromScreen?: keyof import('./navigationTypes').DashboardStackParamList +): void => { + navigation.navigate('AlertDetails', { + alertId, + alert, + fromScreen, + }); +}; + +/** + * Navigate to Department Stats screen + * @param navigation - Navigation prop from React Navigation + * @param department - Required department name + * @param dateRange - Optional date range for statistics + */ +export const navigateToDepartmentStats = ( + navigation: DashboardNavigationProp, + department: string, + dateRange?: { + start: Date; + end: Date; + } +): void => { + navigation.navigate('DepartmentStats', { + department, + dateRange, + }); +}; + +/** + * Navigate to Quick Actions screen + * @param navigation - Navigation prop from React Navigation + * @param actionType - Optional action type to pre-select + * @param patientId - Optional patient ID for patient-specific actions + */ +export const navigateToQuickActions = ( + navigation: DashboardNavigationProp, + actionType?: 'emergency' | 'scan' | 'report' | 'consultation', + patientId?: string +): void => { + navigation.navigate('QuickActions', { + actionType, + patientId, + }); +}; + +/** + * Go back to previous screen + * @param navigation - Navigation prop from React Navigation + */ +export const goBack = (navigation: DashboardNavigationProp): void => { + if (navigation.canGoBack()) { + navigation.goBack(); + } +}; + +/** + * Reset navigation stack to ER Dashboard screen + * @param navigation - Navigation prop from React Navigation + * @param params - Optional parameters for the dashboard + */ +export const resetToERDashboard = ( + navigation: DashboardNavigationProp, + params?: { + filter?: 'all' | 'critical' | 'active' | 'pending'; + refresh?: boolean; + } +): void => { + navigation.reset({ + index: 0, + routes: [{ name: 'ERDashboard', params }], + }); +}; + +/** + * Replace current screen with ER Dashboard screen + * @param navigation - Navigation prop from React Navigation + * @param params - Optional parameters for the dashboard + */ +export const replaceWithERDashboard = ( + navigation: DashboardNavigationProp, + params?: { + filter?: 'all' | 'critical' | 'active' | 'pending'; + refresh?: boolean; + } +): void => { + navigation.replace('ERDashboard', params); +}; + +/** + * Navigate to ER Dashboard and clear back stack + * @param navigation - Navigation prop from React Navigation + * @param params - Optional parameters for the dashboard + */ +export const navigateToERDashboardAndClearStack = ( + navigation: DashboardNavigationProp, + params?: { + filter?: 'all' | 'critical' | 'active' | 'pending'; + refresh?: boolean; + } +): void => { + navigation.reset({ + index: 0, + routes: [{ name: 'ERDashboard', params }], + }); +}; + +/** + * Navigate to Patient Details and clear back stack + * @param navigation - Navigation prop from React Navigation + * @param patientId - Required patient ID + * @param patient - Optional patient data + */ +export const navigateToPatientDetailsAndClearStack = ( + navigation: DashboardNavigationProp, + patientId: string, + patient?: Patient +): void => { + navigation.reset({ + index: 0, + routes: [{ name: 'PatientDetails', params: { patientId, patient } }], + }); +}; + +/** + * Navigate to Alert Details and clear back stack + * @param navigation - Navigation prop from React Navigation + * @param alertId - Required alert ID + * @param alert - Optional alert data + */ +export const navigateToAlertDetailsAndClearStack = ( + navigation: DashboardNavigationProp, + alertId: string, + alert?: AlertType +): void => { + navigation.reset({ + index: 0, + routes: [{ name: 'AlertDetails', params: { alertId, alert } }], + }); +}; + +/* + * End of File: navigationUtils.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Dashboard/redux/alertsSlice.ts b/app/modules/Dashboard/redux/alertsSlice.ts new file mode 100644 index 0000000..beaf0ee --- /dev/null +++ b/app/modules/Dashboard/redux/alertsSlice.ts @@ -0,0 +1,341 @@ +/* + * File: alertsSlice.ts + * Description: Alerts state management slice + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; +import { AlertType, AlertsState } from '../../../shared/types'; + +// ============================================================================ +// ASYNC THUNKS +// ============================================================================ + +/** + * Fetch Alerts Async Thunk + * + * Purpose: Fetch alerts from API + * + * @returns Promise with alerts data or error + */ +export const fetchAlerts = createAsyncThunk( + 'alerts/fetchAlerts', + async (_, { rejectWithValue }) => { + try { + // TODO: Replace with actual API call + await new Promise(resolve => setTimeout(resolve, 1000)); + + // Mock alerts data + const mockAlerts: AlertType[] = [ + { + id: '1', + type: 'CRITICAL_FINDING', + priority: 'CRITICAL', + title: 'Critical Finding Detected', + message: 'AI has detected a potential brain bleed in CT scan. Immediate review required.', + patientId: '1', + patientName: 'John Doe', + bedNumber: 'A1', + timestamp: new Date(), + isRead: false, + isAcknowledged: false, + actionRequired: true, + }, + { + id: '2', + type: 'VITAL_SIGNS_ALERT', + priority: 'HIGH', + title: 'Vital Signs Alert', + message: 'Patient vitals showing concerning trends. Blood pressure elevated.', + patientId: '2', + patientName: 'Jane Smith', + bedNumber: 'B2', + timestamp: new Date(Date.now() - 300000), // 5 minutes ago + isRead: true, + isAcknowledged: true, + actionRequired: false, + }, + ]; + + return mockAlerts; + } catch (error) { + return rejectWithValue('Failed to fetch alerts.'); + } + } +); + +/** + * Acknowledge Alert Async Thunk + * + * Purpose: Acknowledge an alert + * + * @param alertId - ID of the alert to acknowledge + * @returns Promise with success or error + */ +export const acknowledgeAlert = createAsyncThunk( + 'alerts/acknowledgeAlert', + async (alertId: string, { rejectWithValue }) => { + try { + // TODO: Replace with actual API call + await new Promise(resolve => setTimeout(resolve, 500)); + return alertId; + } catch (error) { + return rejectWithValue('Failed to acknowledge alert.'); + } + } +); + +/** + * Mark Alert as Read Async Thunk + * + * Purpose: Mark an alert as read + * + * @param alertId - ID of the alert to mark as read + * @returns Promise with success or error + */ +export const markAlertAsRead = createAsyncThunk( + 'alerts/markAlertAsRead', + async (alertId: string, { rejectWithValue }) => { + try { + // TODO: Replace with actual API call + await new Promise(resolve => setTimeout(resolve, 300)); + return alertId; + } catch (error) { + return rejectWithValue('Failed to mark alert as read.'); + } + } +); + +// ============================================================================ +// INITIAL STATE +// ============================================================================ + +/** + * Initial Alerts State + * + * Purpose: Define the initial state for alerts + * + * Features: + * - Alerts list and management + * - Loading states for async operations + * - Error handling and messages + * - Real-time updates tracking + */ +const initialState: AlertsState = { + // Alerts data + alerts: [], + + // Loading states + isLoading: false, + isRefreshing: false, + + // Error handling + error: null, + + // Real-time updates + lastUpdated: null, + unreadCount: 0, + criticalCount: 0, + + // Filters and preferences + selectedFilter: 'all', + sortBy: 'timestamp', + sortOrder: 'desc', +}; + +// ============================================================================ +// ALERTS SLICE +// ============================================================================ + +/** + * Alerts Slice + * + * Purpose: Redux slice for alerts state management + * + * Features: + * - Alerts data management + * - Real-time updates + * - Filtering and sorting + * - Error handling + * - Loading states + */ +const alertsSlice = createSlice({ + name: 'alerts', + initialState, + reducers: { + /** + * Clear Error Action + * + * Purpose: Clear alerts errors + */ + clearError: (state) => { + state.error = null; + }, + + /** + * Set Filter Action + * + * Purpose: Set alerts filter + */ + setFilter: (state, action: PayloadAction<'all' | 'critical' | 'unread' | 'acknowledged'>) => { + state.selectedFilter = action.payload; + }, + + /** + * Set Sort Action + * + * Purpose: Set alerts sort options + */ + setSort: (state, action: PayloadAction<{ by: string; order: 'asc' | 'desc' }>) => { + state.sortBy = action.payload.by; + state.sortOrder = action.payload.order; + }, + + /** + * Add Alert Action + * + * Purpose: Add a new alert + */ + addAlert: (state, action: PayloadAction) => { + state.alerts.unshift(action.payload); + state.unreadCount += 1; + if (action.payload.priority === 'CRITICAL') { + state.criticalCount += 1; + } + state.lastUpdated = new Date(); + }, + + /** + * Remove Alert Action + * + * Purpose: Remove an alert + */ + removeAlert: (state, action: PayloadAction) => { + const alertIndex = state.alerts.findIndex(alert => alert.id === action.payload); + if (alertIndex !== -1) { + const alert = state.alerts[alertIndex]; + if (!alert.isRead) { + state.unreadCount -= 1; + } + if (alert.priority === 'CRITICAL') { + state.criticalCount -= 1; + } + state.alerts.splice(alertIndex, 1); + } + }, + + /** + * Update Alert Action + * + * Purpose: Update an existing alert + */ + updateAlert: (state, action: PayloadAction<{ id: string; updates: Partial }>) => { + const alertIndex = state.alerts.findIndex(alert => alert.id === action.payload.id); + if (alertIndex !== -1) { + const oldAlert = state.alerts[alertIndex]; + state.alerts[alertIndex] = { ...oldAlert, ...action.payload.updates }; + + // Update counters + if (action.payload.updates.isRead !== undefined) { + if (action.payload.updates.isRead && !oldAlert.isRead) { + state.unreadCount -= 1; + } else if (!action.payload.updates.isRead && oldAlert.isRead) { + state.unreadCount += 1; + } + } + } + }, + + /** + * Clear All Alerts Action + * + * Purpose: Clear all alerts + */ + clearAllAlerts: (state) => { + state.alerts = []; + state.unreadCount = 0; + state.criticalCount = 0; + }, + + /** + * Mark All as Read Action + * + * Purpose: Mark all alerts as read + */ + markAllAsRead: (state) => { + state.alerts.forEach(alert => { + alert.isRead = true; + }); + state.unreadCount = 0; + }, + }, + extraReducers: (builder) => { + // Fetch Alerts + builder + .addCase(fetchAlerts.pending, (state) => { + state.isLoading = true; + state.error = null; + }) + .addCase(fetchAlerts.fulfilled, (state, action) => { + state.isLoading = false; + state.alerts = action.payload; + state.unreadCount = action.payload.filter(alert => !alert.isRead).length; + state.criticalCount = action.payload.filter(alert => alert.priority === 'CRITICAL').length; + state.lastUpdated = new Date(); + state.error = null; + }) + .addCase(fetchAlerts.rejected, (state, action) => { + state.isLoading = false; + state.error = action.payload as string; + }); + + // Acknowledge Alert + builder + .addCase(acknowledgeAlert.fulfilled, (state, action) => { + const alertIndex = state.alerts.findIndex(alert => alert.id === action.payload); + if (alertIndex !== -1) { + state.alerts[alertIndex].isAcknowledged = true; + } + }) + .addCase(acknowledgeAlert.rejected, (state, action) => { + state.error = action.payload as string; + }); + + // Mark Alert as Read + builder + .addCase(markAlertAsRead.fulfilled, (state, action) => { + const alertIndex = state.alerts.findIndex(alert => alert.id === action.payload); + if (alertIndex !== -1 && !state.alerts[alertIndex].isRead) { + state.alerts[alertIndex].isRead = true; + state.unreadCount -= 1; + } + }) + .addCase(markAlertAsRead.rejected, (state, action) => { + state.error = action.payload as string; + }); + }, +}); + +// ============================================================================ +// EXPORTS +// ============================================================================ + +export const { + clearError, + setFilter, + setSort, + addAlert, + removeAlert, + updateAlert, + clearAllAlerts, + markAllAsRead, +} = alertsSlice.actions; + +export default alertsSlice.reducer; + +/* + * End of File: alertsSlice.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Dashboard/redux/dashboardSlice.ts b/app/modules/Dashboard/redux/dashboardSlice.ts new file mode 100644 index 0000000..73d198a --- /dev/null +++ b/app/modules/Dashboard/redux/dashboardSlice.ts @@ -0,0 +1,280 @@ +/* + * File: dashboardSlice.ts + * Description: Dashboard state management slice + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; +import { ERDashboard, DashboardState } from '../../../shared/types'; + +// ============================================================================ +// ASYNC THUNKS +// ============================================================================ + +/** + * Fetch Dashboard Data Async Thunk + * + * Purpose: Fetch dashboard data from API + * + * @returns Promise with dashboard data or error + */ +export const fetchDashboardData = createAsyncThunk( + 'dashboard/fetchDashboardData', + async (_, { rejectWithValue }) => { + try { + // TODO: Replace with actual API call + // Simulate API call with timeout + await new Promise(resolve => setTimeout(resolve, 1500)); + + // Mock dashboard data + const mockDashboard: ERDashboard = { + totalPatients: 24, + criticalPatients: 3, + pendingScans: 8, + recentReports: 12, + bedOccupancy: 85, + departmentStats: { + emergency: 8, + trauma: 4, + cardiac: 3, + neurology: 2, + pediatrics: 5, + icu: 2, + }, + shiftInfo: { + currentShift: 'DAY', + startTime: new Date(), + endTime: new Date(), + attendingPhysician: 'Dr. Smith', + residents: ['Dr. Johnson', 'Dr. Williams'], + nurses: ['Nurse Brown', 'Nurse Davis'], + }, + lastUpdated: new Date(), + }; + + return mockDashboard; + } catch (error) { + return rejectWithValue('Failed to fetch dashboard data.'); + } + } +); + +/** + * Refresh Dashboard Data Async Thunk + * + * Purpose: Refresh dashboard data + * + * @returns Promise with updated dashboard data or error + */ +export const refreshDashboardData = createAsyncThunk( + 'dashboard/refreshDashboardData', + async (_, { rejectWithValue }) => { + try { + // TODO: Replace with actual API call + await new Promise(resolve => setTimeout(resolve, 1000)); + + // Mock refreshed dashboard data + const mockDashboard: ERDashboard = { + totalPatients: 26, + criticalPatients: 2, + pendingScans: 6, + recentReports: 15, + bedOccupancy: 87, + departmentStats: { + emergency: 9, + trauma: 3, + cardiac: 4, + neurology: 2, + pediatrics: 6, + icu: 2, + }, + shiftInfo: { + currentShift: 'DAY', + startTime: new Date(), + endTime: new Date(), + attendingPhysician: 'Dr. Smith', + residents: ['Dr. Johnson', 'Dr. Williams'], + nurses: ['Nurse Brown', 'Nurse Davis'], + }, + lastUpdated: new Date(), + }; + + return mockDashboard; + } catch (error) { + return rejectWithValue('Failed to refresh dashboard data.'); + } + } +); + +// ============================================================================ +// INITIAL STATE +// ============================================================================ + +/** + * Initial Dashboard State + * + * Purpose: Define the initial state for dashboard + * + * Features: + * - Dashboard data and statistics + * - Loading states for async operations + * - Error handling and messages + * - Real-time updates tracking + */ +const initialState: DashboardState = { + // Dashboard data + dashboard: null, + + // Loading states + isLoading: false, + isRefreshing: false, + + // Error handling + error: null, + + // Real-time updates + lastUpdated: null, + isConnected: true, + + // Filters and preferences + selectedFilter: 'all', + sortBy: 'priority', + sortOrder: 'desc', +}; + +// ============================================================================ +// DASHBOARD SLICE +// ============================================================================ + +/** + * Dashboard Slice + * + * Purpose: Redux slice for dashboard state management + * + * Features: + * - Dashboard data management + * - Real-time updates + * - Filtering and sorting + * - Error handling + * - Loading states + */ +const dashboardSlice = createSlice({ + name: 'dashboard', + initialState, + reducers: { + /** + * Clear Error Action + * + * Purpose: Clear dashboard errors + */ + clearError: (state) => { + state.error = null; + }, + + /** + * Set Filter Action + * + * Purpose: Set dashboard filter + */ + setFilter: (state, action: PayloadAction<'all' | 'critical' | 'active' | 'pending'>) => { + state.selectedFilter = action.payload; + }, + + /** + * Set Sort Action + * + * Purpose: Set dashboard sort options + */ + setSort: (state, action: PayloadAction<{ by: string; order: 'asc' | 'desc' }>) => { + state.sortBy = action.payload.by; + state.sortOrder = action.payload.order; + }, + + /** + * Update Connection Status Action + * + * Purpose: Update real-time connection status + */ + updateConnectionStatus: (state, action: PayloadAction) => { + state.isConnected = action.payload; + }, + + /** + * Update Last Updated Action + * + * Purpose: Update last updated timestamp + */ + updateLastUpdated: (state, action: PayloadAction) => { + state.lastUpdated = action.payload; + }, + + /** + * Update Dashboard Data Action + * + * Purpose: Update dashboard data manually + */ + updateDashboardData: (state, action: PayloadAction>) => { + if (state.dashboard) { + state.dashboard = { ...state.dashboard, ...action.payload }; + state.lastUpdated = new Date(); + } + }, + }, + extraReducers: (builder) => { + // Fetch Dashboard Data + builder + .addCase(fetchDashboardData.pending, (state) => { + state.isLoading = true; + state.error = null; + }) + .addCase(fetchDashboardData.fulfilled, (state, action) => { + state.isLoading = false; + state.dashboard = action.payload; + state.lastUpdated = action.payload.lastUpdated; + state.error = null; + }) + .addCase(fetchDashboardData.rejected, (state, action) => { + state.isLoading = false; + state.error = action.payload as string; + }); + + // Refresh Dashboard Data + builder + .addCase(refreshDashboardData.pending, (state) => { + state.isRefreshing = true; + state.error = null; + }) + .addCase(refreshDashboardData.fulfilled, (state, action) => { + state.isRefreshing = false; + state.dashboard = action.payload; + state.lastUpdated = action.payload.lastUpdated; + state.error = null; + }) + .addCase(refreshDashboardData.rejected, (state, action) => { + state.isRefreshing = false; + state.error = action.payload as string; + }); + }, +}); + +// ============================================================================ +// EXPORTS +// ============================================================================ + +export const { + clearError, + setFilter, + setSort, + updateConnectionStatus, + updateLastUpdated, + updateDashboardData, +} = dashboardSlice.actions; + +export default dashboardSlice.reducer; + +/* + * End of File: dashboardSlice.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Dashboard/redux/uiSlice.ts b/app/modules/Dashboard/redux/uiSlice.ts new file mode 100644 index 0000000..f8891d5 --- /dev/null +++ b/app/modules/Dashboard/redux/uiSlice.ts @@ -0,0 +1,330 @@ +/* + * File: uiSlice.ts + * Description: UI state management slice + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; + +// ============================================================================ +// UI STATE INTERFACE +// ============================================================================ + +/** + * UI State Interface + * + * Purpose: Define the structure of UI state + * + * Features: + * - Loading states for different UI components + * - Modal and overlay management + * - Navigation state + * - Theme and appearance settings + * - User interaction states + */ +interface UIState { + // Loading states + isLoading: boolean; + loadingMessage: string | null; + + // Modal states + isModalOpen: boolean; + modalType: string | null; + modalData: any; + + // Overlay states + isOverlayVisible: boolean; + overlayType: string | null; + + // Navigation states + currentScreen: string | null; + navigationStack: string[]; + + // Theme and appearance + isDarkMode: boolean; + fontSize: 'small' | 'medium' | 'large'; + highContrast: boolean; + + // User interaction states + isRefreshing: boolean; + isScrolling: boolean; + lastInteraction: Date | null; + + // Error states + hasError: boolean; + errorMessage: string | null; + errorType: 'warning' | 'error' | 'info' | null; +} + +// ============================================================================ +// INITIAL STATE +// ============================================================================ + +/** + * Initial UI State + * + * Purpose: Define the initial state for UI + * + * Features: + * - Default loading states + * - Default modal and overlay states + * - Default theme settings + * - Default interaction states + */ +const initialState: UIState = { + // Loading states + isLoading: false, + loadingMessage: null, + + // Modal states + isModalOpen: false, + modalType: null, + modalData: null, + + // Overlay states + isOverlayVisible: false, + overlayType: null, + + // Navigation states + currentScreen: null, + navigationStack: [], + + // Theme and appearance + isDarkMode: false, + fontSize: 'medium', + highContrast: false, + + // User interaction states + isRefreshing: false, + isScrolling: false, + lastInteraction: null, + + // Error states + hasError: false, + errorMessage: null, + errorType: null, +}; + +// ============================================================================ +// UI SLICE +// ============================================================================ + +/** + * UI Slice + * + * Purpose: Redux slice for UI state management + * + * Features: + * - Loading state management + * - Modal and overlay control + * - Navigation state tracking + * - Theme and appearance settings + * - User interaction tracking + * - Error state management + */ +const uiSlice = createSlice({ + name: 'ui', + initialState, + reducers: { + /** + * Set Loading Action + * + * Purpose: Set loading state with optional message + */ + setLoading: (state, action: PayloadAction<{ isLoading: boolean; message?: string }>) => { + state.isLoading = action.payload.isLoading; + state.loadingMessage = action.payload.message || null; + }, + + /** + * Show Modal Action + * + * Purpose: Show a modal with specific type and data + */ + showModal: (state, action: PayloadAction<{ type: string; data?: any }>) => { + state.isModalOpen = true; + state.modalType = action.payload.type; + state.modalData = action.payload.data || null; + }, + + /** + * Hide Modal Action + * + * Purpose: Hide the current modal + */ + hideModal: (state) => { + state.isModalOpen = false; + state.modalType = null; + state.modalData = null; + }, + + /** + * Show Overlay Action + * + * Purpose: Show an overlay with specific type + */ + showOverlay: (state, action: PayloadAction<{ type: string }>) => { + state.isOverlayVisible = true; + state.overlayType = action.payload.type; + }, + + /** + * Hide Overlay Action + * + * Purpose: Hide the current overlay + */ + hideOverlay: (state) => { + state.isOverlayVisible = false; + state.overlayType = null; + }, + + /** + * Set Current Screen Action + * + * Purpose: Set the current screen name + */ + setCurrentScreen: (state, action: PayloadAction) => { + state.currentScreen = action.payload; + if (!state.navigationStack.includes(action.payload)) { + state.navigationStack.push(action.payload); + } + }, + + /** + * Clear Navigation Stack Action + * + * Purpose: Clear the navigation stack + */ + clearNavigationStack: (state) => { + state.navigationStack = []; + }, + + /** + * Toggle Dark Mode Action + * + * Purpose: Toggle dark mode on/off + */ + toggleDarkMode: (state) => { + state.isDarkMode = !state.isDarkMode; + }, + + /** + * Set Font Size Action + * + * Purpose: Set the font size preference + */ + setFontSize: (state, action: PayloadAction<'small' | 'medium' | 'large'>) => { + state.fontSize = action.payload; + }, + + /** + * Toggle High Contrast Action + * + * Purpose: Toggle high contrast mode + */ + toggleHighContrast: (state) => { + state.highContrast = !state.highContrast; + }, + + /** + * Set Refreshing Action + * + * Purpose: Set refreshing state + */ + setRefreshing: (state, action: PayloadAction) => { + state.isRefreshing = action.payload; + }, + + /** + * Set Scrolling Action + * + * Purpose: Set scrolling state + */ + setScrolling: (state, action: PayloadAction) => { + state.isScrolling = action.payload; + }, + + /** + * Update Last Interaction Action + * + * Purpose: Update the last interaction timestamp + */ + updateLastInteraction: (state) => { + state.lastInteraction = new Date(); + }, + + /** + * Show Error Action + * + * Purpose: Show an error message + */ + showError: (state, action: PayloadAction<{ message: string; type?: 'warning' | 'error' | 'info' }>) => { + state.hasError = true; + state.errorMessage = action.payload.message; + state.errorType = action.payload.type || 'error'; + }, + + /** + * Clear Error Action + * + * Purpose: Clear the current error + */ + clearError: (state) => { + state.hasError = false; + state.errorMessage = null; + state.errorType = null; + }, + + /** + * Reset UI State Action + * + * Purpose: Reset UI state to initial values + */ + resetUIState: (state) => { + state.isLoading = false; + state.loadingMessage = null; + state.isModalOpen = false; + state.modalType = null; + state.modalData = null; + state.isOverlayVisible = false; + state.overlayType = null; + state.isRefreshing = false; + state.isScrolling = false; + state.hasError = false; + state.errorMessage = null; + state.errorType = null; + }, + }, +}); + +// ============================================================================ +// EXPORTS +// ============================================================================ + +export const { + setLoading, + showModal, + hideModal, + showOverlay, + hideOverlay, + setCurrentScreen, + clearNavigationStack, + toggleDarkMode, + setFontSize, + toggleHighContrast, + setRefreshing, + setScrolling, + updateLastInteraction, + showError, + clearError, + resetUIState, +} = uiSlice.actions; + +export default uiSlice.reducer; + +/* + * End of File: uiSlice.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Dashboard/screens/ERDashboardScreen.tsx b/app/modules/Dashboard/screens/ERDashboardScreen.tsx new file mode 100644 index 0000000..822619d --- /dev/null +++ b/app/modules/Dashboard/screens/ERDashboardScreen.tsx @@ -0,0 +1,752 @@ +/* + * File: ERDashboardScreen.tsx + * Description: Main ER dashboard screen displaying patient list, critical alerts, and department statistics + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import React, { useState, useEffect } from 'react'; +import { + View, + Text, + StyleSheet, + ScrollView, + TouchableOpacity, + RefreshControl, + FlatList, + Alert, +} from 'react-native'; +import { theme } from '../../../theme/theme'; +import { ERDashboard, Patient, Alert as AlertType } from '../../../shared/types'; +import { PatientCard } from '../components/PatientCard'; +import { CriticalAlerts } from '../components/CriticalAlerts'; +import { DashboardHeader } from '../components/DashboardHeader'; +import { QuickActions } from '../components/QuickActions'; +import { DepartmentStats } from '../components/DepartmentStats'; + +/** + * ERDashboardScreenProps Interface + * + * Purpose: Defines the props required by the ERDashboardScreen component + * + * Props: + * - navigation: React Navigation object for screen navigation + */ +interface ERDashboardScreenProps { + navigation: any; +} + +/** + * ERDashboardScreen Component + * + * Purpose: Main dashboard screen for Emergency Department physicians + * + * Dashboard Features: + * 1. Real-time patient overview with filtering capabilities + * 2. Critical alerts display for immediate attention + * 3. Quick action buttons for common tasks + * 4. Department statistics and bed occupancy + * 5. Pull-to-refresh functionality for live updates + * 6. Internal data generation for demonstration + * + * Patient Filtering: + * - All: Shows all patients in the system + * - Critical: Shows only patients with critical priority + * - Active: Shows only patients with active status + * + * Data Flow: + * 1. Generate mock data internally for demonstration + * 2. Filter patients based on selected filter + * 3. Display critical alerts at the top + * 4. Show patient cards in scrollable list + * 5. Handle refresh and navigation interactions + */ +export const ERDashboardScreen: React.FC = ({ + navigation, +}) => { + // ============================================================================ + // STATE MANAGEMENT + // ============================================================================ + + // Refresh state for pull-to-refresh functionality + const [refreshing, setRefreshing] = useState(false); + + // Patient filter state to control which patients are displayed + const [selectedFilter, setSelectedFilter] = useState<'all' | 'critical' | 'active'>('all'); + + // Dashboard data state + const [dashboard, setDashboard] = useState(null); + const [patients, setPatients] = useState([]); + const [alerts, setAlerts] = useState([]); + const [isLoading, setIsLoading] = useState(true); + + // ============================================================================ + // MOCK DATA GENERATION + // ============================================================================ + + /** + * generateMockDashboard Function + * + * Purpose: Generate mock dashboard data for demonstration + * + * Returns: ERDashboard object with realistic ER statistics + */ + const generateMockDashboard = (): ERDashboard => ({ + totalPatients: 24, // Total number of patients in ER + criticalPatients: 3, // Number of patients requiring immediate attention + pendingScans: 8, // Number of scans waiting for review + recentReports: 12, // Number of reports generated recently + bedOccupancy: 85, // Percentage of beds currently occupied + departmentStats: { + emergency: 8, // Patients in emergency department + trauma: 4, // Patients in trauma department + cardiac: 3, // Patients in cardiac department + neurology: 2, // Patients in neurology department + pediatrics: 5, // Patients in pediatrics department + icu: 2, // Patients in ICU + }, + shiftInfo: { + currentShift: 'DAY' as const, // Current shift (DAY/NIGHT) + startTime: new Date(), // Shift start time + endTime: new Date(), // Shift end time + attendingPhysician: 'Dr. Smith', // Lead physician on duty + residents: ['Dr. Johnson', 'Dr. Williams'], // Resident physicians + nurses: ['Nurse Brown', 'Nurse Davis'], // Nursing staff + }, + lastUpdated: new Date(), // Last time dashboard was updated + }); + + /** + * generateMockPatients Function + * + * Purpose: Generate mock patient data for demonstration + * + * Returns: Array of Patient objects with realistic medical data + */ + const generateMockPatients = (): Patient[] => [ + { + id: '1', // Unique patient identifier + mrn: 'MRN001', // Medical Record Number + firstName: 'John', + lastName: 'Doe', + dateOfBirth: new Date('1985-03-15'), + gender: 'MALE' as const, + age: 38, + bedNumber: 'A1', // Assigned bed number + roomNumber: '101', // Room number + admissionDate: new Date('2024-01-15'), + status: 'ACTIVE' as const, // Current patient status + priority: 'CRITICAL' as const, // Priority level for treatment + department: 'Emergency', + attendingPhysician: 'Dr. Smith', + allergies: [ + { + id: '1', + name: 'Penicillin', + severity: 'SEVERE' as const, + reaction: 'Anaphylaxis' + }, + ], + medications: [ + { + id: '1', + name: 'Morphine', + dosage: '2mg', + frequency: 'Every 4 hours', + route: 'IV', // Administration route + startDate: new Date(), + status: 'ACTIVE' as const, + prescribedBy: 'Dr. Smith', + }, + ], + vitalSigns: { + bloodPressure: { systolic: 140, diastolic: 90, timestamp: new Date() }, + heartRate: { value: 95, timestamp: new Date() }, + temperature: { value: 37.2, timestamp: new Date() }, + respiratoryRate: { value: 18, timestamp: new Date() }, + oxygenSaturation: { value: 98, timestamp: new Date() }, + }, + medicalHistory: [], + currentDiagnosis: 'Chest pain, rule out MI', // Current medical diagnosis + lastUpdated: new Date(), + }, + { + id: '2', + mrn: 'MRN002', + firstName: 'Jane', + lastName: 'Smith', + dateOfBirth: new Date('1990-07-22'), + gender: 'FEMALE' as const, + age: 33, + bedNumber: 'B2', + roomNumber: '102', + admissionDate: new Date('2024-01-15'), + status: 'ACTIVE' as const, + priority: 'HIGH' as const, + department: 'Trauma', + attendingPhysician: 'Dr. Johnson', + allergies: [], + medications: [], + vitalSigns: { + bloodPressure: { systolic: 120, diastolic: 80, timestamp: new Date() }, + heartRate: { value: 88, timestamp: new Date() }, + temperature: { value: 36.8, timestamp: new Date() }, + respiratoryRate: { value: 16, timestamp: new Date() }, + oxygenSaturation: { value: 99, timestamp: new Date() }, + }, + medicalHistory: [], + currentDiagnosis: 'Multiple trauma from MVA', // MVA = Motor Vehicle Accident + lastUpdated: new Date(), + }, + { + id: '3', + mrn: 'MRN003', + firstName: 'Michael', + lastName: 'Brown', + dateOfBirth: new Date('1978-11-08'), + gender: 'MALE' as const, + age: 45, + bedNumber: 'C3', + roomNumber: '103', + admissionDate: new Date('2024-01-15'), + status: 'PENDING' as const, + priority: 'MEDIUM' as const, + department: 'Cardiac', + attendingPhysician: 'Dr. Williams', + allergies: [ + { + id: '2', + name: 'Aspirin', + severity: 'MODERATE' as const, + reaction: 'Stomach upset' + }, + ], + medications: [ + { + id: '2', + name: 'Nitroglycerin', + dosage: '0.4mg', + frequency: 'As needed', + route: 'Sublingual', + startDate: new Date(), + status: 'ACTIVE' as const, + prescribedBy: 'Dr. Williams', + }, + ], + vitalSigns: { + bloodPressure: { systolic: 160, diastolic: 100, timestamp: new Date() }, + heartRate: { value: 110, timestamp: new Date() }, + temperature: { value: 36.9, timestamp: new Date() }, + respiratoryRate: { value: 20, timestamp: new Date() }, + oxygenSaturation: { value: 95, timestamp: new Date() }, + }, + medicalHistory: [], + currentDiagnosis: 'Hypertension, chest pain', + lastUpdated: new Date(), + }, + { + id: '4', + mrn: 'MRN004', + firstName: 'Sarah', + lastName: 'Wilson', + dateOfBirth: new Date('1995-04-12'), + gender: 'FEMALE' as const, + age: 28, + bedNumber: 'D4', + roomNumber: '104', + admissionDate: new Date('2024-01-15'), + status: 'ACTIVE' as const, + priority: 'CRITICAL' as const, + department: 'Neurology', + attendingPhysician: 'Dr. Davis', + allergies: [], + medications: [ + { + id: '3', + name: 'Mannitol', + dosage: '100ml', + frequency: 'Every 6 hours', + route: 'IV', + startDate: new Date(), + status: 'ACTIVE' as const, + prescribedBy: 'Dr. Davis', + }, + ], + vitalSigns: { + bloodPressure: { systolic: 180, diastolic: 110, timestamp: new Date() }, + heartRate: { value: 60, timestamp: new Date() }, + temperature: { value: 38.5, timestamp: new Date() }, + respiratoryRate: { value: 12, timestamp: new Date() }, + oxygenSaturation: { value: 92, timestamp: new Date() }, + }, + medicalHistory: [], + currentDiagnosis: 'Suspected intracranial hemorrhage', + lastUpdated: new Date(), + }, + { + id: '5', + mrn: 'MRN005', + firstName: 'David', + lastName: 'Miller', + dateOfBirth: new Date('1982-09-30'), + gender: 'MALE' as const, + age: 41, + bedNumber: 'E5', + roomNumber: '105', + admissionDate: new Date('2024-01-15'), + status: 'ACTIVE' as const, + priority: 'HIGH' as const, + department: 'Pediatrics', + attendingPhysician: 'Dr. Brown', + allergies: [ + { + id: '3', + name: 'Latex', + severity: 'SEVERE' as const, + reaction: 'Contact dermatitis' + }, + ], + medications: [], + vitalSigns: { + bloodPressure: { systolic: 110, diastolic: 70, timestamp: new Date() }, + heartRate: { value: 85, timestamp: new Date() }, + temperature: { value: 37.8, timestamp: new Date() }, + respiratoryRate: { value: 22, timestamp: new Date() }, + oxygenSaturation: { value: 97, timestamp: new Date() }, + }, + medicalHistory: [], + currentDiagnosis: 'Fever of unknown origin', + lastUpdated: new Date(), + }, + ]; + + /** + * generateMockAlerts Function + * + * Purpose: Generate mock alert data for demonstration + * + * Returns: Array of Alert objects with realistic medical alerts + */ + const generateMockAlerts = (): AlertType[] => [ + { + id: '1', + type: 'CRITICAL_FINDING' as const, // Type of alert + priority: 'CRITICAL' as const, // Priority level + title: 'Critical Finding Detected', + message: 'AI has detected a potential brain bleed in CT scan. Immediate review required.', + patientId: '4', // Associated patient + patientName: 'Sarah Wilson', + bedNumber: 'D4', + timestamp: new Date(), // When alert was generated + isRead: false, // Whether alert has been read + isAcknowledged: false, // Whether alert has been acknowledged + actionRequired: true, // Whether action is required + }, + { + id: '2', + type: 'VITAL_SIGNS_ALERT' as const, + priority: 'HIGH' as const, + title: 'Vital Sign Alert', + message: 'Blood pressure elevated: 180/110. Patient requires immediate attention.', + patientId: '4', + patientName: 'Sarah Wilson', + bedNumber: 'D4', + timestamp: new Date(Date.now() - 300000), // 5 minutes ago + isRead: false, + isAcknowledged: false, + actionRequired: true, + }, + { + id: '3', + type: 'MEDICATION_ALERT' as const, + priority: 'MEDIUM' as const, + title: 'Medication Due', + message: 'Morphine due for patient John Doe in 15 minutes.', + patientId: '1', + patientName: 'John Doe', + bedNumber: 'A1', + timestamp: new Date(Date.now() - 600000), // 10 minutes ago + isRead: true, + isAcknowledged: true, + actionRequired: false, + }, + ]; + + // ============================================================================ + // EFFECTS + // ============================================================================ + + /** + * useEffect for initial data loading + * + * Purpose: Load initial mock data when component mounts + * + * Flow: + * 1. Set loading state to true + * 2. Generate mock data + * 3. Set data in state + * 4. Set loading state to false + */ + useEffect(() => { + const loadInitialData = async () => { + setIsLoading(true); + + // Simulate API call delay + await new Promise(resolve => setTimeout(resolve, 1000)); + + // Generate and set mock data + setDashboard(generateMockDashboard()); + setPatients(generateMockPatients()); + setAlerts(generateMockAlerts()); + + setIsLoading(false); + }; + + loadInitialData(); + }, []); + + // ============================================================================ + // EVENT HANDLERS + // ============================================================================ + + /** + * handleRefresh Function + * + * Purpose: Handle pull-to-refresh functionality to update dashboard data + * + * Flow: + * 1. Set refreshing state to true (show loading indicator) + * 2. Simulate API call with delay + * 3. Update data with fresh mock data + * 4. Set refreshing state to false (hide loading indicator) + */ + const handleRefresh = async () => { + setRefreshing(true); // Show refresh indicator + + // Simulate API call with 1-second delay + await new Promise(resolve => setTimeout(resolve, 1000)); + + // Update data with fresh mock data + setDashboard(generateMockDashboard()); + setPatients(generateMockPatients()); + setAlerts(generateMockAlerts()); + + setRefreshing(false); // Hide refresh indicator + }; + + /** + * handlePatientPress Function + * + * Purpose: Handle patient card press navigation + * + * @param patient - Patient object that was pressed + */ + const handlePatientPress = (patient: Patient) => { + // TODO: Navigate to patient details screen + console.log('Patient pressed:', patient.firstName, patient.lastName); + Alert.alert('Patient Details', `Navigate to ${patient.firstName} ${patient.lastName}'s details`); + }; + + /** + * handleAlertPress Function + * + * Purpose: Handle alert press interaction + * + * @param alert - Alert object that was pressed + */ + const handleAlertPress = (alert: AlertType) => { + // TODO: Navigate to alert details or patient details + console.log('Alert pressed:', alert.title); + Alert.alert('Alert Details', alert.message); + }; + + // ============================================================================ + // DATA PROCESSING + // ============================================================================ + + /** + * filteredPatients - Computed property + * + * Purpose: Filter patients based on selected filter criteria + * + * Filter Options: + * - 'all': Show all patients regardless of status or priority + * - 'critical': Show only patients with CRITICAL priority + * - 'active': Show only patients with ACTIVE status + */ + const filteredPatients = patients.filter(patient => { + switch (selectedFilter) { + case 'critical': + return patient.priority === 'CRITICAL'; // Only critical priority patients + case 'active': + return patient.status === 'ACTIVE'; // Only active status patients + default: + return true; // All patients + } + }); + + /** + * criticalAlerts - Computed property + * + * Purpose: Extract critical alerts from all alerts for immediate display + * + * Filter: Only alerts with CRITICAL priority that require immediate attention + */ + const criticalAlerts = alerts.filter(alert => alert.priority === 'CRITICAL'); + + /** + * pendingScans - Computed property + * + * Purpose: Identify patients with pending scans or high priority needs + * + * Filter: Patients with PENDING status or HIGH priority + */ + const pendingScans = patients.filter(patient => + patient.status === 'PENDING' || patient.priority === 'HIGH' + ); + + // ============================================================================ + // RENDER FUNCTIONS + // ============================================================================ + + /** + * renderPatientCard Function + * + * Purpose: Render individual patient card component + * + * @param item - Patient data object + * @returns PatientCard component with patient data and press handler + */ + const renderPatientCard = ({ item }: { item: Patient }) => ( + handlePatientPress(item)} // Navigate to patient details + /> + ); + + /** + * renderHeader Function + * + * Purpose: Render the dashboard header section with all dashboard components + * + * Components included: + * - DashboardHeader: Shows shift info and key statistics + * - CriticalAlerts: Displays urgent alerts requiring attention + * - QuickActions: Provides quick access to common functions + * - DepartmentStats: Shows department-wise patient distribution + * - Filter buttons: Allow filtering of patient list + */ + const renderHeader = () => ( + + {/* Dashboard header with shift information and key metrics */} + {dashboard && } + + {/* Critical alerts section - only show if there are critical alerts */} + {criticalAlerts.length > 0 && ( + + )} + + {/* Quick action buttons for common tasks */} + { + // TODO: Implement quick action handlers + console.log('Quick action:', action); + }} + /> + + {/* Department statistics showing patient distribution */} + {dashboard && } + + {/* Patient filter section with filter buttons */} + + Patients + + {/* All patients filter button */} + setSelectedFilter('all')} + > + + All ({patients.length}) + + + + {/* Critical patients filter button */} + setSelectedFilter('critical')} + > + + Critical ({patients.filter(p => p.priority === 'CRITICAL').length}) + + + + {/* Active patients filter button */} + setSelectedFilter('active')} + > + + Active ({patients.filter(p => p.status === 'ACTIVE').length}) + + + + + + ); + + // ============================================================================ + // LOADING STATE + // ============================================================================ + + /** + * Loading state render + * + * Purpose: Show loading indicator while data is being generated + */ + if (isLoading) { + return ( + + Loading Dashboard... + + ); + } + + // ============================================================================ + // MAIN RENDER + // ============================================================================ + + return ( + + {/* FlatList for efficient rendering of patient cards */} + item.id} // Unique key for each patient + ListHeaderComponent={renderHeader} // Header with dashboard components + contentContainerStyle={styles.listContainer} // Container styling + refreshControl={ + + } + showsVerticalScrollIndicator={false} // Hide scroll indicator for cleaner look + /> + + ); +}; + +// ============================================================================ +// STYLES SECTION +// ============================================================================ + +const styles = StyleSheet.create({ + // Main container for the dashboard screen + container: { + flex: 1, + backgroundColor: theme.colors.background, + }, + + // Loading container for initial data loading + loadingContainer: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: theme.colors.background, + }, + + // Loading text styling + loadingText: { + fontSize: theme.typography.fontSize.bodyLarge, + color: theme.colors.textSecondary, + fontFamily: theme.typography.fontFamily.medium, + }, + + // Container for the FlatList content + listContainer: { + paddingBottom: theme.spacing.lg, // Add bottom padding for tab bar + }, + + // Header section containing dashboard components + header: { + paddingHorizontal: theme.spacing.md, // Horizontal padding for content + }, + + // Container for patient filter section + filterContainer: { + marginTop: theme.spacing.lg, // Top margin for separation + marginBottom: theme.spacing.md, // Bottom margin for list spacing + }, + + // Section title styling + sectionTitle: { + fontSize: theme.typography.fontSize.displaySmall, + fontFamily: theme.typography.fontFamily.bold, + color: theme.colors.textPrimary, + marginBottom: theme.spacing.md, + }, + + // Container for filter buttons + filterButtons: { + flexDirection: 'row', // Horizontal layout + gap: theme.spacing.sm, // Space between buttons + }, + + // Individual filter button styling + filterButton: { + paddingHorizontal: theme.spacing.md, + paddingVertical: theme.spacing.sm, + borderRadius: theme.borderRadius.medium, + borderWidth: 1, + borderColor: theme.colors.border, + backgroundColor: theme.colors.background, + }, + + // Active filter button styling + filterButtonActive: { + backgroundColor: theme.colors.primary, // Primary color background + borderColor: theme.colors.primary, // Primary color border + }, + + // Filter button text styling + filterButtonText: { + fontSize: theme.typography.fontSize.bodyMedium, + color: theme.colors.textSecondary, + fontFamily: theme.typography.fontFamily.medium, + }, + + // Active filter button text styling + filterButtonTextActive: { + color: theme.colors.background, // White text on primary background + }, +}); + +/* + * End of File: ERDashboardScreen.tsx + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/PatientCare/redux/patientCareSlice.ts b/app/modules/PatientCare/redux/patientCareSlice.ts new file mode 100644 index 0000000..6908a7f --- /dev/null +++ b/app/modules/PatientCare/redux/patientCareSlice.ts @@ -0,0 +1,475 @@ +/* + * File: patientCareSlice.ts + * Description: Patient care state management slice + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; +import { Patient, PatientCareState } from '../../../shared/types'; + +// ============================================================================ +// ASYNC THUNKS +// ============================================================================ + +/** + * Fetch Patients Async Thunk + * + * Purpose: Fetch patients list from API + * + * @returns Promise with patients data or error + */ +export const fetchPatients = createAsyncThunk( + 'patientCare/fetchPatients', + async (_, { rejectWithValue }) => { + try { + // TODO: Replace with actual API call + await new Promise(resolve => setTimeout(resolve, 1500)); + + // Mock patients data + const mockPatients: Patient[] = [ + { + id: '1', + mrn: 'MRN001', + firstName: 'John', + lastName: 'Doe', + dateOfBirth: new Date('1985-03-15'), + gender: 'MALE', + age: 38, + bedNumber: 'A1', + roomNumber: '101', + admissionDate: new Date('2024-01-15'), + status: 'ACTIVE', + priority: 'CRITICAL', + department: 'Emergency', + attendingPhysician: 'Dr. Smith', + allergies: [ + { + id: '1', + name: 'Penicillin', + severity: 'SEVERE', + reaction: 'Anaphylaxis', + }, + ], + medications: [ + { + id: '1', + name: 'Morphine', + dosage: '2mg', + frequency: 'Every 4 hours', + route: 'IV', + startDate: new Date(), + status: 'ACTIVE', + prescribedBy: 'Dr. Smith', + }, + ], + vitalSigns: { + bloodPressure: { systolic: 140, diastolic: 90, timestamp: new Date() }, + heartRate: { value: 95, timestamp: new Date() }, + temperature: { value: 37.2, timestamp: new Date() }, + respiratoryRate: { value: 18, timestamp: new Date() }, + oxygenSaturation: { value: 98, timestamp: new Date() }, + }, + medicalHistory: [], + currentDiagnosis: 'Chest pain, rule out MI', + lastUpdated: new Date(), + }, + { + id: '2', + mrn: 'MRN002', + firstName: 'Jane', + lastName: 'Smith', + dateOfBirth: new Date('1990-07-22'), + gender: 'FEMALE', + age: 33, + bedNumber: 'B2', + roomNumber: '102', + admissionDate: new Date('2024-01-15'), + status: 'ACTIVE', + priority: 'HIGH', + department: 'Trauma', + attendingPhysician: 'Dr. Johnson', + allergies: [], + medications: [], + vitalSigns: { + bloodPressure: { systolic: 120, diastolic: 80, timestamp: new Date() }, + heartRate: { value: 88, timestamp: new Date() }, + temperature: { value: 36.8, timestamp: new Date() }, + respiratoryRate: { value: 16, timestamp: new Date() }, + oxygenSaturation: { value: 99, timestamp: new Date() }, + }, + medicalHistory: [], + currentDiagnosis: 'Multiple trauma from MVA', + lastUpdated: new Date(), + }, + ]; + + return mockPatients; + } catch (error) { + return rejectWithValue('Failed to fetch patients.'); + } + } +); + +/** + * Fetch Patient Details Async Thunk + * + * Purpose: Fetch detailed patient information + * + * @param patientId - ID of the patient to fetch + * @returns Promise with patient details or error + */ +export const fetchPatientDetails = createAsyncThunk( + 'patientCare/fetchPatientDetails', + async (patientId: string, { rejectWithValue }) => { + try { + // TODO: Replace with actual API call + await new Promise(resolve => setTimeout(resolve, 1000)); + + // Mock patient details (same as above but with more detailed info) + const mockPatient: Patient = { + id: patientId, + mrn: `MRN${patientId.padStart(3, '0')}`, + firstName: 'John', + lastName: 'Doe', + dateOfBirth: new Date('1985-03-15'), + gender: 'MALE', + age: 38, + bedNumber: 'A1', + roomNumber: '101', + admissionDate: new Date('2024-01-15'), + status: 'ACTIVE', + priority: 'CRITICAL', + department: 'Emergency', + attendingPhysician: 'Dr. Smith', + allergies: [ + { + id: '1', + name: 'Penicillin', + severity: 'SEVERE', + reaction: 'Anaphylaxis', + }, + ], + medications: [ + { + id: '1', + name: 'Morphine', + dosage: '2mg', + frequency: 'Every 4 hours', + route: 'IV', + startDate: new Date(), + status: 'ACTIVE', + prescribedBy: 'Dr. Smith', + }, + ], + vitalSigns: { + bloodPressure: { systolic: 140, diastolic: 90, timestamp: new Date() }, + heartRate: { value: 95, timestamp: new Date() }, + temperature: { value: 37.2, timestamp: new Date() }, + respiratoryRate: { value: 18, timestamp: new Date() }, + oxygenSaturation: { value: 98, timestamp: new Date() }, + }, + medicalHistory: [], + currentDiagnosis: 'Chest pain, rule out MI', + lastUpdated: new Date(), + }; + + return mockPatient; + } catch (error) { + return rejectWithValue('Failed to fetch patient details.'); + } + } +); + +/** + * Update Patient Async Thunk + * + * Purpose: Update patient information + * + * @param patientData - Updated patient data + * @returns Promise with updated patient or error + */ +export const updatePatient = createAsyncThunk( + 'patientCare/updatePatient', + async (patientData: Partial & { id: string }, { rejectWithValue }) => { + try { + // TODO: Replace with actual API call + await new Promise(resolve => setTimeout(resolve, 800)); + return patientData; + } catch (error) { + return rejectWithValue('Failed to update patient.'); + } + } +); + +// ============================================================================ +// INITIAL STATE +// ============================================================================ + +/** + * Initial Patient Care State + * + * Purpose: Define the initial state for patient care + * + * Features: + * - Patients list and management + * - Current patient details + * - Loading states for async operations + * - Error handling and messages + * - Search and filtering + */ +const initialState: PatientCareState = { + // Patients data + patients: [], + currentPatient: null, + + // Loading states + isLoading: false, + isRefreshing: false, + isLoadingPatientDetails: false, + + // Error handling + error: null, + + // Search and filtering + searchQuery: '', + selectedFilter: 'all', + sortBy: 'priority', + sortOrder: 'desc', + + // Pagination + currentPage: 1, + itemsPerPage: 20, + totalItems: 0, + + // Cache + lastUpdated: null, + cacheExpiry: null, +}; + +// ============================================================================ +// PATIENT CARE SLICE +// ============================================================================ + +/** + * Patient Care Slice + * + * Purpose: Redux slice for patient care state management + * + * Features: + * - Patient data management + * - Search and filtering + * - Pagination + * - Caching + * - Error handling + * - Loading states + */ +const patientCareSlice = createSlice({ + name: 'patientCare', + initialState, + reducers: { + /** + * Clear Error Action + * + * Purpose: Clear patient care errors + */ + clearError: (state) => { + state.error = null; + }, + + /** + * Set Search Query Action + * + * Purpose: Set search query for patients + */ + setSearchQuery: (state, action: PayloadAction) => { + state.searchQuery = action.payload; + state.currentPage = 1; // Reset to first page when searching + }, + + /** + * Set Filter Action + * + * Purpose: Set patient filter + */ + setFilter: (state, action: PayloadAction<'all' | 'active' | 'discharged' | 'critical'>) => { + state.selectedFilter = action.payload; + state.currentPage = 1; // Reset to first page when filtering + }, + + /** + * Set Sort Action + * + * Purpose: Set patient sort options + */ + setSort: (state, action: PayloadAction<{ by: string; order: 'asc' | 'desc' }>) => { + state.sortBy = action.payload.by; + state.sortOrder = action.payload.order; + }, + + /** + * Set Current Page Action + * + * Purpose: Set current page for pagination + */ + setCurrentPage: (state, action: PayloadAction) => { + state.currentPage = action.payload; + }, + + /** + * Set Items Per Page Action + * + * Purpose: Set items per page for pagination + */ + setItemsPerPage: (state, action: PayloadAction) => { + state.itemsPerPage = action.payload; + state.currentPage = 1; // Reset to first page when changing items per page + }, + + /** + * Set Current Patient Action + * + * Purpose: Set the currently selected patient + */ + setCurrentPatient: (state, action: PayloadAction) => { + state.currentPatient = action.payload; + }, + + /** + * Update Patient in List Action + * + * Purpose: Update a patient in the patients list + */ + updatePatientInList: (state, action: PayloadAction) => { + const index = state.patients.findIndex(patient => patient.id === action.payload.id); + if (index !== -1) { + state.patients[index] = action.payload; + } + + // Update current patient if it's the same patient + if (state.currentPatient && state.currentPatient.id === action.payload.id) { + state.currentPatient = action.payload; + } + }, + + /** + * Add Patient Action + * + * Purpose: Add a new patient to the list + */ + addPatient: (state, action: PayloadAction) => { + state.patients.unshift(action.payload); + state.totalItems += 1; + }, + + /** + * Remove Patient Action + * + * Purpose: Remove a patient from the list + */ + removePatient: (state, action: PayloadAction) => { + const index = state.patients.findIndex(patient => patient.id === action.payload); + if (index !== -1) { + state.patients.splice(index, 1); + state.totalItems -= 1; + } + + // Clear current patient if it's the same patient + if (state.currentPatient && state.currentPatient.id === action.payload) { + state.currentPatient = null; + } + }, + + /** + * Clear Cache Action + * + * Purpose: Clear patient data cache + */ + clearCache: (state) => { + state.patients = []; + state.currentPatient = null; + state.lastUpdated = null; + state.cacheExpiry = null; + }, + }, + extraReducers: (builder) => { + // Fetch Patients + builder + .addCase(fetchPatients.pending, (state) => { + state.isLoading = true; + state.error = null; + }) + .addCase(fetchPatients.fulfilled, (state, action) => { + state.isLoading = false; + state.patients = action.payload; + state.totalItems = action.payload.length; + state.lastUpdated = new Date(); + state.cacheExpiry = new Date(Date.now() + 5 * 60 * 1000); // 5 minutes + state.error = null; + }) + .addCase(fetchPatients.rejected, (state, action) => { + state.isLoading = false; + state.error = action.payload as string; + }); + + // Fetch Patient Details + builder + .addCase(fetchPatientDetails.pending, (state) => { + state.isLoadingPatientDetails = true; + state.error = null; + }) + .addCase(fetchPatientDetails.fulfilled, (state, action) => { + state.isLoadingPatientDetails = false; + state.currentPatient = action.payload; + state.error = null; + }) + .addCase(fetchPatientDetails.rejected, (state, action) => { + state.isLoadingPatientDetails = false; + state.error = action.payload as string; + }); + + // Update Patient + builder + .addCase(updatePatient.fulfilled, (state, action) => { + // Update patient in list + const index = state.patients.findIndex(patient => patient.id === action.payload.id); + if (index !== -1) { + state.patients[index] = { ...state.patients[index], ...action.payload }; + } + + // Update current patient if it's the same patient + if (state.currentPatient && state.currentPatient.id === action.payload.id) { + state.currentPatient = { ...state.currentPatient, ...action.payload }; + } + }) + .addCase(updatePatient.rejected, (state, action) => { + state.error = action.payload as string; + }); + }, +}); + +// ============================================================================ +// EXPORTS +// ============================================================================ + +export const { + clearError, + setSearchQuery, + setFilter, + setSort, + setCurrentPage, + setItemsPerPage, + setCurrentPatient, + updatePatientInList, + addPatient, + removePatient, + clearCache, +} = patientCareSlice.actions; + +export default patientCareSlice.reducer; + +/* + * End of File: patientCareSlice.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Settings/components/ProfileCard.tsx b/app/modules/Settings/components/ProfileCard.tsx new file mode 100644 index 0000000..1901986 --- /dev/null +++ b/app/modules/Settings/components/ProfileCard.tsx @@ -0,0 +1,310 @@ +/* + * File: ProfileCard.tsx + * Description: Profile card component displaying user information + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import React from 'react'; +import { View, Text, StyleSheet, TouchableOpacity, Image } from 'react-native'; +import { theme } from '../../../theme/theme'; +import { UserProfile } from '../../../shared/types'; + +/** + * ProfileCardProps Interface + * + * Purpose: Defines the props required by the ProfileCard component + * + * Props: + * - profile: User profile data to display + * - onPress: Callback function when card is pressed + */ +interface ProfileCardProps { + profile: UserProfile; + onPress: () => void; +} + +/** + * ProfileCard Component + * + * Purpose: Displays user profile information in a card format + * + * Features: + * - Profile picture display + * - User name and credentials + * - Department and role information + * - Contact information + * - Interactive card design + */ +export const ProfileCard: React.FC = ({ profile, onPress }) => { + /** + * formatCredentials Function + * + * Purpose: Format credentials array into a readable string + * + * @returns Formatted credentials string + */ + const formatCredentials = (): string => { + return profile.credentials.join(', '); + }; + + /** + * formatSpecialties Function + * + * Purpose: Format specialties array into a readable string + * + * @returns Formatted specialties string + */ + const formatSpecialties = (): string => { + return profile.specialties.slice(0, 2).join(', '); + }; + + + + return ( + + {/* Profile picture and basic info */} + + + {profile.profilePicture ? ( + { + console.log('Profile image loading error:', error.nativeEvent.error); + }} + /> + ) : ( + + + {profile.firstName.charAt(0)}{profile.lastName.charAt(0)} + + + )} + + + + + {profile.firstName} {profile.lastName} + + {formatCredentials()} + {formatSpecialties()} + + + + Edit + + + + {/* Department and role information */} + + + Department: + {profile.department} + + + + Role: + + {profile.role.replace('_', ' ').toLowerCase().replace(/\b\w/g, l => l.toUpperCase())} + + + + + Experience: + {profile.yearsOfExperience} years + + + + {/* Contact information */} + + + Email: + + {profile.email} + + + + + Phone: + {profile.phoneNumber} + + + + ); +}; + +// ============================================================================ +// STYLES SECTION +// ============================================================================ + +const styles = StyleSheet.create({ + // Main container for the profile card + container: { + backgroundColor: theme.colors.background, + borderRadius: theme.borderRadius.large, + padding: theme.spacing.lg, + marginVertical: theme.spacing.md, + shadowColor: '#000000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.1, + shadowRadius: 4, + elevation: 3, + borderColor: theme.colors.border, + borderWidth: 1, + }, + + // Header section with profile picture and basic info + header: { + flexDirection: 'row', + alignItems: 'center', + marginBottom: theme.spacing.md, + }, + + // Profile image container + imageContainer: { + marginRight: theme.spacing.md, + }, + + // Profile image styling + profileImage: { + width: 60, + height: 60, + borderRadius: 30, + backgroundColor: theme.colors.backgroundAlt, + borderWidth: 2, + borderColor: theme.colors.border, + }, + + // Basic information section + basicInfo: { + flex: 1, + }, + + // User name styling + name: { + fontSize: theme.typography.fontSize.displaySmall, + fontFamily: theme.typography.fontFamily.bold, + color: theme.colors.textPrimary, + marginBottom: theme.spacing.xs, + }, + + // Credentials styling + credentials: { + fontSize: theme.typography.fontSize.bodyMedium, + fontFamily: theme.typography.fontFamily.medium, + color: theme.colors.primary, + marginBottom: theme.spacing.xs, + }, + + // Specialties styling + specialties: { + fontSize: theme.typography.fontSize.bodySmall, + color: theme.colors.textSecondary, + }, + + // Edit icon/text styling + editIcon: { + paddingHorizontal: theme.spacing.sm, + paddingVertical: theme.spacing.xs, + backgroundColor: theme.colors.primary, + borderRadius: theme.borderRadius.small, + }, + + // Edit text styling + editText: { + fontSize: theme.typography.fontSize.bodySmall, + fontFamily: theme.typography.fontFamily.medium, + color: theme.colors.background, + }, + + // Information section styling + infoSection: { + marginBottom: theme.spacing.md, + paddingTop: theme.spacing.md, + borderTopColor: theme.colors.border, + borderTopWidth: 1, + }, + + // Information row styling + infoRow: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: theme.spacing.sm, + }, + + // Information label styling + infoLabel: { + fontSize: theme.typography.fontSize.bodyMedium, + fontFamily: theme.typography.fontFamily.medium, + color: theme.colors.textSecondary, + }, + + // Information value styling + infoValue: { + fontSize: theme.typography.fontSize.bodyMedium, + fontFamily: theme.typography.fontFamily.medium, + color: theme.colors.textPrimary, + textAlign: 'right', + flex: 1, + }, + + // Contact section styling + contactSection: { + paddingTop: theme.spacing.md, + borderTopColor: theme.colors.border, + borderTopWidth: 1, + }, + + // Contact row styling + contactRow: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: theme.spacing.sm, + }, + + // Contact label styling + contactLabel: { + fontSize: theme.typography.fontSize.bodyMedium, + fontFamily: theme.typography.fontFamily.medium, + color: theme.colors.textSecondary, + }, + + // Contact value styling + contactValue: { + fontSize: theme.typography.fontSize.bodyMedium, + fontFamily: theme.typography.fontFamily.medium, + color: theme.colors.textPrimary, + textAlign: 'right', + flex: 1, + }, + + // Fallback avatar styling + fallbackAvatar: { + width: 60, + height: 60, + borderRadius: 30, + backgroundColor: theme.colors.primary, + justifyContent: 'center', + alignItems: 'center', + borderWidth: 2, + borderColor: theme.colors.border, + }, + + // Fallback text styling + fallbackText: { + fontSize: theme.typography.fontSize.displaySmall, + fontFamily: theme.typography.fontFamily.bold, + color: theme.colors.background, + textAlign: 'center', + }, +}); + +/* + * End of File: ProfileCard.tsx + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Settings/components/SettingsHeader.tsx b/app/modules/Settings/components/SettingsHeader.tsx new file mode 100644 index 0000000..02bb2c5 --- /dev/null +++ b/app/modules/Settings/components/SettingsHeader.tsx @@ -0,0 +1,68 @@ +/* + * File: SettingsHeader.tsx + * Description: Header component for the settings screen + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import React from 'react'; +import { View, Text, StyleSheet } from 'react-native'; +import { theme } from '../../../theme/theme'; + +/** + * SettingsHeaderProps Interface + * + * Purpose: Defines the props required by the SettingsHeader component + * + * Props: + * - title: Title text to display in the header + */ +interface SettingsHeaderProps { + title: string; +} + +/** + * SettingsHeader Component + * + * Purpose: Displays the header for the settings screen + * + * Features: + * - Clean, minimal header design + * - Consistent with app theme + * - Proper spacing and typography + */ +export const SettingsHeader: React.FC = ({ title }) => { + return ( + + {title} + + ); +}; + +// ============================================================================ +// STYLES SECTION +// ============================================================================ + +const styles = StyleSheet.create({ + // Main container for the header + container: { + backgroundColor: theme.colors.background, + paddingHorizontal: theme.spacing.md, + paddingVertical: theme.spacing.lg, + borderBottomColor: theme.colors.border, + borderBottomWidth: 1, + }, + + // Title text styling + title: { + fontSize: theme.typography.fontSize.displayMedium, + fontFamily: theme.typography.fontFamily.bold, + color: theme.colors.textPrimary, + }, +}); + +/* + * End of File: SettingsHeader.tsx + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Settings/components/SettingsItemComponent.tsx b/app/modules/Settings/components/SettingsItemComponent.tsx new file mode 100644 index 0000000..b3b4037 --- /dev/null +++ b/app/modules/Settings/components/SettingsItemComponent.tsx @@ -0,0 +1,299 @@ +/* + * File: SettingsItemComponent.tsx + * Description: Settings item component displaying individual settings options + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import React from 'react'; +import { View, Text, StyleSheet, TouchableOpacity, Switch } from 'react-native'; +import { theme } from '../../../theme/theme'; +import { SettingsItem } from '../../../shared/types'; +import Icon from 'react-native-vector-icons/Feather'; +/** + * SettingsItemComponentProps Interface + * + * Purpose: Defines the props required by the SettingsItemComponent + * + * Props: + * - item: Settings item data to display + * - isLast: Boolean indicating if this is the last item in the section + */ +interface SettingsItemComponentProps { + item: SettingsItem; + isLast: boolean; +} + +/** + * SettingsItemComponent Component + * + * Purpose: Displays individual settings items with different interaction types + * + * Features: + * - Navigation items with chevron + * - Toggle items with switch + * - Action items with custom styling + * - Icon support (placeholder) + * - Consistent styling and spacing + */ +export const SettingsItemComponent: React.FC = ({ item, isLast }) => { + /** + * renderIcon Function + * + * Purpose: Render icon for the settings item + * + * @returns Icon component or placeholder + */ + const renderIcon = () => { + // TODO: Implement actual icon rendering + return ( + + + + ); + }; + + /** + * renderValue Function + * + * Purpose: Render the value/action component based on item type + * + * @returns Value component (switch, chevron, or custom) + */ + const renderValue = () => { + switch (item.type) { + case 'TOGGLE': + return ( + + ); + + case 'NAVIGATION': + return ( + โ€บ + ); + + case 'ACTION': + return ( + Sign Out + ); + + default: + return null; + } + }; + + /** + * getItemStyle Function + * + * Purpose: Get appropriate styling based on item type and position + * + * @returns Style object for the item container + */ + const getItemStyle = () => { + const baseStyle = [styles.container]; + + // Add border bottom if not the last item + if (!isLast) { + baseStyle.push(styles.withBorder); + } + + // Add special styling for action items + if (item.type === 'ACTION') { + baseStyle.push(styles.actionItem); + } + + // Add disabled styling if item is disabled + if (item.disabled) { + baseStyle.push(styles.disabledItem); + } + + return baseStyle; + }; + + /** + * getTextStyle Function + * + * Purpose: Get appropriate text styling based on item state + * + * @returns Style object for text + */ + const getTextStyle = () => { + if (item.disabled) { + return [styles.title, styles.disabledText]; + } + if (item.type === 'ACTION') { + return [styles.title, styles.actionTitle]; + } + return styles.title; + }; + + /** + * getSubtitleStyle Function + * + * Purpose: Get appropriate subtitle styling based on item state + * + * @returns Style object for subtitle + */ + const getSubtitleStyle = () => { + if (item.disabled) { + return [styles.subtitle, styles.disabledText]; + } + return styles.subtitle; + }; + + return ( + + {/* Icon */} + {renderIcon()} + + {/* Content */} + + + {item.title} + {item.subtitle && ( + {item.subtitle} + )} + + + {/* Badge */} + {item.badge && ( + + {item.badge} + + )} + + + {/* Value/Action */} + {renderValue()} + + ); +}; + +// ============================================================================ +// STYLES SECTION +// ============================================================================ + +const styles = StyleSheet.create({ + // Main container for the settings item + container: { + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: theme.spacing.md, + paddingVertical: theme.spacing.md, + backgroundColor: theme.colors.background, + borderRadius: theme.borderRadius.medium, + }, + + // Container with bottom border + withBorder: { + borderBottomColor: theme.colors.border, + borderBottomWidth: 1, + }, + + // Action item styling + actionItem: { + backgroundColor: theme.colors.background, + }, + + // Disabled item styling + disabledItem: { + opacity: 0.5, + }, + + // Icon placeholder styling + iconPlaceholder: { + width: 24, + height: 24, + marginRight: theme.spacing.md, + justifyContent: 'center', + alignItems: 'center', + }, + + // Icon text styling + iconText: { + fontSize: 16, + }, + + // Content container + content: { + flex: 1, + flexDirection: 'row', + alignItems: 'center', + }, + + // Text container + textContainer: { + flex: 1, + }, + + // Title text styling + title: { + fontSize: theme.typography.fontSize.bodyMedium, + fontFamily: theme.typography.fontFamily.medium, + color: theme.colors.textPrimary, + marginBottom: theme.spacing.xs, + }, + + // Action title styling + actionTitle: { + color: theme.colors.error, + }, + + // Subtitle text styling + subtitle: { + fontSize: theme.typography.fontSize.bodySmall, + color: theme.colors.textSecondary, + }, + + // Disabled text styling + disabledText: { + color: theme.colors.textMuted, + }, + + // Badge styling + badge: { + backgroundColor: theme.colors.primary, + borderRadius: theme.borderRadius.small, + paddingHorizontal: theme.spacing.sm, + paddingVertical: theme.spacing.xs, + marginLeft: theme.spacing.sm, + }, + + // Badge text styling + badgeText: { + fontSize: theme.typography.fontSize.caption, + fontFamily: theme.typography.fontFamily.medium, + color: theme.colors.background, + }, + + // Chevron styling for navigation items + chevron: { + fontSize: theme.typography.fontSize.bodyLarge, + color: theme.colors.textMuted, + fontFamily: theme.typography.fontFamily.bold, + }, + + // Action text styling + actionText: { + fontSize: theme.typography.fontSize.bodyMedium, + fontFamily: theme.typography.fontFamily.medium, + color: theme.colors.error, + }, +}); + +/* + * End of File: SettingsItemComponent.tsx + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Settings/components/SettingsSectionComponent.tsx b/app/modules/Settings/components/SettingsSectionComponent.tsx new file mode 100644 index 0000000..2b5b56e --- /dev/null +++ b/app/modules/Settings/components/SettingsSectionComponent.tsx @@ -0,0 +1,90 @@ +/* + * File: SettingsSectionComponent.tsx + * Description: Settings section component displaying grouped settings items + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import React from 'react'; +import { View, Text, StyleSheet } from 'react-native'; +import { theme } from '../../../theme/theme'; +import { SettingsSectionData } from '../../../shared/types'; +import { SettingsItemComponent } from './SettingsItemComponent'; + +/** + * SettingsSectionComponentProps Interface + * + * Purpose: Defines the props required by the SettingsSectionComponent + * + * Props: + * - section: Settings section data to display + */ +interface SettingsSectionComponentProps { + section: SettingsSectionData; +} + +/** + * SettingsSectionComponent Component + * + * Purpose: Displays a settings section with grouped items + * + * Features: + * - Section title display + * - Grouped settings items + * - Consistent styling and spacing + * - Clean visual separation between sections + */ +export const SettingsSectionComponent: React.FC = ({ section }) => { + return ( + + {/* Section title */} + {section.title} + + {/* Settings items */} + + {section.items.map((item, index) => ( + + ))} + + + ); +}; + +// ============================================================================ +// STYLES SECTION +// ============================================================================ + +const styles = StyleSheet.create({ + // Main container for the settings section + container: { + marginBottom: theme.spacing.lg, + }, + + // Section title styling + sectionTitle: { + fontSize: theme.typography.fontSize.bodyLarge, + fontFamily: theme.typography.fontFamily.bold, + color: theme.colors.textPrimary, + marginBottom: theme.spacing.sm, + paddingHorizontal: theme.spacing.sm, + }, + + // Container for settings items + itemsContainer: { + backgroundColor: theme.colors.background, + borderRadius: theme.borderRadius.medium, + borderColor: theme.colors.border, + borderWidth: 1, + ...theme.shadows.primary, + }, +}); + +/* + * End of File: SettingsSectionComponent.tsx + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Settings/index.ts b/app/modules/Settings/index.ts new file mode 100644 index 0000000..4b4bdc1 --- /dev/null +++ b/app/modules/Settings/index.ts @@ -0,0 +1,80 @@ +/* + * File: index.ts + * Description: Settings module exports + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +// ============================================================================ +// SCREENS +// ============================================================================ + +export { default as SettingsScreen } from './screens/SettingsScreen'; + +// ============================================================================ +// NAVIGATION +// ============================================================================ + +export { + SettingsStackNavigator, + SettingsStackParamList, + SettingsNavigationProp, + SettingsScreenProps, + ProfileEditScreenProps, + SecuritySettingsScreenProps, + NotificationSettingsScreenProps, + ClinicalPreferencesScreenProps, + PrivacySettingsScreenProps, + AccessibilitySettingsScreenProps, + AboutScreenProps, + HelpSupportScreenProps, + navigateToSettings, + navigateToProfileEdit, + navigateToSecuritySettings, + navigateToNotificationSettings, + navigateToClinicalPreferences, + navigateToPrivacySettings, + navigateToAccessibilitySettings, + navigateToAbout, + navigateToHelpSupport, + goBack, + resetToSettings, + replaceWithSettings, + navigateToSettingsAndClearStack, + navigateToProfileEditAndClearStack, + navigateToSecuritySettingsAndClearStack, +} from './navigation'; + +// ============================================================================ +// COMPONENTS +// ============================================================================ + +export { default as SettingsHeader } from './components/SettingsHeader'; +export { default as ProfileCard } from './components/ProfileCard'; +export { default as SettingsSectionComponent } from './components/SettingsSectionComponent'; +export { default as SettingsItemComponent } from './components/SettingsItemComponent'; + +// ============================================================================ +// REDUX +// ============================================================================ + +export { + fetchUserProfile, + updateUserProfile, + fetchUserPreferences, + updateUserPreferences, + clearError, + setProfileValidation, + setPreferencesValidation, + updateProfileField, + updatePreferenceField, + resetProfile, + resetPreferences, + clearSettingsCache, +} from './redux/settingsSlice'; + +/* + * End of File: index.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Settings/navigation/SettingsStackNavigator.tsx b/app/modules/Settings/navigation/SettingsStackNavigator.tsx new file mode 100644 index 0000000..88c79f3 --- /dev/null +++ b/app/modules/Settings/navigation/SettingsStackNavigator.tsx @@ -0,0 +1,91 @@ +/* + * File: SettingsStackNavigator.tsx + * Description: Stack navigator for settings screens within the Settings module + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import React from 'react'; +import { createStackNavigator } from '@react-navigation/stack'; + +// Import settings screens +import { SettingsScreen } from '../screens/SettingsScreen'; + +// Import navigation types +import { SettingsStackParamList } from './navigationTypes'; +import { theme } from '../../../theme'; + +// Create stack navigator for Settings module +const Stack = createStackNavigator(); + +/** + * SettingsStackNavigator - Manages navigation between settings screens + * + * This navigator handles the flow between: + * - SettingsScreen: Main settings screen with profile and preferences + * - Future screens: Profile edit, security settings, notifications, etc. + * + * Features: + * - Clean header styling + * - Smooth transitions between screens + * - Type-safe navigation parameters + * - Settings-focused design + */ +const SettingsStackNavigator: React.FC = () => { + return ( + + {/* Settings Screen - Main settings entry point */} + + + ); +}; + +export default SettingsStackNavigator; + +/* + * End of File: SettingsStackNavigator.tsx + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Settings/navigation/index.ts b/app/modules/Settings/navigation/index.ts new file mode 100644 index 0000000..b9f40db --- /dev/null +++ b/app/modules/Settings/navigation/index.ts @@ -0,0 +1,59 @@ +/* + * File: index.ts + * Description: Barrel exports for Settings module navigation + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +// Export main navigator +export { default as SettingsStackNavigator } from './SettingsStackNavigator'; + +// Export navigation types +export type { + SettingsStackParamList, + SettingsNavigationProp, + SettingsScreenProps, + SettingsScreenProps as MainSettingsScreenProps, + ProfileEditScreenProps, + SecuritySettingsScreenProps, + NotificationSettingsScreenProps, + ClinicalPreferencesScreenProps, + PrivacySettingsScreenProps, + AccessibilitySettingsScreenProps, + AboutScreenProps, + HelpSupportScreenProps, + SettingsScreenParams, + ProfileEditScreenParams, + SecuritySettingsScreenParams, + NotificationSettingsScreenParams, + ClinicalPreferencesScreenParams, + PrivacySettingsScreenParams, + AccessibilitySettingsScreenParams, + AboutScreenParams, + HelpSupportScreenParams, +} from './navigationTypes'; + +// Export navigation utilities +export { + navigateToSettings, + navigateToProfileEdit, + navigateToSecuritySettings, + navigateToNotificationSettings, + navigateToClinicalPreferences, + navigateToPrivacySettings, + navigateToAccessibilitySettings, + navigateToAbout, + navigateToHelpSupport, + goBack, + resetToSettings, + replaceWithSettings, + navigateToSettingsAndClearStack, + navigateToProfileEditAndClearStack, + navigateToSecuritySettingsAndClearStack, +} from './navigationUtils'; + +/* + * End of File: index.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Settings/navigation/navigationTypes.ts b/app/modules/Settings/navigation/navigationTypes.ts new file mode 100644 index 0000000..4f56697 --- /dev/null +++ b/app/modules/Settings/navigation/navigationTypes.ts @@ -0,0 +1,252 @@ +/* + * File: navigationTypes.ts + * Description: TypeScript types for Settings module navigation + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import { StackNavigationProp } from '@react-navigation/stack'; +import { UserProfile, UserPreferences } from '../../../shared/types'; + +/** + * SettingsStackParamList - Defines the parameter list for Settings stack navigator + * + * This interface defines all the screens available in the Settings module + * and their associated navigation parameters. + */ +export type SettingsStackParamList = { + // Settings screen - Main settings with profile and preferences + Settings: SettingsScreenParams; + + // Profile Edit screen - Edit user profile information + ProfileEdit: ProfileEditScreenParams; + + // Security Settings screen - Security and privacy settings + SecuritySettings: SecuritySettingsScreenParams; + + // Notification Settings screen - Notification preferences + NotificationSettings: NotificationSettingsScreenParams; + + // Clinical Preferences screen - Clinical workflow preferences + ClinicalPreferences: ClinicalPreferencesScreenParams; + + // Privacy Settings screen - Privacy and data settings + PrivacySettings: PrivacySettingsScreenParams; + + // Accessibility Settings screen - Accessibility preferences + AccessibilitySettings: AccessibilitySettingsScreenParams; + + // About screen - App information and version + About: AboutScreenParams; + + // Help & Support screen - Help documentation and support + HelpSupport: HelpSupportScreenParams; +}; + +/** + * SettingsNavigationProp - Type for navigation prop in Settings screens + * + * This type provides type-safe navigation methods for screens + * within the Settings module. + */ +export type SettingsNavigationProp = StackNavigationProp; + +/** + * SettingsScreenProps - Base props interface for Settings screens + * + * This interface provides the common props that all Settings screens + * will receive, including navigation and route. + */ +export interface SettingsScreenProps { + navigation: SettingsNavigationProp; + route: { + key: string; + name: T; + params: SettingsStackParamList[T]; + }; +} + +// ============================================================================ +// SCREEN PARAMETER TYPES +// ============================================================================ + +/** + * SettingsScreenParams + * + * Purpose: Parameters passed to the main settings screen + * + * Parameters: + * - section: Optional section to navigate to within settings + * - refresh: Optional flag to force refresh + */ +export interface SettingsScreenParams { + section?: 'profile' | 'security' | 'notifications' | 'clinical' | 'privacy' | 'accessibility' | 'about' | 'help'; + refresh?: boolean; +} + +/** + * ProfileEditScreenParams + * + * Purpose: Parameters for the profile edit screen + * + * Parameters: + * - profile: Optional profile data to pre-populate + * - fromScreen: Optional source screen for back navigation + */ +export interface ProfileEditScreenParams { + profile?: UserProfile; + fromScreen?: keyof SettingsStackParamList; +} + +/** + * SecuritySettingsScreenParams + * + * Purpose: Parameters for the security settings screen + * + * Parameters: + * - showBiometricSetup: Optional flag to show biometric setup + * - fromScreen: Optional source screen for back navigation + */ +export interface SecuritySettingsScreenParams { + showBiometricSetup?: boolean; + fromScreen?: keyof SettingsStackParamList; +} + +/** + * NotificationSettingsScreenParams + * + * Purpose: Parameters for the notification settings screen + * + * Parameters: + * - preferences: Optional notification preferences to pre-populate + * - fromScreen: Optional source screen for back navigation + */ +export interface NotificationSettingsScreenParams { + preferences?: UserPreferences['notificationPreferences']; + fromScreen?: keyof SettingsStackParamList; +} + +/** + * ClinicalPreferencesScreenParams + * + * Purpose: Parameters for the clinical preferences screen + * + * Parameters: + * - preferences: Optional clinical preferences to pre-populate + * - fromScreen: Optional source screen for back navigation + */ +export interface ClinicalPreferencesScreenParams { + preferences?: UserPreferences['clinicalPreferences']; + fromScreen?: keyof SettingsStackParamList; +} + +/** + * PrivacySettingsScreenParams + * + * Purpose: Parameters for the privacy settings screen + * + * Parameters: + * - preferences: Optional privacy preferences to pre-populate + * - fromScreen: Optional source screen for back navigation + */ +export interface PrivacySettingsScreenParams { + preferences?: UserPreferences['privacyPreferences']; + fromScreen?: keyof SettingsStackParamList; +} + +/** + * AccessibilitySettingsScreenParams + * + * Purpose: Parameters for the accessibility settings screen + * + * Parameters: + * - preferences: Optional accessibility preferences to pre-populate + * - fromScreen: Optional source screen for back navigation + */ +export interface AccessibilitySettingsScreenParams { + preferences?: UserPreferences['accessibilityPreferences']; + fromScreen?: keyof SettingsStackParamList; +} + +/** + * AboutScreenParams + * + * Purpose: Parameters for the about screen + * + * Parameters: + * - showChangelog: Optional flag to show changelog + * - showLicenses: Optional flag to show licenses + */ +export interface AboutScreenParams { + showChangelog?: boolean; + showLicenses?: boolean; +} + +/** + * HelpSupportScreenParams + * + * Purpose: Parameters for the help and support screen + * + * Parameters: + * - topic: Optional help topic to navigate to + * - showContact: Optional flag to show contact form + */ +export interface HelpSupportScreenParams { + topic?: 'faq' | 'troubleshooting' | 'contact' | 'feedback'; + showContact?: boolean; +} + +// ============================================================================ +// SCREEN-SPECIFIC PROPS TYPES +// ============================================================================ + +/** + * SettingsScreenProps - Props for SettingsScreen component + */ +export type SettingsScreenProps = SettingsScreenProps<'Settings'>; + +/** + * ProfileEditScreenProps - Props for ProfileEditScreen component + */ +export type ProfileEditScreenProps = SettingsScreenProps<'ProfileEdit'>; + +/** + * SecuritySettingsScreenProps - Props for SecuritySettingsScreen component + */ +export type SecuritySettingsScreenProps = SettingsScreenProps<'SecuritySettings'>; + +/** + * NotificationSettingsScreenProps - Props for NotificationSettingsScreen component + */ +export type NotificationSettingsScreenProps = SettingsScreenProps<'NotificationSettings'>; + +/** + * ClinicalPreferencesScreenProps - Props for ClinicalPreferencesScreen component + */ +export type ClinicalPreferencesScreenProps = SettingsScreenProps<'ClinicalPreferences'>; + +/** + * PrivacySettingsScreenProps - Props for PrivacySettingsScreen component + */ +export type PrivacySettingsScreenProps = SettingsScreenProps<'PrivacySettings'>; + +/** + * AccessibilitySettingsScreenProps - Props for AccessibilitySettingsScreen component + */ +export type AccessibilitySettingsScreenProps = SettingsScreenProps<'AccessibilitySettings'>; + +/** + * AboutScreenProps - Props for AboutScreen component + */ +export type AboutScreenProps = SettingsScreenProps<'About'>; + +/** + * HelpSupportScreenProps - Props for HelpSupportScreen component + */ +export type HelpSupportScreenProps = SettingsScreenProps<'HelpSupport'>; + +/* + * End of File: navigationTypes.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Settings/navigation/navigationUtils.ts b/app/modules/Settings/navigation/navigationUtils.ts new file mode 100644 index 0000000..6b68fdd --- /dev/null +++ b/app/modules/Settings/navigation/navigationUtils.ts @@ -0,0 +1,264 @@ +/* + * File: navigationUtils.ts + * Description: Navigation utilities for Settings module + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import { SettingsNavigationProp } from './navigationTypes'; +import { UserProfile, UserPreferences } from '../../../shared/types'; + +/** + * SettingsNavigationUtils - Utility functions for Settings module navigation + * + * This module provides helper functions for common navigation patterns + * within the Settings module, ensuring consistent navigation behavior. + */ + +/** + * Navigate to Settings screen + * @param navigation - Navigation prop from React Navigation + * @param params - Optional parameters for the settings screen + */ +export const navigateToSettings = ( + navigation: SettingsNavigationProp, + params?: { + section?: 'profile' | 'security' | 'notifications' | 'clinical' | 'privacy' | 'accessibility' | 'about' | 'help'; + refresh?: boolean; + } +): void => { + navigation.navigate('Settings', params); +}; + +/** + * Navigate to Profile Edit screen + * @param navigation - Navigation prop from React Navigation + * @param profile - Optional profile data to pre-populate + * @param fromScreen - Optional source screen for back navigation + */ +export const navigateToProfileEdit = ( + navigation: SettingsNavigationProp, + profile?: UserProfile, + fromScreen?: keyof import('./navigationTypes').SettingsStackParamList +): void => { + navigation.navigate('ProfileEdit', { + profile, + fromScreen, + }); +}; + +/** + * Navigate to Security Settings screen + * @param navigation - Navigation prop from React Navigation + * @param showBiometricSetup - Optional flag to show biometric setup + * @param fromScreen - Optional source screen for back navigation + */ +export const navigateToSecuritySettings = ( + navigation: SettingsNavigationProp, + showBiometricSetup?: boolean, + fromScreen?: keyof import('./navigationTypes').SettingsStackParamList +): void => { + navigation.navigate('SecuritySettings', { + showBiometricSetup, + fromScreen, + }); +}; + +/** + * Navigate to Notification Settings screen + * @param navigation - Navigation prop from React Navigation + * @param preferences - Optional notification preferences to pre-populate + * @param fromScreen - Optional source screen for back navigation + */ +export const navigateToNotificationSettings = ( + navigation: SettingsNavigationProp, + preferences?: UserPreferences['notificationPreferences'], + fromScreen?: keyof import('./navigationTypes').SettingsStackParamList +): void => { + navigation.navigate('NotificationSettings', { + preferences, + fromScreen, + }); +}; + +/** + * Navigate to Clinical Preferences screen + * @param navigation - Navigation prop from React Navigation + * @param preferences - Optional clinical preferences to pre-populate + * @param fromScreen - Optional source screen for back navigation + */ +export const navigateToClinicalPreferences = ( + navigation: SettingsNavigationProp, + preferences?: UserPreferences['clinicalPreferences'], + fromScreen?: keyof import('./navigationTypes').SettingsStackParamList +): void => { + navigation.navigate('ClinicalPreferences', { + preferences, + fromScreen, + }); +}; + +/** + * Navigate to Privacy Settings screen + * @param navigation - Navigation prop from React Navigation + * @param preferences - Optional privacy preferences to pre-populate + * @param fromScreen - Optional source screen for back navigation + */ +export const navigateToPrivacySettings = ( + navigation: SettingsNavigationProp, + preferences?: UserPreferences['privacyPreferences'], + fromScreen?: keyof import('./navigationTypes').SettingsStackParamList +): void => { + navigation.navigate('PrivacySettings', { + preferences, + fromScreen, + }); +}; + +/** + * Navigate to Accessibility Settings screen + * @param navigation - Navigation prop from React Navigation + * @param preferences - Optional accessibility preferences to pre-populate + * @param fromScreen - Optional source screen for back navigation + */ +export const navigateToAccessibilitySettings = ( + navigation: SettingsNavigationProp, + preferences?: UserPreferences['accessibilityPreferences'], + fromScreen?: keyof import('./navigationTypes').SettingsStackParamList +): void => { + navigation.navigate('AccessibilitySettings', { + preferences, + fromScreen, + }); +}; + +/** + * Navigate to About screen + * @param navigation - Navigation prop from React Navigation + * @param showChangelog - Optional flag to show changelog + * @param showLicenses - Optional flag to show licenses + */ +export const navigateToAbout = ( + navigation: SettingsNavigationProp, + showChangelog?: boolean, + showLicenses?: boolean +): void => { + navigation.navigate('About', { + showChangelog, + showLicenses, + }); +}; + +/** + * Navigate to Help & Support screen + * @param navigation - Navigation prop from React Navigation + * @param topic - Optional help topic to navigate to + * @param showContact - Optional flag to show contact form + */ +export const navigateToHelpSupport = ( + navigation: SettingsNavigationProp, + topic?: 'faq' | 'troubleshooting' | 'contact' | 'feedback', + showContact?: boolean +): void => { + navigation.navigate('HelpSupport', { + topic, + showContact, + }); +}; + +/** + * Go back to previous screen + * @param navigation - Navigation prop from React Navigation + */ +export const goBack = (navigation: SettingsNavigationProp): void => { + if (navigation.canGoBack()) { + navigation.goBack(); + } +}; + +/** + * Reset navigation stack to Settings screen + * @param navigation - Navigation prop from React Navigation + * @param params - Optional parameters for the settings screen + */ +export const resetToSettings = ( + navigation: SettingsNavigationProp, + params?: { + section?: 'profile' | 'security' | 'notifications' | 'clinical' | 'privacy' | 'accessibility' | 'about' | 'help'; + refresh?: boolean; + } +): void => { + navigation.reset({ + index: 0, + routes: [{ name: 'Settings', params }], + }); +}; + +/** + * Replace current screen with Settings screen + * @param navigation - Navigation prop from React Navigation + * @param params - Optional parameters for the settings screen + */ +export const replaceWithSettings = ( + navigation: SettingsNavigationProp, + params?: { + section?: 'profile' | 'security' | 'notifications' | 'clinical' | 'privacy' | 'accessibility' | 'about' | 'help'; + refresh?: boolean; + } +): void => { + navigation.replace('Settings', params); +}; + +/** + * Navigate to Settings and clear back stack + * @param navigation - Navigation prop from React Navigation + * @param params - Optional parameters for the settings screen + */ +export const navigateToSettingsAndClearStack = ( + navigation: SettingsNavigationProp, + params?: { + section?: 'profile' | 'security' | 'notifications' | 'clinical' | 'privacy' | 'accessibility' | 'about' | 'help'; + refresh?: boolean; + } +): void => { + navigation.reset({ + index: 0, + routes: [{ name: 'Settings', params }], + }); +}; + +/** + * Navigate to Profile Edit and clear back stack + * @param navigation - Navigation prop from React Navigation + * @param profile - Optional profile data to pre-populate + */ +export const navigateToProfileEditAndClearStack = ( + navigation: SettingsNavigationProp, + profile?: UserProfile +): void => { + navigation.reset({ + index: 0, + routes: [{ name: 'ProfileEdit', params: { profile } }], + }); +}; + +/** + * Navigate to Security Settings and clear back stack + * @param navigation - Navigation prop from React Navigation + * @param showBiometricSetup - Optional flag to show biometric setup + */ +export const navigateToSecuritySettingsAndClearStack = ( + navigation: SettingsNavigationProp, + showBiometricSetup?: boolean +): void => { + navigation.reset({ + index: 0, + routes: [{ name: 'SecuritySettings', params: { showBiometricSetup } }], + }); +}; + +/* + * End of File: navigationUtils.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Settings/redux/settingsSlice.ts b/app/modules/Settings/redux/settingsSlice.ts new file mode 100644 index 0000000..388c40a --- /dev/null +++ b/app/modules/Settings/redux/settingsSlice.ts @@ -0,0 +1,394 @@ +/* + * File: settingsSlice.ts + * Description: Settings state management slice + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; +import { UserProfile, UserPreferences, SettingsState } from '../../../shared/types'; + +// ============================================================================ +// ASYNC THUNKS +// ============================================================================ + +/** + * Fetch User Profile Async Thunk + * + * Purpose: Fetch user profile from API + * + * @returns Promise with user profile data or error + */ +export const fetchUserProfile = createAsyncThunk( + 'settings/fetchUserProfile', + async (_, { rejectWithValue }) => { + try { + // TODO: Replace with actual API call + await new Promise(resolve => setTimeout(resolve, 1000)); + + // Mock user profile data + const mockProfile: UserProfile = { + id: '1', + email: 'john.smith@hospital.com', + firstName: 'Dr. John', + lastName: 'Smith', + role: 'ATTENDING_PHYSICIAN', + department: 'Emergency Medicine', + credentials: ['MD', 'FACEP'], + specialties: ['Emergency Medicine', 'Trauma'], + profilePicture: null, + phoneNumber: '+1-555-0123', + yearsOfExperience: 15, + isActive: true, + lastLogin: new Date().toISOString(), + permissions: ['READ_PATIENTS', 'WRITE_PATIENTS', 'VIEW_ALERTS'], + }; + + return mockProfile; + } catch (error) { + return rejectWithValue('Failed to fetch user profile.'); + } + } +); + +/** + * Update User Profile Async Thunk + * + * Purpose: Update user profile information + * + * @param profileData - Updated profile data + * @returns Promise with updated profile or error + */ +export const updateUserProfile = createAsyncThunk( + 'settings/updateUserProfile', + async (profileData: Partial & { id: string }, { rejectWithValue }) => { + try { + // TODO: Replace with actual API call + await new Promise(resolve => setTimeout(resolve, 800)); + return profileData; + } catch (error) { + return rejectWithValue('Failed to update user profile.'); + } + } +); + +/** + * Fetch User Preferences Async Thunk + * + * Purpose: Fetch user preferences from API + * + * @returns Promise with user preferences data or error + */ +export const fetchUserPreferences = createAsyncThunk( + 'settings/fetchUserPreferences', + async (_, { rejectWithValue }) => { + try { + // TODO: Replace with actual API call + await new Promise(resolve => setTimeout(resolve, 500)); + + // Mock user preferences data + const mockPreferences: UserPreferences = { + notifications: { + criticalAlerts: true, + patientUpdates: true, + shiftChanges: false, + systemMaintenance: false, + soundEnabled: true, + vibrationEnabled: true, + }, + clinical: { + autoRefreshInterval: 30, + showVitalSigns: true, + showAllergies: true, + showMedications: true, + defaultView: 'list', + }, + privacy: { + biometricEnabled: true, + sessionTimeout: 30, + dataRetention: 90, + auditLogging: true, + }, + accessibility: { + fontSize: 'medium', + highContrast: false, + screenReader: false, + reducedMotion: false, + }, + }; + + return mockPreferences; + } catch (error) { + return rejectWithValue('Failed to fetch user preferences.'); + } + } +); + +/** + * Update User Preferences Async Thunk + * + * Purpose: Update user preferences + * + * @param preferences - Updated preferences data + * @returns Promise with updated preferences or error + */ +export const updateUserPreferences = createAsyncThunk( + 'settings/updateUserPreferences', + async (preferences: Partial, { rejectWithValue }) => { + try { + // TODO: Replace with actual API call + await new Promise(resolve => setTimeout(resolve, 600)); + return preferences; + } catch (error) { + return rejectWithValue('Failed to update user preferences.'); + } + } +); + +// ============================================================================ +// INITIAL STATE +// ============================================================================ + +/** + * Initial Settings State + * + * Purpose: Define the initial state for settings + * + * Features: + * - User profile management + * - User preferences management + * - Loading states for async operations + * - Error handling and messages + * - Settings validation + */ +const initialState: SettingsState = { + // User profile + userProfile: null, + + // User preferences + userPreferences: null, + + // Loading states + isLoading: false, + isUpdatingProfile: false, + isUpdatingPreferences: false, + + // Error handling + error: null, + + // Settings validation + isProfileValid: true, + isPreferencesValid: true, + + // Last updated timestamps + profileLastUpdated: null, + preferencesLastUpdated: null, +}; + +// ============================================================================ +// SETTINGS SLICE +// ============================================================================ + +/** + * Settings Slice + * + * Purpose: Redux slice for settings state management + * + * Features: + * - User profile management + * - User preferences management + * - Settings validation + * - Error handling + * - Loading states + */ +const settingsSlice = createSlice({ + name: 'settings', + initialState, + reducers: { + /** + * Clear Error Action + * + * Purpose: Clear settings errors + */ + clearError: (state) => { + state.error = null; + }, + + /** + * Set Profile Validation Action + * + * Purpose: Set profile validation status + */ + setProfileValidation: (state, action: PayloadAction) => { + state.isProfileValid = action.payload; + }, + + /** + * Set Preferences Validation Action + * + * Purpose: Set preferences validation status + */ + setPreferencesValidation: (state, action: PayloadAction) => { + state.isPreferencesValid = action.payload; + }, + + /** + * Update Profile Field Action + * + * Purpose: Update a specific profile field + */ + updateProfileField: (state, action: PayloadAction<{ field: keyof UserProfile; value: any }>) => { + if (state.userProfile) { + (state.userProfile as any)[action.payload.field] = action.payload.value; + state.profileLastUpdated = new Date(); + } + }, + + /** + * Update Preference Field Action + * + * Purpose: Update a specific preference field + */ + updatePreferenceField: (state, action: PayloadAction<{ path: string; value: any }>) => { + if (state.userPreferences) { + const path = action.payload.path.split('.'); + let current: any = state.userPreferences; + + for (let i = 0; i < path.length - 1; i++) { + current = current[path[i]]; + } + + current[path[path.length - 1]] = action.payload.value; + state.preferencesLastUpdated = new Date(); + } + }, + + /** + * Reset Profile Action + * + * Purpose: Reset profile to last saved state + */ + resetProfile: (state) => { + // TODO: Implement reset logic + state.isProfileValid = true; + }, + + /** + * Reset Preferences Action + * + * Purpose: Reset preferences to last saved state + */ + resetPreferences: (state) => { + // TODO: Implement reset logic + state.isPreferencesValid = true; + }, + + /** + * Clear Settings Cache Action + * + * Purpose: Clear settings data cache + */ + clearSettingsCache: (state) => { + state.userProfile = null; + state.userPreferences = null; + state.profileLastUpdated = null; + state.preferencesLastUpdated = null; + }, + }, + extraReducers: (builder) => { + // Fetch User Profile + builder + .addCase(fetchUserProfile.pending, (state) => { + state.isLoading = true; + state.error = null; + }) + .addCase(fetchUserProfile.fulfilled, (state, action) => { + state.isLoading = false; + state.userProfile = action.payload; + state.profileLastUpdated = new Date(); + state.error = null; + }) + .addCase(fetchUserProfile.rejected, (state, action) => { + state.isLoading = false; + state.error = action.payload as string; + }); + + // Update User Profile + builder + .addCase(updateUserProfile.pending, (state) => { + state.isUpdatingProfile = true; + state.error = null; + }) + .addCase(updateUserProfile.fulfilled, (state, action) => { + state.isUpdatingProfile = false; + if (state.userProfile) { + state.userProfile = { ...state.userProfile, ...action.payload }; + state.profileLastUpdated = new Date(); + } + state.error = null; + }) + .addCase(updateUserProfile.rejected, (state, action) => { + state.isUpdatingProfile = false; + state.error = action.payload as string; + }); + + // Fetch User Preferences + builder + .addCase(fetchUserPreferences.pending, (state) => { + state.isLoading = true; + state.error = null; + }) + .addCase(fetchUserPreferences.fulfilled, (state, action) => { + state.isLoading = false; + state.userPreferences = action.payload; + state.preferencesLastUpdated = new Date(); + state.error = null; + }) + .addCase(fetchUserPreferences.rejected, (state, action) => { + state.isLoading = false; + state.error = action.payload as string; + }); + + // Update User Preferences + builder + .addCase(updateUserPreferences.pending, (state) => { + state.isUpdatingPreferences = true; + state.error = null; + }) + .addCase(updateUserPreferences.fulfilled, (state, action) => { + state.isUpdatingPreferences = false; + if (state.userPreferences) { + state.userPreferences = { ...state.userPreferences, ...action.payload }; + state.preferencesLastUpdated = new Date(); + } + state.error = null; + }) + .addCase(updateUserPreferences.rejected, (state, action) => { + state.isUpdatingPreferences = false; + state.error = action.payload as string; + }); + }, +}); + +// ============================================================================ +// EXPORTS +// ============================================================================ + +export const { + clearError, + setProfileValidation, + setPreferencesValidation, + updateProfileField, + updatePreferenceField, + resetProfile, + resetPreferences, + clearSettingsCache, +} = settingsSlice.actions; + +export default settingsSlice.reducer; + +/* + * End of File: settingsSlice.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Settings/screens/SettingsScreen.tsx b/app/modules/Settings/screens/SettingsScreen.tsx new file mode 100644 index 0000000..5c0295a --- /dev/null +++ b/app/modules/Settings/screens/SettingsScreen.tsx @@ -0,0 +1,610 @@ +/* + * File: SettingsScreen.tsx + * Description: Main settings screen with profile management and app preferences + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import React, { useState, useEffect } from 'react'; +import { + View, + Text, + StyleSheet, + ScrollView, + TouchableOpacity, + Alert, + RefreshControl, + Image, +} from 'react-native'; +import { theme } from '../../../theme/theme'; +import { + SettingsSection, + SettingsSectionData, + SettingsItem +} from '../../../shared/types'; +import { SettingsHeader } from '../components/SettingsHeader'; +import { SettingsSectionComponent } from '../components/SettingsSectionComponent'; +import { ProfileCard } from '../components/ProfileCard'; +import { CustomModal } from '../../../shared/components'; +import { useAppDispatch, useAppSelector } from '../../../store/hooks'; +import { logoutUser } from '../../Auth/redux/authActions'; +import { + selectUser, + selectUserDisplayName, + selectUserEmail, + selectUserFirstName, + selectUserLastName, + selectUserProfilePhoto, + selectNotificationPreferences, + selectDashboardSettings +} from '../../Auth/redux/authSelectors'; + +/** + * SettingsScreenProps Interface + * + * Purpose: Defines the props required by the SettingsScreen component + * + * Props: + * - navigation: React Navigation object for screen navigation + */ +interface SettingsScreenProps { + navigation: any; +} + +/** + * SettingsScreen Component + * + * Purpose: Main settings screen for user profile management and app preferences + * + * Features: + * 1. User profile overview and quick access + * 2. Comprehensive settings sections + * 3. Navigation to detailed settings screens + * 4. Pull-to-refresh functionality + * 5. Mock data generation for demonstration + * + * Settings Sections: + * - Profile: Personal and professional information + * - Notifications: Alert and notification preferences + * - Clinical: Clinical workflow preferences + * - Privacy: Security and privacy settings + * - Accessibility: Accessibility features + * - About: App information and help + * - Logout: Sign out functionality + */ +export const SettingsScreen: React.FC = ({ + navigation, +}) => { + // ============================================================================ + // STATE MANAGEMENT + // ============================================================================ + + // Settings sections state + const [settingsSections, setSettingsSections] = useState([]); + + // UI state + const [refreshing, setRefreshing] = useState(false); + + // Modal state + const [modalVisible, setModalVisible] = useState(false); + const [modalConfig, setModalConfig] = useState({ + title: '', + message: '', + type: 'info' as 'success' | 'error' | 'warning' | 'info' | 'confirm', + onConfirm: () => {}, + showCancel: false, + icon: '', + }); + + // Redux dispatch and selectors + const dispatch = useAppDispatch(); + + // User data from Redux + const user = useAppSelector(selectUser); + const userDisplayName = useAppSelector(selectUserDisplayName); + const userEmail = useAppSelector(selectUserEmail); + const userFirstName = useAppSelector(selectUserFirstName); + const userLastName = useAppSelector(selectUserLastName); + const userProfilePhoto = useAppSelector(selectUserProfilePhoto); + const notificationPreferences = useAppSelector(selectNotificationPreferences); + const dashboardSettings = useAppSelector(selectDashboardSettings); + console.log('user details i got', user); + + // ============================================================================ + // SETTINGS SECTIONS GENERATION + // ============================================================================ + + /** + * generateSettingsSections Function + * + * Purpose: Generate settings sections with items for the settings screen + * + * Returns: Array of SettingsSectionData with navigation and action items + */ + const generateSettingsSections = (): SettingsSectionData[] => [ + { + id: 'PROFILE', + title: 'Profile & Account', + items: [ + { + id: 'edit-profile', + title: 'Edit Profile', + subtitle: 'Update personal and professional information', + icon: 'user', + type: 'NAVIGATION', + onPress: () => handleNavigation('PROFILE'), + }, + { + id: 'change-password', + title: 'Change Password', + subtitle: 'Update your account password', + icon: 'lock', + type: 'NAVIGATION', + onPress: () => handleNavigation('CHANGE_PASSWORD'), + }, + { + id: 'security-settings', + title: 'Security Settings', + subtitle: 'Two-factor authentication and biometrics', + icon: 'shield', + type: 'NAVIGATION', + onPress: () => handleNavigation('SECURITY'), + }, + ], + }, + { + id: 'NOTIFICATIONS', + title: 'Notifications', + items: [ + { + id: 'notification-preferences', + title: 'Notification Preferences', + subtitle: 'Manage alert and notification settings', + icon: 'bell', + type: 'NAVIGATION', + onPress: () => handleNavigation('NOTIFICATIONS'), + }, + { + id: 'quiet-hours', + title: 'Quiet Hours', + subtitle: 'Set do not disturb periods', + icon: 'moon', + type: 'NAVIGATION', + onPress: () => handleNavigation('QUIET_HOURS'), + }, + { + id: 'push-notifications', + title: 'Push Notifications', + subtitle: 'Enable or disable push notifications', + icon: 'phone', + type: 'TOGGLE', + value: notificationPreferences?.system_notifications.push, + onPress: () => handleToggleSetting('pushNotifications'), + }, + ], + }, + + { + id: 'PRIVACY', + title: 'Privacy & Security', + items: [ + { + id: 'privacy-settings', + title: 'Privacy Settings', + subtitle: 'Manage data sharing and privacy controls', + icon: 'settings', + type: 'NAVIGATION', + onPress: () => handleNavigation('PRIVACY'), + }, + { + id: 'biometric-auth', + title: 'Biometric Authentication', + subtitle: 'Use fingerprint or face ID', + icon: 'lock', + type: 'TOGGLE', + value: false, // TODO: Add biometric auth preference to user data + onPress: () => handleToggleSetting('biometricAuth'), + }, + { + id: 'session-timeout', + title: 'Session Timeout', + subtitle: 'Auto-logout after inactivity', + icon: 'clock', + type: 'NAVIGATION', + onPress: () => handleNavigation('SESSION_TIMEOUT'), + }, + ], + }, + + { + id: 'ABOUT', + title: 'About & Support', + items: [ + { + id: 'app-info', + title: 'App Information', + subtitle: 'Version, build number, and details', + icon: 'smartphone', + type: 'NAVIGATION', + onPress: () => handleNavigation('APP_INFO'), + }, + { + id: 'help-support', + title: 'Help & Support', + subtitle: 'Contact support and view documentation', + icon: 'help', + type: 'NAVIGATION', + onPress: () => handleNavigation('HELP'), + }, + { + id: 'feedback', + title: 'Send Feedback', + subtitle: 'Report bugs or suggest improvements', + icon: 'rss', + type: 'NAVIGATION', + onPress: () => handleNavigation('FEEDBACK'), + }, + ], + }, + { + id: 'LOGOUT', + title: 'Account', + items: [ + { + id: 'logout', + title: 'Sign Out', + subtitle: 'Sign out of your account', + icon: 'log-out', + type: 'ACTION', + onPress: handleLogout, + }, + ], + }, + ]; + + // ============================================================================ + // EFFECTS + // ============================================================================ + + /** + * useEffect for initial settings sections generation + * + * Purpose: Generate settings sections when component mounts or user data changes + */ + useEffect(() => { + setSettingsSections(generateSettingsSections()); + }, [user, notificationPreferences, dashboardSettings]); + + // ============================================================================ + // EVENT HANDLERS + // ============================================================================ + + /** + * handleRefresh Function + * + * Purpose: Handle pull-to-refresh functionality to update settings data + * + * Flow: + * 1. Set refreshing state to true (show loading indicator) + * 2. Simulate API call with delay + * 3. Regenerate settings sections with current user data + * 4. Set refreshing state to false (hide loading indicator) + */ + const handleRefresh = async () => { + setRefreshing(true); + + // Simulate API call with 1-second delay + await new Promise(resolve => setTimeout(() => resolve(), 1000)); + + // Regenerate settings sections with current user data + setSettingsSections(generateSettingsSections()); + + setRefreshing(false); + }; + + /** + * handleNavigation Function + * + * Purpose: Handle navigation to different settings screens + * + * @param screen - Screen to navigate to + */ + const handleNavigation = (screen: string) => { + // TODO: Implement navigation to specific settings screens + console.log('Navigate to:', screen); + setModalConfig({ + title: 'Navigation', + message: `Navigate to ${screen} screen`, + type: 'info', + onConfirm: () => {}, + showCancel: false, + icon: 'info', + }); + setModalVisible(true); + }; + + /** + * handleToggleSetting Function + * + * Purpose: Handle toggle settings changes + * + * @param setting - Setting to toggle + */ + const handleToggleSetting = (setting: string) => { + // TODO: Implement setting toggle logic + console.log('Toggle setting:', setting); + setModalConfig({ + title: 'Setting Toggle', + message: `Toggle ${setting} setting`, + type: 'info', + icon: 'info', + onConfirm: () => {}, + showCancel: false, + }); + setModalVisible(true); + }; + + /** + * handleLogout Function + * + * Purpose: Handle user logout with Redux integration + * + * Flow: + * 1. Show confirmation dialog + * 2. Dispatch logout action to Redux + * 3. Clear authentication state + * 4. Show success message + * 5. Automatically navigate to login screen via Redux state change + */ + const handleLogout = () => { + setModalConfig({ + title: 'Sign Out', + message: 'Are you sure you want to sign out?', + type: 'confirm', + icon: 'log-out', + onConfirm: async () => { + try { + // Dispatch logout thunk to Redux + await dispatch(logoutUser()); + + // Log the logout action + console.log('User logged out successfully'); + } catch (error) { + console.error('Logout error:', error); + setModalConfig({ + title: 'Error', + message: 'Failed to sign out. Please try again.', + type: 'error', + icon: 'info', + onConfirm: () => {}, + showCancel: false, + }); + setModalVisible(true); + } + }, + showCancel: true, + }); + setModalVisible(true); + }; + + /** + * handleProfilePress Function + * + * Purpose: Handle profile card press navigation + */ + const handleProfilePress = () => { + handleNavigation('PROFILE'); + }; + + // ============================================================================ + // MAIN RENDER + // ============================================================================ + + return ( + + {/* Settings header with title */} + + + {/* Scrollable settings content */} + + } + showsVerticalScrollIndicator={false} + > + {/* Profile card section */} + {user && ( + + + + + {user.profile_photo_url ? ( + + ) : ( + + + {user.first_name.charAt(0)}{user.last_name.charAt(0)} + + + )} + + + + + {user.display_name || `${user.first_name} ${user.last_name}`} + + {user.email} + {user.dashboard_role} + + + + Edit + + + + + )} + + {/* Settings sections */} + {settingsSections.map((section) => ( + + ))} + + {/* Bottom spacing for tab bar */} + + + + {/* Custom Modal */} + setModalVisible(false)} + /> + + ); +}; + +// ============================================================================ +// STYLES SECTION +// ============================================================================ + +const styles = StyleSheet.create({ + // Main container for the settings screen + container: { + flex: 1, + backgroundColor: theme.colors.background, + }, + + // Loading container for initial data loading + loadingContainer: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: theme.colors.background, + }, + + // Loading text styling + loadingText: { + fontSize: theme.typography.fontSize.bodyLarge, + color: theme.colors.textSecondary, + fontFamily: theme.typography.fontFamily.medium, + }, + + // Scroll view styling + scrollView: { + flex: 1, + }, + + // Scroll content styling + scrollContent: { + paddingHorizontal: theme.spacing.md, + }, + + // Bottom spacing for tab bar + bottomSpacing: { + height: theme.spacing.xl, + }, + + // Profile card styles + profileCard: { + backgroundColor: theme.colors.background, + borderRadius: theme.borderRadius.medium, + padding: theme.spacing.md, + marginBottom: theme.spacing.md, + ...theme.shadows.primary, + }, + + profileHeader: { + flexDirection: 'row', + alignItems: 'center', + }, + + profileImageContainer: { + marginRight: theme.spacing.md, + }, + + profileImage: { + width: 60, + height: 60, + borderRadius: 30, + }, + + fallbackAvatar: { + width: 60, + height: 60, + borderRadius: 30, + backgroundColor: theme.colors.primary, + justifyContent: 'center', + alignItems: 'center', + }, + + fallbackText: { + color: theme.colors.background, + fontSize: theme.typography.fontSize.bodyLarge, + fontFamily: theme.typography.fontFamily.bold, + }, + + profileInfo: { + flex: 1, + }, + + profileName: { + fontSize: theme.typography.fontSize.bodyLarge, + fontFamily: theme.typography.fontFamily.bold, + color: theme.colors.textPrimary, + marginBottom: theme.spacing.xs, + }, + + profileEmail: { + fontSize: theme.typography.fontSize.bodyMedium, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.textSecondary, + marginBottom: theme.spacing.xs, + }, + + profileRole: { + fontSize: theme.typography.fontSize.bodySmall, + fontFamily: theme.typography.fontFamily.medium, + color: theme.colors.primary, + }, + + editIcon: { + paddingHorizontal: theme.spacing.sm, + paddingVertical: theme.spacing.xs, + backgroundColor: theme.colors.backgroundAlt, + borderRadius: theme.borderRadius.small, + }, + + editText: { + fontSize: theme.typography.fontSize.bodySmall, + fontFamily: theme.typography.fontFamily.medium, + color: theme.colors.primary, + }, +}); + +/* + * End of File: SettingsScreen.tsx + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/navigation/MainTabNavigator.tsx b/app/navigation/MainTabNavigator.tsx new file mode 100644 index 0000000..69b5ce4 --- /dev/null +++ b/app/navigation/MainTabNavigator.tsx @@ -0,0 +1,148 @@ +/* + * File: MainTabNavigator.tsx + * Description: Bottom tab navigator for the main app interface + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import React from 'react'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { theme } from '../theme/theme'; +import { DashboardStackNavigator } from '../modules/Dashboard/navigation'; +import { SettingsStackNavigator } from '../modules/Settings/navigation'; +import { MainTabParamList } from './navigationTypes'; +import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; + +// Create the bottom tab navigator +const Tab = createBottomTabNavigator(); + +/** + * MainTabNavigator Component + * + * Purpose: Creates the bottom tab navigation for the main app interface + * + * Tab Structure: + * - Dashboard: Main ER dashboard with patient overview and statistics + * - Patients: Detailed patient list and management interface + * - Alerts: Critical notifications and alert management + * - Reports: Medical reports and documentation access + * - Settings: User preferences and app configuration + * + * Features: + * - Consistent styling with app theme + * - Tab-specific icons and labels + * - Proper parameter passing to screens + * - Accessibility support + */ +export const MainTabNavigator: React.FC = () => { + return ( + + {/* Dashboard Tab - Main ER overview */} + ( + + ), + headerShown: false, + }} + /> + + {/* Patients Tab - Patient list and management */} + ( + + ), + headerShown: false, + }} + /> + + {/* Alerts Tab - Critical notifications */} + ( + + ), + headerShown: false, + }} + /> + + {/* Reports Tab - Medical documentation */} + ( + + ), + headerShown: false, + }} + /> + + {/* Settings Tab - User preferences */} + ( + + ), + headerShown: false, + }} + /> + + ); +}; + +/* + * End of File: MainTabNavigator.tsx + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/navigation/RootStackNavigator.tsx b/app/navigation/RootStackNavigator.tsx new file mode 100644 index 0000000..f63d0cd --- /dev/null +++ b/app/navigation/RootStackNavigator.tsx @@ -0,0 +1,80 @@ +/* + * File: RootStackNavigator.tsx + * Description: Root stack navigator managing authentication and main app flow + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import React from 'react'; +import { createStackNavigator } from '@react-navigation/stack'; +import { AuthStackNavigator } from '../modules/Auth/navigation'; +import { MainTabNavigator } from './MainTabNavigator'; +import { RootStackParamList } from './navigationTypes'; +import { useAppSelector } from '../store/hooks'; +import { selectIsAuthenticated, selectIsOnboarded } from '../modules/Auth/redux/authSelectors'; +import ResetPasswordScreen from '../modules/Auth/screens/ResetPasswordScreen'; + +// Create the stack navigator +const Stack = createStackNavigator(); + +/** + * RootStackNavigatorProps Interface + * + * Purpose: Defines the props required by the RootStackNavigator component + * + * Props: + * - isAuthenticated: Boolean indicating if user is logged in + */ +interface RootStackNavigatorProps { + isAuthenticated: boolean; +} + +/** + * RootStackNavigator Component + * + * Purpose: Manages the main navigation flow between authentication, onboarding, and main app + * + * Navigation Flow: + * 1. App starts โ†’ Check authentication status + * 2. If not authenticated โ†’ Show AuthStackNavigator (LoginScreen) + * 3. If authenticated but not onboarded โ†’ Show ResetPasswordScreen + * 4. If authenticated and onboarded โ†’ Show MainTabNavigator (dashboard) + * + * Features: + * - Conditional rendering based on authentication and onboarding status + * - Seamless transition between auth, onboarding, and main app + * - Proper prop passing to child components + * - Hidden headers for custom styling + */ +export const RootStackNavigator: React.FC = ({ + isAuthenticated, +}) => { + // Get onboarding status from Redux + const isOnboarded = useAppSelector(selectIsOnboarded); + + return ( + + {/* Conditional rendering based on authentication and onboarding status */} + {!isAuthenticated ? ( + // Show auth stack if user is not authenticated + + ) : !isOnboarded ? ( + // Show reset password screen if user is authenticated but not onboarded + + ) : ( + // Show main app tabs if user is authenticated and onboarded + + )} + + ); +}; + +/* + * End of File: RootStackNavigator.tsx + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/navigation/index.ts b/app/navigation/index.ts new file mode 100644 index 0000000..6b39d90 --- /dev/null +++ b/app/navigation/index.ts @@ -0,0 +1,59 @@ +/* + * File: index.ts + * Description: Main navigation module exports + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +// ============================================================================ +// NAVIGATION COMPONENTS +// ============================================================================ + +// Main navigation components +export { MainTabNavigator } from './MainTabNavigator'; +export { RootStackNavigator } from './RootStackNavigator'; + +// ============================================================================ +// NAVIGATION TYPES +// ============================================================================ + +// Type definitions for navigation +export type { + RootStackParamList, + MainTabParamList, + DashboardScreenParams, + PatientsScreenParams, + AlertsScreenParams, + ReportsScreenParams, + SettingsScreenParams, + NavigationProps, + TabNavigationProps, + NavigationRef, +} from './navigationTypes'; + +// ============================================================================ +// NAVIGATION UTILITIES +// ============================================================================ + +// Navigation utility functions +export { + navigationRef, + setNavigationRef, + navigateToScreen, + goBack, + resetNavigation, + navigateToDashboard, + navigateToPatientDetails, + navigateToAlerts, + navigateToReports, + navigateToSettings, + navigateToLogin, + navigateToMainApp, + handleNavigationError, +} from './navigationUtils'; + +/* + * End of File: index.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/navigation/navigationTypes.ts b/app/navigation/navigationTypes.ts new file mode 100644 index 0000000..2367d1a --- /dev/null +++ b/app/navigation/navigationTypes.ts @@ -0,0 +1,190 @@ +/* + * File: navigationTypes.ts + * Description: TypeScript type definitions for navigation parameters and routes + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import { NavigatorScreenParams } from '@react-navigation/native'; +import { ERDashboard, Patient, Alert as AlertType } from '../shared/types'; + +// ============================================================================ +// ROOT NAVIGATION TYPES +// ============================================================================ + +/** + * RootStackParamList + * + * Purpose: Defines the main navigation stack parameters for the entire app + * + * Routes: + * - Auth: Authentication stack navigator + * - Main: Main app with bottom tab navigation + */ +export type RootStackParamList = { + Auth: undefined; // Auth stack navigator handles its own screens + ResetPassword: undefined; // Reset password screen for onboarding + Main: NavigatorScreenParams; // Main app with tab navigation +}; + +// ============================================================================ +// MAIN TAB NAVIGATION TYPES +// ============================================================================ + +/** + * MainTabParamList + * + * Purpose: Defines the bottom tab navigation parameters + * + * Tabs: + * - Dashboard: ER dashboard with patient overview + * - Patients: Patient list and management + * - Alerts: Critical notifications and alerts + * - Reports: Medical reports and documentation + * - Settings: User preferences and app configuration + */ +export type MainTabParamList = { + Dashboard: DashboardScreenParams; // Dashboard with initial data + Patients: PatientsScreenParams; // Patient list screen + Alerts: AlertsScreenParams; // Alerts screen + Reports: ReportsScreenParams; // Reports screen + Settings: SettingsScreenParams; // Settings screen +}; + +// ============================================================================ +// SCREEN PARAMETER TYPES +// ============================================================================ + +/** + * DashboardScreenParams + * + * Purpose: Parameters passed to the dashboard screen + * + * Parameters: + * - dashboard: ER dashboard data including statistics and shift info + * - patients: Array of patient data for display + * - alerts: Array of critical alerts and notifications + */ +export interface DashboardScreenParams { + dashboard: ERDashboard; + patients: Patient[]; + alerts: AlertType[]; +} + +/** + * PatientsScreenParams + * + * Purpose: Parameters for the patients screen + * + * Parameters: + * - filter: Optional filter to apply to patient list + * - searchQuery: Optional search term for patient search + */ +export interface PatientsScreenParams { + filter?: 'all' | 'critical' | 'active' | 'pending'; + searchQuery?: string; +} + +/** + * AlertsScreenParams + * + * Purpose: Parameters for the alerts screen + * + * Parameters: + * - priority: Optional priority filter for alerts + * - unreadOnly: Optional flag to show only unread alerts + */ +export interface AlertsScreenParams { + priority?: 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW'; + unreadOnly?: boolean; +} + +/** + * ReportsScreenParams + * + * Purpose: Parameters for the reports screen + * + * Parameters: + * - patientId: Optional patient ID to filter reports + * - dateRange: Optional date range for report filtering + */ +export interface ReportsScreenParams { + patientId?: string; + dateRange?: { + start: Date; + end: Date; + }; +} + +/** + * SettingsScreenParams + * + * Purpose: Parameters for the settings screen + * + * Parameters: + * - section: Optional section to navigate to within settings + */ +export interface SettingsScreenParams { + section?: 'profile' | 'notifications' | 'security' | 'about'; +} + +// ============================================================================ +// NAVIGATION PROPS TYPES +// ============================================================================ + +/** + * NavigationProps + * + * Purpose: Common navigation props used across components + * + * Properties: + * - navigation: Navigation object for screen navigation + * - route: Current route information and parameters + */ +export interface NavigationProps { + navigation: any; // Will be properly typed when navigation is set up + route: { + params: RootStackParamList[T]; + }; +} + +/** + * TabNavigationProps + * + * Purpose: Navigation props for tab screens + * + * Properties: + * - navigation: Tab navigation object + * - route: Current tab route information + */ +export interface TabNavigationProps { + navigation: any; // Will be properly typed when navigation is set up + route: { + params: MainTabParamList[T]; + }; +} + +// ============================================================================ +// NAVIGATION UTILITY TYPES +// ============================================================================ + +/** + * NavigationRef + * + * Purpose: Reference to the navigation object for programmatic navigation + * + * Usage: + * - Used for navigation from outside React components + * - Enables navigation from services, utilities, or event handlers + */ +export type NavigationRef = { + navigate: (name: keyof RootStackParamList, params?: any) => void; + goBack: () => void; + reset: (state: any) => void; +}; + +/* + * End of File: navigationTypes.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/navigation/navigationUtils.ts b/app/navigation/navigationUtils.ts new file mode 100644 index 0000000..b26ad02 --- /dev/null +++ b/app/navigation/navigationUtils.ts @@ -0,0 +1,263 @@ +/* + * File: navigationUtils.ts + * Description: Navigation utility functions for common navigation operations + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import { NavigationRef } from './navigationTypes'; + +// ============================================================================ +// NAVIGATION REFERENCE +// ============================================================================ + +/** + * navigationRef + * + * Purpose: Global navigation reference for programmatic navigation + * + * Usage: + * - Set this reference in the main App component + * - Use for navigation from outside React components + * - Enables navigation from services, utilities, or event handlers + * + * Example: + * navigationRef.current?.navigate('Main'); + */ +export let navigationRef: React.RefObject | null = null; + +/** + * setNavigationRef + * + * Purpose: Set the global navigation reference + * + * @param ref - Navigation reference object + * + * Usage: + * Called from the main App component to set the navigation reference + */ +export const setNavigationRef = (ref: React.RefObject) => { + navigationRef = ref as React.RefObject; +}; + +// ============================================================================ +// NAVIGATION HELPER FUNCTIONS +// ============================================================================ + +/** + * navigateToScreen + * + * Purpose: Navigate to a specific screen with optional parameters + * + * @param screenName - Name of the screen to navigate to + * @param params - Optional parameters to pass to the screen + * + * Example: + * navigateToScreen('Main', { tab: 'Dashboard' }); + */ +export const navigateToScreen = (screenName: string, params?: any) => { + if (navigationRef?.current) { + navigationRef.current.navigate(screenName as any, params); + } +}; + +/** + * goBack + * + * Purpose: Navigate back to the previous screen + * + * Example: + * goBack(); + */ +export const goBack = () => { + if (navigationRef?.current) { + navigationRef.current.goBack(); + } +}; + +/** + * resetNavigation + * + * Purpose: Reset the navigation state to a specific screen + * + * @param screenName - Name of the screen to reset to + * @param params - Optional parameters for the screen + * + * Example: + * resetNavigation('Login'); // Reset to login screen + */ +export const resetNavigation = (screenName: string, params?: any) => { + if (navigationRef?.current) { + navigationRef.current.reset({ + index: 0, + routes: [{ name: screenName, params }], + }); + } +}; + +// ============================================================================ +// SCREEN-SPECIFIC NAVIGATION FUNCTIONS +// ============================================================================ + +/** + * navigateToDashboard + * + * Purpose: Navigate to the dashboard with specific data + * + * @param dashboard - Dashboard data to pass + * @param patients - Patient data to pass + * @param alerts - Alert data to pass + * + * Example: + * navigateToDashboard(dashboardData, patientList, alertList); + */ +export const navigateToDashboard = ( + dashboard: any, + patients: any[], + alerts: any[] +) => { + navigateToScreen('Main', { + screen: 'Dashboard', + params: { + dashboard, + patients, + alerts, + }, + }); +}; + +/** + * navigateToPatientDetails + * + * Purpose: Navigate to patient details screen + * + * @param patientId - ID of the patient to view + * + * Example: + * navigateToPatientDetails('patient123'); + */ +export const navigateToPatientDetails = (patientId: string) => { + navigateToScreen('Main', { + screen: 'Patients', + params: { + patientId, + }, + }); +}; + +/** + * navigateToAlerts + * + * Purpose: Navigate to alerts screen with optional filters + * + * @param priority - Optional priority filter + * @param unreadOnly - Optional flag for unread alerts only + * + * Example: + * navigateToAlerts('CRITICAL', true); + */ +export const navigateToAlerts = (priority?: string, unreadOnly?: boolean) => { + navigateToScreen('Main', { + screen: 'Alerts', + params: { + priority, + unreadOnly, + }, + }); +}; + +/** + * navigateToReports + * + * Purpose: Navigate to reports screen with optional filters + * + * @param patientId - Optional patient ID filter + * @param dateRange - Optional date range filter + * + * Example: + * navigateToReports('patient123', { start: new Date(), end: new Date() }); + */ +export const navigateToReports = (patientId?: string, dateRange?: any) => { + navigateToScreen('Main', { + screen: 'Reports', + params: { + patientId, + dateRange, + }, + }); +}; + +/** + * navigateToSettings + * + * Purpose: Navigate to settings screen with optional section + * + * @param section - Optional section to navigate to within settings + * + * Example: + * navigateToSettings('profile'); + */ +export const navigateToSettings = (section?: string) => { + navigateToScreen('Main', { + screen: 'Settings', + params: { + section, + }, + }); +}; + +// ============================================================================ +// AUTHENTICATION NAVIGATION FUNCTIONS +// ============================================================================ + +/** + * navigateToLogin + * + * Purpose: Navigate to login screen + * + * Example: + * navigateToLogin(); + */ +export const navigateToLogin = () => { + resetNavigation('Login'); +}; + +/** + * navigateToMainApp + * + * Purpose: Navigate to main app after successful authentication + * + * Example: + * navigateToMainApp(); + */ +export const navigateToMainApp = () => { + resetNavigation('Main'); +}; + +// ============================================================================ +// ERROR HANDLING +// ============================================================================ + +/** + * handleNavigationError + * + * Purpose: Handle navigation errors gracefully + * + * @param error - Navigation error object + * @param fallbackScreen - Fallback screen to navigate to + * + * Example: + * handleNavigationError(error, 'Login'); + */ +export const handleNavigationError = (error: any, fallbackScreen: string) => { + console.error('Navigation error:', error); + + // Navigate to fallback screen + resetNavigation(fallbackScreen); +}; + +/* + * End of File: navigationUtils.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/shared/components/CustomModal.tsx b/app/shared/components/CustomModal.tsx new file mode 100644 index 0000000..907af20 --- /dev/null +++ b/app/shared/components/CustomModal.tsx @@ -0,0 +1,348 @@ +/* + * File: CustomModal.tsx + * Description: Custom modal component with matching UI design + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import React from 'react'; +import { + View, + Text, + StyleSheet, + TouchableOpacity, + Modal, + TouchableWithoutFeedback, + Dimensions, +} from 'react-native'; +import { theme } from '../../theme/theme'; +import Icon from 'react-native-vector-icons/Feather'; + +// ============================================================================ +// INTERFACES +// ============================================================================ + +/** + * CustomModalProps Interface + * + * Purpose: Defines the props required by the CustomModal component + * + * Props: + * - visible: Whether the modal is visible + * - title: Modal title + * - message: Modal message/content + * - type: Modal type (success, error, warning, info, confirm) + * - onConfirm: Callback for confirm action + * - onCancel: Callback for cancel action + * - confirmText: Text for confirm button + * - cancelText: Text for cancel button + * - onClose: Callback for closing modal + * - showCancel: Whether to show cancel button + * - icon: Custom icon name + */ +interface CustomModalProps { + visible: boolean; + title: string; + message: string; + type?: 'success' | 'error' | 'warning' | 'info' | 'confirm'; + onConfirm?: () => void; + onCancel?: () => void; + confirmText?: string; + cancelText?: string; + onClose?: () => void; + showCancel?: boolean; + icon?: string; +} + +// ============================================================================ +// CUSTOM MODAL COMPONENT +// ============================================================================ + +/** + * CustomModal Component + * + * Purpose: Displays a custom modal with consistent UI design + * + * Features: + * - Multiple modal types (success, error, warning, info, confirm) + * - Customizable buttons and text + * - Consistent theme styling + * - Backdrop tap to close + * - Icon support + * - Responsive design + */ +export const CustomModal: React.FC = ({ + visible, + title, + message, + type = 'info', + onConfirm, + onCancel, + confirmText = 'OK', + cancelText = 'Cancel', + onClose, + showCancel = false, + icon, +}) => { + // ============================================================================ + // MODAL CONFIGURATION + // ============================================================================ + + /** + * Get modal configuration based on type + */ + const getModalConfig = () => { + switch (type) { + case 'success': + return { + icon: icon || 'check-circle', + iconColor: theme.colors.success, + backgroundColor: theme.colors.background, + borderColor: theme.colors.success, + }; + case 'error': + return { + icon: icon || 'alert-circle', + iconColor: theme.colors.error, + backgroundColor: theme.colors.background, + borderColor: theme.colors.error, + }; + case 'warning': + return { + icon: icon || 'alert-triangle', + iconColor: theme.colors.warning, + backgroundColor: theme.colors.background, + borderColor: theme.colors.warning, + }; + case 'confirm': + return { + icon: icon || 'help-circle', + iconColor: theme.colors.primary, + backgroundColor: theme.colors.background, + borderColor: theme.colors.primary, + }; + default: + return { + icon: icon || 'info', + iconColor: theme.colors.info, + backgroundColor: theme.colors.background, + borderColor: theme.colors.info, + }; + } + }; + + const config = getModalConfig(); + + // ============================================================================ + // EVENT HANDLERS + // ============================================================================ + + /** + * Handle confirm action + */ + const handleConfirm = () => { + if (onConfirm) { + onConfirm(); + } + if (onClose) { + onClose(); + } + }; + + /** + * Handle cancel action + */ + const handleCancel = () => { + if (onCancel) { + onCancel(); + } + if (onClose) { + onClose(); + } + }; + + /** + * Handle backdrop press + */ + const handleBackdropPress = () => { + if (onClose) { + onClose(); + } + }; + + // ============================================================================ + // RENDER + // ============================================================================ + + return ( + + + + + + {/* Icon */} + + + + + {/* Title */} + {title} + + {/* Message */} + {message} + + {/* Buttons */} + + {showCancel && ( + + {cancelText} + + )} + + + {confirmText} + + + + + + + + ); +}; + +// ============================================================================ +// STYLES +// ============================================================================ + +const { width: screenWidth } = Dimensions.get('window'); + +const styles = StyleSheet.create({ + // Backdrop + backdrop: { + flex: 1, + backgroundColor: 'rgba(0, 0, 0, 0.5)', + justifyContent: 'center', + alignItems: 'center', + paddingHorizontal: theme.spacing.lg, + }, + + // Modal container + modalContainer: { + width: screenWidth - theme.spacing.lg * 2, + backgroundColor: theme.colors.background, + borderRadius: theme.borderRadius.large, + padding: theme.spacing.xl, + alignItems: 'center', + borderWidth: 2, + ...theme.shadows.large, + }, + + // Icon container + iconContainer: { + width: 80, + height: 80, + borderRadius: 40, + justifyContent: 'center', + alignItems: 'center', + marginBottom: theme.spacing.lg, + }, + + // Title + title: { + fontSize: theme.typography.fontSize.displaySmall, + fontFamily: theme.typography.fontFamily.bold, + color: theme.colors.textPrimary, + textAlign: 'center', + marginBottom: theme.spacing.md, + }, + + // Message + message: { + fontSize: theme.typography.fontSize.bodyLarge, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.textSecondary, + textAlign: 'center', + lineHeight: theme.typography.fontSize.bodyLarge * 1.4, + marginBottom: theme.spacing.xl, + }, + + // Button container + buttonContainer: { + flexDirection: 'row', + width: '100%', + gap: theme.spacing.md, + }, + + // Button base + button: { + flex: 1, + paddingVertical: theme.spacing.md, + paddingHorizontal: theme.spacing.lg, + borderRadius: theme.borderRadius.medium, + alignItems: 'center', + justifyContent: 'center', + ...theme.shadows.primary, + }, + + // Cancel button + cancelButton: { + backgroundColor: theme.colors.backgroundAlt, + borderWidth: 1, + borderColor: theme.colors.border, + }, + + // Cancel button text + cancelButtonText: { + fontSize: theme.typography.fontSize.bodyLarge, + fontFamily: theme.typography.fontFamily.medium, + color: theme.colors.textSecondary, + }, + + // Confirm button + confirmButton: { + backgroundColor: theme.colors.primary, + }, + + // Confirm button with cancel + confirmButtonWithCancel: { + flex: 1, + }, + + // Confirm button text + confirmButtonText: { + fontSize: theme.typography.fontSize.bodyLarge, + fontFamily: theme.typography.fontFamily.bold, + color: theme.colors.background, + }, +}); + +/* + * End of File: CustomModal.tsx + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/shared/components/index.ts b/app/shared/components/index.ts new file mode 100644 index 0000000..5166a25 --- /dev/null +++ b/app/shared/components/index.ts @@ -0,0 +1,14 @@ +/* + * File: index.ts + * Description: Shared components exports + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +export { CustomModal } from './CustomModal'; + +/* + * End of File: index.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/shared/types/alerts.ts b/app/shared/types/alerts.ts new file mode 100644 index 0000000..b58ccdc --- /dev/null +++ b/app/shared/types/alerts.ts @@ -0,0 +1,115 @@ +/* + * File: alerts.ts + * Description: Alert and notification type definitions + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +export interface Alert { + id: string; + type: AlertType; + priority: AlertPriority; + title: string; + message: string; + patientId?: string; + patientName?: string; + bedNumber?: string; + timestamp: Date; + isRead: boolean; + isAcknowledged: boolean; + actionRequired: boolean; + expiresAt?: Date; + metadata?: AlertMetadata; +} + +export type AlertType = + | 'CRITICAL_FINDING' + | 'VITAL_SIGNS_ALERT' + | 'SCAN_COMPLETED' + | 'MEDICATION_ALERT' + | 'BED_ASSIGNMENT' + | 'SHIFT_CHANGE' + | 'SYSTEM_ALERT' + | 'EMERGENCY_CODE'; + +export interface AlertMetadata { + scanId?: string; + vitalSigns?: { + type: string; + value: number; + normalRange: string; + }; + medication?: { + name: string; + dosage: string; + reason: string; + }; + location?: { + floor: string; + room: string; + bed: string; + }; + aiFindings?: { + confidence: number; + summary: string; + recommendations: string[]; + }; +} + +export interface CriticalFinding { + id: string; + patientId: string; + patientName: string; + bedNumber: string; + findingType: CriticalFindingType; + severity: 'LIFE_THREATENING' | 'URGENT' | 'SERIOUS'; + description: string; + aiSummary: string; + recommendations: string[]; + timestamp: Date; + acknowledgedBy?: string; + acknowledgedAt?: Date; + actionTaken?: string; + followUpRequired: boolean; +} + +export type CriticalFindingType = + | 'BRAIN_BLEED' + | 'PULMONARY_EMBOLISM' + | 'AORTIC_DISSECTION' + | 'APPENDICITIS' + | 'FRACTURE' + | 'INTERNAL_BLEEDING' + | 'CARDIAC_ARREST' + | 'STROKE' + | 'SEPSIS' + | 'OTHER'; + +export interface AlertSettings { + pushNotifications: boolean; + soundAlerts: boolean; + vibrationAlerts: boolean; + criticalAlertsOnly: boolean; + quietHours: { + enabled: boolean; + startTime: string; + endTime: string; + }; + alertTypes: { + [key in AlertType]: boolean; + }; +} + +export interface AlertResponse { + alertId: string; + response: 'ACKNOWLEDGED' | 'DISMISSED' | 'ESCALATED'; + notes?: string; + timestamp: Date; + userId: string; +} + +/* + * End of File: alerts.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/shared/types/auth.ts b/app/shared/types/auth.ts new file mode 100644 index 0000000..df6396a --- /dev/null +++ b/app/shared/types/auth.ts @@ -0,0 +1,93 @@ +/* + * File: auth.ts + * Description: Authentication and user-related type definitions + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +export interface NotificationPreferences { + critical_alerts: { + email: boolean; + in_app: boolean; + push: boolean; + sms: boolean; + }; + system_notifications: { + push: boolean; + email: boolean; + in_app: boolean; + }; +} + +export interface DashboardSettings { + theme: string; + language: string; + timezone: string; +} + +export interface User { + user_id: string; + email: string; + first_name: string; + last_name: string; + display_name: string; + hospital_id: string; + dashboard_role: string; + profile_photo_url: string | null; + theme_color: string | null; + accent_color: string | null; + notification_preferences: NotificationPreferences; + dashboard_settings: DashboardSettings; + onboarded: boolean; + onboarding_completed: boolean; + onboarding_step: number; + onboarding_message: string; + access_token: string; +} + +export type UserRole = + | 'ER_PHYSICIAN' + | 'RESIDENT' + | 'MEDICAL_STUDENT' + | 'EMERGENCY_ACCESS' + | 'TEMPORARY_ACCESS'; + +export interface AuthState { + user: User | null; + isAuthenticated: boolean; + isLoading: boolean; + error: string | null; + sessionExpiry: Date | null; + deviceId: string | null; +} + +export interface LoginCredentials { + email: string; + password: string; + rememberDevice?: boolean; +} + +export interface SSOLoginData { + token: string; + hospitalId: string; + redirectUrl?: string; +} + +export interface EmergencyAccess { + accessCode: string; + reason: string; + duration: number; // minutes +} + +export interface SessionData { + token: string; + refreshToken: string; + expiresAt: Date; + deviceId: string; +} + +/* + * End of File: auth.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/shared/types/common.ts b/app/shared/types/common.ts new file mode 100644 index 0000000..1964b87 --- /dev/null +++ b/app/shared/types/common.ts @@ -0,0 +1,92 @@ +/* + * File: common.ts + * Description: Common type definitions used across the application + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +export interface ApiResponse { + success: boolean; + data?: T; + error?: string; + message?: string; + timestamp: Date; +} + +export interface PaginatedResponse { + data: T[]; + pagination: { + page: number; + limit: number; + total: number; + totalPages: number; + }; +} + +export interface LoadingState { + isLoading: boolean; + error: string | null; +} + +export interface NavigationParams { + [key: string]: any; +} + +export interface ScreenProps { + navigation: any; + route: { + params: NavigationParams; + }; +} + +export interface FormField { + name: string; + label: string; + type: 'text' | 'email' | 'password' | 'number' | 'select' | 'date' | 'textarea'; + required: boolean; + validation?: (value: any) => string | null; + options?: { label: string; value: any }[]; + placeholder?: string; + defaultValue?: any; +} + +export interface ValidationError { + field: string; + message: string; +} + +export interface AppSettings { + theme: 'light' | 'dark' | 'auto'; + language: string; + notifications: boolean; + biometricAuth: boolean; + autoLogout: boolean; + offlineMode: boolean; +} + +export interface DeviceInfo { + id: string; + name: string; + platform: 'ios' | 'android'; + version: string; + model: string; + isTablet: boolean; +} + +export interface NetworkStatus { + isConnected: boolean; + type: 'wifi' | 'cellular' | 'none'; + strength?: number; +} + +export interface ErrorBoundaryState { + hasError: boolean; + error?: Error; + errorInfo?: any; +} + +/* + * End of File: common.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/shared/types/dashboard.ts b/app/shared/types/dashboard.ts new file mode 100644 index 0000000..66fab45 --- /dev/null +++ b/app/shared/types/dashboard.ts @@ -0,0 +1,83 @@ +/* + * File: dashboard.ts + * Description: Dashboard and ER-related type definitions + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import { Patient, PatientStatus, AlertPriority } from './patient'; + +export interface ERDashboard { + totalPatients: number; + criticalPatients: number; + pendingScans: number; + recentReports: number; + bedOccupancy: number; + departmentStats: DepartmentStats; + shiftInfo: ShiftInfo; + lastUpdated: Date; +} + +export interface DepartmentStats { + emergency: number; + trauma: number; + cardiac: number; + neurology: number; + pediatrics: number; + icu: number; +} + +export interface ShiftInfo { + currentShift: 'DAY' | 'NIGHT' | 'EVENING'; + startTime: Date; + endTime: Date; + attendingPhysician: string; + residents: string[]; + nurses: string[]; +} + +export interface PatientList { + patients: Patient[]; + filters: PatientFilters; + sortBy: PatientSortBy; + isLoading: boolean; + error: string | null; +} + +export interface PatientFilters { + status: PatientStatus[]; + priority: AlertPriority[]; + department: string[]; + bedNumber?: string; + attendingPhysician?: string; +} + +export type PatientSortBy = + | 'PRIORITY' + | 'NAME' + | 'BED_NUMBER' + | 'ADMISSION_DATE' + | 'LAST_UPDATED'; + +export interface QuickAction { + id: string; + title: string; + icon: string; + action: () => void; + isEnabled: boolean; + requiresConfirmation?: boolean; +} + +export interface DashboardMetrics { + responseTime: number; // average response time in minutes + patientSatisfaction: number; // percentage + criticalAlertsResolved: number; + scansReviewed: number; + patientsDischarged: number; +} + +/* + * End of File: dashboard.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/shared/types/index.ts b/app/shared/types/index.ts new file mode 100644 index 0000000..4b879ce --- /dev/null +++ b/app/shared/types/index.ts @@ -0,0 +1,19 @@ +/* + * File: index.ts + * Description: Shared type definitions exports for the Physician App + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +export * from './auth'; +export * from './patient'; +export * from './dashboard'; +export * from './alerts'; +export * from './settings'; +export * from './common'; + +/* + * End of File: index.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/shared/types/patient.ts b/app/shared/types/patient.ts new file mode 100644 index 0000000..8786a10 --- /dev/null +++ b/app/shared/types/patient.ts @@ -0,0 +1,118 @@ +/* + * File: patient.ts + * Description: Patient-related type definitions for medical data + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +export interface Patient { + id: string; + mrn: string; // Medical Record Number + firstName: string; + lastName: string; + dateOfBirth: Date; + gender: 'MALE' | 'FEMALE' | 'OTHER'; + age: number; + bedNumber: string; + roomNumber: string; + admissionDate: Date; + status: PatientStatus; + priority: AlertPriority; + department: string; + attendingPhysician: string; + allergies: Allergy[]; + medications: Medication[]; + vitalSigns: VitalSigns; + medicalHistory: MedicalHistory[]; + currentDiagnosis: string; + lastUpdated: Date; +} + +export type PatientStatus = + | 'ACTIVE' + | 'PENDING' + | 'DISCHARGED' + | 'TRANSFERRED' + | 'CRITICAL'; + +export type AlertPriority = + | 'CRITICAL' + | 'HIGH' + | 'MEDIUM' + | 'LOW'; + +export interface VitalSigns { + bloodPressure: { + systolic: number; + diastolic: number; + timestamp: Date; + }; + heartRate: { + value: number; + timestamp: Date; + }; + temperature: { + value: number; + timestamp: Date; + }; + respiratoryRate: { + value: number; + timestamp: Date; + }; + oxygenSaturation: { + value: number; + timestamp: Date; + }; +} + +export interface Allergy { + id: string; + name: string; + severity: 'MILD' | 'MODERATE' | 'SEVERE'; + reaction: string; + notes?: string; +} + +export interface Medication { + id: string; + name: string; + dosage: string; + frequency: string; + route: string; + startDate: Date; + endDate?: Date; + status: 'ACTIVE' | 'DISCONTINUED' | 'COMPLETED'; + prescribedBy: string; +} + +export interface MedicalHistory { + id: string; + condition: string; + diagnosisDate: Date; + status: 'ACTIVE' | 'RESOLVED' | 'CHRONIC'; + notes?: string; + treatingPhysician: string; +} + +export interface ScanResult { + id: string; + patientId: string; + scanType: 'CT' | 'MRI' | 'XRAY' | 'ULTRASOUND'; + bodyPart: string; + status: 'PENDING' | 'IN_PROGRESS' | 'COMPLETED' | 'REVIEWED'; + orderedBy: string; + orderedDate: Date; + completedDate?: Date; + reviewedBy?: string; + reviewedDate?: Date; + findings: string; + aiSummary?: string; + images: string[]; + priority: AlertPriority; +} + +/* + * End of File: patient.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/shared/types/settings.ts b/app/shared/types/settings.ts new file mode 100644 index 0000000..980762f --- /dev/null +++ b/app/shared/types/settings.ts @@ -0,0 +1,414 @@ +/* + * File: settings.ts + * Description: Settings and user profile type definitions + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +// ============================================================================ +// USER PROFILE TYPES +// ============================================================================ + +/** + * UserProfile Interface + * + * Purpose: Defines the complete user profile structure + * + * Properties: + * - Personal information (name, contact, demographics) + * - Professional information (credentials, specialties) + * - Hospital information (department, role, permissions) + * - Preferences and settings + */ +export interface UserProfile { + id: string; + mrn: string; // Medical Record Number + employeeId: string; // Hospital Employee ID + + // Personal Information + firstName: string; + lastName: string; + middleName?: string; + email: string; + phoneNumber: string; + dateOfBirth: Date; + gender: 'MALE' | 'FEMALE' | 'OTHER'; + address: Address; + + // Professional Information + credentials: string[]; // MD, PhD, etc. + specialties: string[]; // Emergency Medicine, Trauma, etc. + boardCertifications: string[]; // Board certifications + licenseNumber: string; // Medical license number + licenseExpiryDate: Date; + yearsOfExperience: number; + + // Hospital Information + department: string; // Emergency Department, etc. + role: ProfileUserRole; // Attending, Resident, etc. + shiftPreference: 'DAY' | 'NIGHT' | 'FLEXIBLE'; + supervisor?: string; // Direct supervisor + permissions: UserPermissions; + + // Profile Settings + profilePicture?: string; // URL to profile picture + emergencyContact: EmergencyContact; + preferences: UserPreferences; + + // Metadata + createdAt: Date; + updatedAt: Date; + lastLoginAt: Date; + isActive: boolean; +} + +/** + * Address Interface + * + * Purpose: Defines address structure for user profile + */ +export interface Address { + street: string; + city: string; + state: string; + zipCode: string; + country: string; +} + +/** + * EmergencyContact Interface + * + * Purpose: Defines emergency contact information + */ +export interface EmergencyContact { + name: string; + relationship: string; + phoneNumber: string; + email?: string; + address?: Address; +} + +/** + * ProfileUserRole Type + * + * Purpose: Defines different user roles in the hospital system for profile settings + */ +export type ProfileUserRole = + | 'ATTENDING_PHYSICIAN' + | 'RESIDENT_PHYSICIAN' + | 'FELLOW' + | 'MEDICAL_STUDENT' + | 'NURSE_PRACTITIONER' + | 'PHYSICIAN_ASSISTANT' + | 'ADMINISTRATOR' + | 'RESEARCHER'; + +/** + * UserPermissions Interface + * + * Purpose: Defines user permissions and access levels + */ +export interface UserPermissions { + canViewPatients: boolean; + canEditPatients: boolean; + canViewReports: boolean; + canEditReports: boolean; + canViewAlerts: boolean; + canAcknowledgeAlerts: boolean; + canManageUsers: boolean; + canViewAnalytics: boolean; + canExportData: boolean; + canAccessAdminPanel: boolean; +} + +// ============================================================================ +// USER PREFERENCES TYPES +// ============================================================================ + +/** + * UserPreferences Interface + * + * Purpose: Defines user preferences and settings + */ +export interface UserPreferences { + // Display Preferences + theme: 'LIGHT' | 'DARK' | 'AUTO'; + fontSize: 'SMALL' | 'MEDIUM' | 'LARGE'; + language: string; // ISO language code + timezone: string; // IANA timezone + + // Notification Preferences + notifications: NotificationPreferences; + + // Clinical Preferences + clinical: ClinicalPreferences; + + // Privacy Preferences + privacy: PrivacyPreferences; + + // Accessibility Preferences + accessibility: AccessibilityPreferences; +} + +/** + * NotificationPreferences Interface + * + * Purpose: Defines notification settings + */ +export interface NotificationPreferences { + pushNotifications: boolean; + emailNotifications: boolean; + smsNotifications: boolean; + + // Alert Types + criticalAlerts: boolean; + patientUpdates: boolean; + scanResults: boolean; + medicationReminders: boolean; + shiftChanges: boolean; + systemMaintenance: boolean; + + // Timing + quietHours: { + enabled: boolean; + startTime: string; // HH:MM format + endTime: string; // HH:MM format + }; + + // Sound and Vibration + soundEnabled: boolean; + vibrationEnabled: boolean; + customSound?: string; // Custom notification sound +} + +/** + * ClinicalPreferences Interface + * + * Purpose: Defines clinical workflow preferences + */ +export interface ClinicalPreferences { + // Default Views + defaultDashboardView: 'LIST' | 'GRID' | 'TIMELINE'; + defaultPatientSort: 'PRIORITY' | 'NAME' | 'ADMISSION_TIME' | 'BED_NUMBER'; + showPatientPhotos: boolean; + + // Clinical Alerts + autoAcknowledgeNonCritical: boolean; + alertTimeoutMinutes: number; // Auto-dismiss after X minutes + + // Documentation + autoSaveInterval: number; // Seconds + defaultReportTemplate: string; + enableVoiceNotes: boolean; + + // Patient Care + showAllergiesProminently: boolean; + showMedicationInteractions: boolean; + enableClinicalDecisionSupport: boolean; +} + +/** + * PrivacyPreferences Interface + * + * Purpose: Defines privacy and security settings + */ +export interface PrivacyPreferences { + // Session Management + sessionTimeoutMinutes: number; + requireBiometricAuth: boolean; + autoLockOnBackground: boolean; + + // Data Sharing + allowAnalytics: boolean; + allowCrashReporting: boolean; + allowUsageStatistics: boolean; + + // Privacy Controls + showPatientPhotos: boolean; + enableScreenCapture: boolean; + enableScreenshot: boolean; +} + +/** + * AccessibilityPreferences Interface + * + * Purpose: Defines accessibility settings + */ +export interface AccessibilityPreferences { + // Visual + highContrastMode: boolean; + reduceMotion: boolean; + boldText: boolean; + + // Audio + screenReaderEnabled: boolean; + audioDescriptions: boolean; + + // Interaction + largerTouchTargets: boolean; + reduceTransparency: boolean; +} + +// ============================================================================ +// SETTINGS SCREEN TYPES +// ============================================================================ + +/** + * SettingsSection Type + * + * Purpose: Defines different sections in the settings screen + */ +export type SettingsSection = + | 'PROFILE' + | 'NOTIFICATIONS' + | 'CLINICAL' + | 'PRIVACY' + | 'ACCESSIBILITY' + | 'ABOUT' + | 'HELP' + | 'LOGOUT'; + +/** + * SettingsItem Interface + * + * Purpose: Defines a single settings item + */ +export interface SettingsItem { + id: string; + title: string; + subtitle?: string; + icon: string; + type: 'NAVIGATION' | 'TOGGLE' | 'SELECTION' | 'ACTION'; + value?: any; + onPress?: () => void; + disabled?: boolean; + badge?: string; +} + +/** + * SettingsSection Interface + * + * Purpose: Defines a settings section with items + */ +export interface SettingsSectionData { + id: SettingsSection; + title: string; + items: SettingsItem[]; +} + +// ============================================================================ +// PROFILE UPDATE TYPES +// ============================================================================ + +/** + * ProfileUpdateData Interface + * + * Purpose: Defines data structure for profile updates + */ +export interface ProfileUpdateData { + firstName?: string; + lastName?: string; + middleName?: string; + email?: string; + phoneNumber?: string; + address?: Partial
; + emergencyContact?: Partial; + specialties?: string[]; + boardCertifications?: string[]; + shiftPreference?: 'DAY' | 'NIGHT' | 'FLEXIBLE'; +} + +/** + * ProfileUpdateRequest Interface + * + * Purpose: Defines API request for profile updates + */ +export interface ProfileUpdateRequest { + userId: string; + updates: ProfileUpdateData; + reason?: string; // Reason for update +} + +/** + * ProfileUpdateResponse Interface + * + * Purpose: Defines API response for profile updates + */ +export interface ProfileUpdateResponse { + success: boolean; + message: string; + updatedProfile?: UserProfile; + errors?: string[]; +} + +// ============================================================================ +// PASSWORD AND SECURITY TYPES +// ============================================================================ + +/** + * PasswordChangeRequest Interface + * + * Purpose: Defines password change request + */ +export interface PasswordChangeRequest { + currentPassword: string; + newPassword: string; + confirmPassword: string; +} + +/** + * SecuritySettings Interface + * + * Purpose: Defines security-related settings + */ +export interface SecuritySettings { + twoFactorEnabled: boolean; + biometricEnabled: boolean; + passwordExpiryDays: number; + failedLoginAttempts: number; + lastPasswordChange: Date; + securityQuestions: SecurityQuestion[]; +} + +/** + * SecurityQuestion Interface + * + * Purpose: Defines security question structure + */ +export interface SecurityQuestion { + id: string; + question: string; + answer: string; // Hashed answer +} + +// ============================================================================ +// EXPORT TYPES +// ============================================================================ + +export type { + UserProfile, + Address, + EmergencyContact, + ProfileUserRole, + UserPermissions, + UserPreferences, + NotificationPreferences, + ClinicalPreferences, + PrivacyPreferences, + AccessibilityPreferences, + SettingsSection, + SettingsItem, + SettingsSectionData, + ProfileUpdateData, + ProfileUpdateRequest, + ProfileUpdateResponse, + PasswordChangeRequest, + SecuritySettings, + SecurityQuestion, +}; + +/* + * End of File: settings.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/shared/utils/api.ts b/app/shared/utils/api.ts new file mode 100644 index 0000000..29ec5d5 --- /dev/null +++ b/app/shared/utils/api.ts @@ -0,0 +1,13 @@ +interface BuildHeadersParams { + token?: string; + contentType?: string; +} + +export const buildHeaders = ({ token, contentType }: BuildHeadersParams = {}) => { + const headers: Record = {}; + + if (token) headers['Authorization'] = `Bearer ${token}`; + if (contentType) headers['Content-Type'] = contentType; + + return { headers }; +}; diff --git a/app/shared/utils/constants.ts b/app/shared/utils/constants.ts new file mode 100644 index 0000000..70033ee --- /dev/null +++ b/app/shared/utils/constants.ts @@ -0,0 +1,95 @@ +/* + * File: constants.ts + * Description: Application constants and configuration values + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ +import Config from 'react-native-config'; + +// API Configuration +export const API_CONFIG = { + BASE_URL:Config.BASE_URL, + TIMEOUT: 30000, + RETRY_ATTEMPTS: 3, + RETRY_DELAY: 1000, +} as const; + +// WebSocket Configuration +export const WEBSOCKET_CONFIG = { + URL: 'wss://ws.neoscan-physician.com', + RECONNECT_INTERVAL: 5000, + MAX_RECONNECT_ATTEMPTS: 10, + HEARTBEAT_INTERVAL: 30000, +} as const; + +// Session Configuration +export const SESSION_CONFIG = { + TIMEOUT: 8 * 60 * 60 * 1000, // 8 hours + INACTIVITY_TIMEOUT: 30 * 60 * 1000, // 30 minutes + DEVICE_REMEMBER_DURATION: 30 * 24 * 60 * 60 * 1000, // 30 days +} as const; + +// Alert Configuration +export const ALERT_CONFIG = { + CRITICAL_TIMEOUT: 2 * 60 * 1000, // 2 minutes + WARNING_TIMEOUT: 10 * 60 * 1000, // 10 minutes + INFO_TIMEOUT: 30 * 60 * 1000, // 30 minutes + MAX_ALERTS: 100, +} as const; + +// Cache Configuration +export const CACHE_CONFIG = { + PATIENT_DATA: 15 * 60 * 1000, // 15 minutes + MEDICAL_RECORDS: 5 * 60 * 1000, // 5 minutes + USER_SETTINGS: 24 * 60 * 60 * 1000, // 24 hours +} as const; + +// UI Configuration +export const UI_CONFIG = { + ANIMATION_DURATION: 300, + DEBOUNCE_DELAY: 300, + THROTTLE_DELAY: 100, + TOUCH_TARGET_SIZE: 44, +} as const; + +// Medical Constants +export const MEDICAL_CONSTANTS = { + NORMAL_VITAL_SIGNS: { + HEART_RATE: { min: 60, max: 100 }, + BLOOD_PRESSURE: { systolic: { min: 90, max: 140 }, diastolic: { min: 60, max: 90 } }, + TEMPERATURE: { min: 36.1, max: 37.2 }, + RESPIRATORY_RATE: { min: 12, max: 20 }, + OXYGEN_SATURATION: { min: 95, max: 100 }, + }, + CRITICAL_VALUES: { + HEART_RATE: { min: 40, max: 140 }, + BLOOD_PRESSURE: { systolic: { min: 70, max: 200 }, diastolic: { min: 40, max: 120 } }, + TEMPERATURE: { min: 35.0, max: 40.0 }, + RESPIRATORY_RATE: { min: 8, max: 30 }, + OXYGEN_SATURATION: { min: 90, max: 100 }, + }, +} as const; + +// Error Messages +export const ERROR_MESSAGES = { + NETWORK_ERROR: 'Network connection error. Please check your internet connection.', + AUTHENTICATION_ERROR: 'Authentication failed. Please log in again.', + SERVER_ERROR: 'Server error. Please try again later.', + VALIDATION_ERROR: 'Please check your input and try again.', + UNKNOWN_ERROR: 'An unexpected error occurred. Please try again.', +} as const; + +// Success Messages +export const SUCCESS_MESSAGES = { + LOGIN_SUCCESS: 'Successfully logged in.', + LOGOUT_SUCCESS: 'Successfully logged out.', + DATA_SAVED: 'Data saved successfully.', + ALERT_ACKNOWLEDGED: 'Alert acknowledged.', + SETTINGS_UPDATED: 'Settings updated successfully.', +} as const; + +/* + * End of File: constants.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/shared/utils/helpers.ts b/app/shared/utils/helpers.ts new file mode 100644 index 0000000..7003f61 --- /dev/null +++ b/app/shared/utils/helpers.ts @@ -0,0 +1,524 @@ +/* + * File: helpers.ts + * Description: General helper functions for common operations + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import { AlertPriority, PatientStatus } from '../types'; + +// ============================================================================ +// ARRAY HELPERS - Functions for array manipulation and processing +// ============================================================================ + +/** + * chunk Function + * + * Purpose: Split an array into smaller chunks of specified size + * + * @param array - The array to be chunked + * @param size - The size of each chunk + * @returns Array of arrays, each containing up to 'size' elements + * + * Example: + * chunk([1, 2, 3, 4, 5, 6], 2) โ†’ [[1, 2], [3, 4], [5, 6]] + * + * Use case: Pagination, displaying data in groups + */ +export const chunk = (array: T[], size: number): T[][] => { + const chunks: T[][] = []; + for (let i = 0; i < array.length; i += size) { + chunks.push(array.slice(i, i + size)); + } + return chunks; +}; + +/** + * groupBy Function + * + * Purpose: Group array elements by a specified key function + * + * @param array - The array to be grouped + * @param key - Function that returns the grouping key for each element + * @returns Object with keys as group names and values as arrays of grouped items + * + * Example: + * groupBy(patients, patient => patient.department) + * โ†’ { 'Emergency': [patient1, patient2], 'ICU': [patient3] } + * + * Use case: Organizing patients by department, status, or priority + */ +export const groupBy = ( + array: T[], + key: (item: T) => K +): Record => { + return array.reduce((groups, item) => { + const group = key(item); + if (!groups[group]) { + groups[group] = []; + } + groups[group].push(item); + return groups; + }, {} as Record); +}; + +// ============================================================================ +// OBJECT HELPERS - Functions for object manipulation and transformation +// ============================================================================ + +/** + * pick Function + * + * Purpose: Create a new object with only specified properties from the original + * + * @param obj - The source object + * @param keys - Array of property names to include + * @returns New object containing only the specified properties + * + * Example: + * pick({ name: 'John', age: 30, id: 1 }, ['name', 'age']) + * โ†’ { name: 'John', age: 30 } + * + * Use case: Extracting specific patient data fields + */ +export const pick = (obj: T, keys: K[]): Pick => { + const result = {} as Pick; + keys.forEach(key => { + if (key in obj) { + result[key] = obj[key]; + } + }); + return result; +}; + +/** + * omit Function + * + * Purpose: Create a new object excluding specified properties from the original + * + * @param obj - The source object + * @param keys - Array of property names to exclude + * @returns New object without the specified properties + * + * Example: + * omit({ name: 'John', age: 30, id: 1 }, ['id']) + * โ†’ { name: 'John', age: 30 } + * + * Use case: Removing sensitive data before sending to API + */ +export const omit = (obj: T, keys: K[]): Omit => { + const result = { ...obj }; + keys.forEach(key => { + delete result[key]; + }); + return result; +}; + +// ============================================================================ +// STRING HELPERS - Functions for string manipulation and formatting +// ============================================================================ + +/** + * capitalize Function + * + * Purpose: Capitalize the first letter of a string and lowercase the rest + * + * @param str - The string to capitalize + * @returns Capitalized string + * + * Example: + * capitalize('john doe') โ†’ 'John doe' + * + * Use case: Formatting patient names, department names + */ +export const capitalize = (str: string): string => { + return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase(); +}; + +/** + * truncate Function + * + * Purpose: Truncate a string to specified length and add ellipsis + * + * @param str - The string to truncate + * @param length - Maximum length before truncation + * @returns Truncated string with ellipsis if needed + * + * Example: + * truncate('Very long diagnosis text', 15) โ†’ 'Very long diagn...' + * + * Use case: Displaying long text in limited space (diagnoses, notes) + */ +export const truncate = (str: string, length: number): string => { + if (str.length <= length) return str; + return str.slice(0, length) + '...'; +}; + +// ============================================================================ +// NUMBER HELPERS - Functions for number manipulation and validation +// ============================================================================ + +/** + * clamp Function + * + * Purpose: Constrain a number between minimum and maximum values + * + * @param value - The number to clamp + * @param min - Minimum allowed value + * @param max - Maximum allowed value + * @returns Clamped value within the specified range + * + * Example: + * clamp(150, 0, 100) โ†’ 100 + * clamp(-10, 0, 100) โ†’ 0 + * + * Use case: Validating vital signs, age ranges, scores + */ +export const clamp = (value: number, min: number, max: number): number => { + return Math.min(Math.max(value, min), max); +}; + +/** + * roundToDecimal Function + * + * Purpose: Round a number to specified decimal places + * + * @param value - The number to round + * @param decimals - Number of decimal places + * @returns Rounded number + * + * Example: + * roundToDecimal(3.14159, 2) โ†’ 3.14 + * + * Use case: Formatting vital signs, medication dosages, scores + */ +export const roundToDecimal = (value: number, decimals: number): number => { + return Math.round(value * Math.pow(10, decimals)) / Math.pow(10, decimals); +}; + +// ============================================================================ +// BOOLEAN HELPERS - Functions for boolean operations and validation +// ============================================================================ + +/** + * isTruthy Function + * + * Purpose: Check if a value is truthy (converts to true) + * + * @param value - The value to check + * @returns Boolean indicating if value is truthy + * + * Example: + * isTruthy('hello') โ†’ true + * isTruthy(0) โ†’ false + * + * Use case: Validating form inputs, checking optional fields + */ +export const isTruthy = (value: any): boolean => { + return Boolean(value); +}; + +/** + * isFalsy Function + * + * Purpose: Check if a value is falsy (converts to false) + * + * @param value - The value to check + * @returns Boolean indicating if value is falsy + * + * Example: + * isFalsy('') โ†’ true + * isFalsy(null) โ†’ true + * + * Use case: Checking for empty or null values + */ +export const isFalsy = (value: any): boolean => { + return !Boolean(value); +}; + +// ============================================================================ +// FUNCTION HELPERS - Functions for function manipulation and optimization +// ============================================================================ + +/** + * debounce Function + * + * Purpose: Create a debounced version of a function that delays execution + * + * @param func - The function to debounce + * @param delay - Delay in milliseconds + * @returns Debounced function + * + * Example: + * const debouncedSearch = debounce(searchPatients, 300); + * // Only executes search after 300ms of no input + * + * Use case: Search inputs, API calls, form validation + */ +export const debounce = any>( + func: T, + delay: number +): ((...args: Parameters) => void) => { + let timeoutId: NodeJS.Timeout; + return (...args: Parameters) => { + clearTimeout(timeoutId); + timeoutId = setTimeout(() => func(...args), delay); + }; +}; + +/** + * throttle Function + * + * Purpose: Create a throttled version of a function that limits execution frequency + * + * @param func - The function to throttle + * @param delay - Minimum time between executions in milliseconds + * @returns Throttled function + * + * Example: + * const throttledScroll = throttle(handleScroll, 100); + * // Only executes once every 100ms maximum + * + * Use case: Scroll events, resize events, real-time updates + */ +export const throttle = any>( + func: T, + delay: number +): ((...args: Parameters) => void) => { + let lastCall = 0; + return (...args: Parameters) => { + const now = Date.now(); + if (now - lastCall >= delay) { + lastCall = now; + func(...args); + } + }; +}; + +// ============================================================================ +// MEDICAL HELPERS - Healthcare-specific utility functions +// ============================================================================ + +/** + * getPriorityColor Function + * + * Purpose: Get the appropriate color for alert priority levels + * + * @param priority - The alert priority level + * @returns Hex color code for the priority + * + * Color Mapping: + * - CRITICAL: Red (#F44336) - Immediate attention required + * - HIGH: Orange (#FF9800) - High priority attention + * - MEDIUM: Blue (#2196F3) - Normal priority + * - LOW: Green (#4CAF50) - Low priority + * - Default: Gray (#9E9E9E) - Unknown priority + * + * Use case: Color-coding alerts, patient cards, status indicators + */ +export const getPriorityColor = (priority: AlertPriority): string => { + switch (priority) { + case 'CRITICAL': + return '#F44336'; // Red for critical alerts + case 'HIGH': + return '#FF9800'; // Orange for high priority + case 'MEDIUM': + return '#2196F3'; // Blue for medium priority + case 'LOW': + return '#4CAF50'; // Green for low priority + default: + return '#9E9E9E'; // Gray for unknown priority + } +}; + +/** + * getStatusColor Function + * + * Purpose: Get the appropriate color for patient status levels + * + * @param status - The patient status + * @returns Hex color code for the status + * + * Color Mapping: + * - CRITICAL: Red (#F44336) - Critical patient condition + * - ACTIVE: Blue (#2196F3) - Currently under care + * - PENDING: Orange (#FF9800) - Waiting for treatment + * - DISCHARGED: Green (#4CAF50) - Successfully discharged + * - TRANSFERRED: Gray (#9E9E9E) - Transferred to another facility + * + * Use case: Color-coding patient cards, status badges, dashboard indicators + */ +export const getStatusColor = (status: PatientStatus): string => { + switch (status) { + case 'CRITICAL': + return '#F44336'; // Red for critical patients + case 'ACTIVE': + return '#2196F3'; // Blue for active patients + case 'PENDING': + return '#FF9800'; // Orange for pending patients + case 'DISCHARGED': + return '#4CAF50'; // Green for discharged patients + case 'TRANSFERRED': + return '#9E9E9E'; // Gray for transferred patients + default: + return '#9E9E9E'; // Gray for unknown status + } +}; + +/** + * calculateAge Function + * + * Purpose: Calculate age from date of birth + * + * @param dateOfBirth - The patient's date of birth + * @returns Age in years + * + * Logic: + * - Calculates year difference + * - Adjusts for month and day to get accurate age + * - Handles leap years and month length variations + * + * Example: + * calculateAge(new Date('1990-05-15')) โ†’ 33 (if current year is 2023) + * + * Use case: Displaying patient age, age-based calculations, demographics + */ +export const calculateAge = (dateOfBirth: Date): number => { + const today = new Date(); + let age = today.getFullYear() - dateOfBirth.getFullYear(); + const monthDiff = today.getMonth() - dateOfBirth.getMonth(); + + // Adjust age if birthday hasn't occurred this year + if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < dateOfBirth.getDate())) { + age--; + } + + return age; +}; + +// ============================================================================ +// VALIDATION HELPERS - Functions for data validation +// ============================================================================ + +/** + * isValidEmail Function + * + * Purpose: Validate email address format + * + * @param email - The email address to validate + * @returns Boolean indicating if email is valid + * + * Validation Rules: + * - Must contain @ symbol + * - Must have text before and after @ + * - Must have domain extension + * + * Use case: Login forms, user registration, contact information + */ +export const isValidEmail = (email: string): boolean => { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return emailRegex.test(email); +}; + +/** + * isValidPhone Function + * + * Purpose: Validate phone number format + * + * @param phone - The phone number to validate + * @returns Boolean indicating if phone number is valid + * + * Validation Rules: + * - Minimum 10 digits + * - Allows +, spaces, hyphens, parentheses + * - International format supported + * + * Use case: Contact information, emergency contacts + */ +export const isValidPhone = (phone: string): boolean => { + const phoneRegex = /^\+?[\d\s\-\(\)]{10,}$/; + return phoneRegex.test(phone); +}; + +/** + * isValidMRN Function + * + * Purpose: Validate Medical Record Number format + * + * @param mrn - The MRN to validate + * @returns Boolean indicating if MRN is valid + * + * Validation Rules: + * - 6-12 characters long + * - Alphanumeric only (A-Z, 0-9) + * - No special characters or spaces + * + * Use case: Patient identification, medical record lookup + */ +export const isValidMRN = (mrn: string): boolean => { + const mrnRegex = /^[A-Z0-9]{6,12}$/; + return mrnRegex.test(mrn); +}; + +// ============================================================================ +// ASYNC HELPERS - Functions for asynchronous operations +// ============================================================================ + +/** + * sleep Function + * + * Purpose: Create a delay for specified milliseconds + * + * @param ms - Milliseconds to delay + * @returns Promise that resolves after the delay + * + * Example: + * await sleep(1000); // Wait for 1 second + * + * Use case: Rate limiting, loading states, animation delays + */ +export const sleep = (ms: number): Promise => { + return new Promise(resolve => setTimeout(resolve, ms)); +}; + +/** + * retry Function + * + * Purpose: Retry an async function with exponential backoff + * + * @param fn - The async function to retry + * @param retries - Number of retry attempts (default: 3) + * @param delay - Initial delay in milliseconds (default: 1000) + * @returns Promise that resolves with function result or rejects after all retries + * + * Retry Strategy: + * - Exponential backoff (delay doubles after each failure) + * - Stops after specified number of retries + * - Throws error if all retries fail + * + * Example: + * const result = await retry(fetchPatientData, 3, 1000); + * + * Use case: API calls, network requests, database operations + */ +export const retry = async ( + fn: () => Promise, + retries: number = 3, + delay: number = 1000 +): Promise => { + try { + return await fn(); + } catch (error) { + if (retries > 0) { + await sleep(delay); + return retry(fn, retries - 1, delay * 2); // Exponential backoff + } + throw error; + } +}; + +/* + * End of File: helpers.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/shared/utils/index.ts b/app/shared/utils/index.ts new file mode 100644 index 0000000..39303b7 --- /dev/null +++ b/app/shared/utils/index.ts @@ -0,0 +1,17 @@ +/* + * File: index.ts + * Description: Shared utility functions exports for the Physician App + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +export * from './api'; +export * from './constants'; +export * from './helpers'; +export * from './validators'; + +/* + * End of File: index.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/shared/utils/toast.ts b/app/shared/utils/toast.ts new file mode 100644 index 0000000..06b94c9 --- /dev/null +++ b/app/shared/utils/toast.ts @@ -0,0 +1,48 @@ +/* + * File: Toast.js + * Description: Utility for displaying toast notifications + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import Toast from 'react-native-toast-message'; +//shows success toast +const showSuccess=(text1='Successfull',text2='')=>{ + + Toast.show({ + type: 'success', + text1, + text2 + }); +} +//shows error text +const showError=(text1='something went wrong',text2='')=>{ + + Toast.show({ + type: 'error', + text1, + text2 + }); +} +//shows warning text +const showWarning=(text1='you have some warning',text2='')=>{ + + Toast.show({ + type: 'info', + text1, + text2 + }); +} + +const handleError=()=>{ + + +} + +export {showSuccess,showError,showWarning,handleError}; + +/* + * End of File: Toast.js + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ diff --git a/app/shared/utils/validators.ts b/app/shared/utils/validators.ts new file mode 100644 index 0000000..fb675be --- /dev/null +++ b/app/shared/utils/validators.ts @@ -0,0 +1,22 @@ +/* + * File: validators.ts + * Description: Common input validation functions. + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +/** + * Validates an email address against a standard regex pattern. + * @param email The email address string to validate. + * @returns `true` if the email is valid, `false` otherwise. + */ +export const validateEmail = (email: string): boolean => { + const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return regex.test(email); +}; + +/* + * End of File: validators.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/store/Provider.tsx b/app/store/Provider.tsx new file mode 100644 index 0000000..77bb4f5 --- /dev/null +++ b/app/store/Provider.tsx @@ -0,0 +1,98 @@ +/* + * File: Provider.tsx + * Description: Redux Provider wrapper with PersistGate + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import React from 'react'; +import { Provider as ReduxProvider } from 'react-redux'; +import { PersistGate } from 'redux-persist/integration/react'; +import { store ,persistor} from '.'; +import { Text, View } from 'react-native'; + + +// ============================================================================ +// PROVIDER COMPONENT +// ============================================================================ + +/** + * Store Provider Component + * + * Purpose: Wrap the app with Redux Provider and PersistGate + * + * Features: + * - Redux store provider + * - Redux Persist gate for state rehydration + * - Loading state during persistence + * - Error handling for persistence failures + * + * @param children - React children components + */ +interface StoreProviderProps { + children: React.ReactNode; +} + +/** + * Loading Component for PersistGate + * + * Purpose: Show loading state while Redux state is being rehydrated + * + * Features: + * - Simple loading indicator + * - Consistent with app theme + * - Minimal blocking UI + */ +const PersistLoadingComponent: React.FC = () => { + return ( + + Loading app data... + + ); +}; + +/** + * Store Provider Component + * + * Purpose: Main provider component that wraps the app + * + * Features: + * - Redux Provider for state management + * - PersistGate for state persistence + * - Loading state during rehydration + * - Error handling for persistence issues + */ +export const StoreProvider: React.FC = ({ children }) => { + return ( + + } + persistor={persistor} + onBeforeLift={() => { + // Called before the store is lifted (rehydrated) + console.log('Redux store is about to be rehydrated'); + }} + onAfterLift={() => { + // Called after the store is lifted (rehydrated) + console.log('Redux store has been rehydrated'); + }} + > + {children} + + + ); +}; + +/* + * End of File: Provider.tsx + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/store/hooks.ts b/app/store/hooks.ts new file mode 100644 index 0000000..4b6d1c6 --- /dev/null +++ b/app/store/hooks.ts @@ -0,0 +1,307 @@ +/* + * File: hooks.ts + * Description: Custom Redux hooks for easy store access + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import { useDispatch, useSelector, TypedUseSelectorHook } from 'react-redux'; +import { RootState, AppDispatch } from './index'; + +// ============================================================================ +// TYPED HOOKS +// ============================================================================ + +/** + * Typed Use Dispatch Hook + * + * Purpose: Provide typed dispatch function + * + * Features: + * - Type-safe dispatch function + * - Proper TypeScript support + * - Consistent with Redux Toolkit patterns + */ +export const useAppDispatch = () => useDispatch(); + +/** + * Typed Use Selector Hook + * + * Purpose: Provide typed selector function + * + * Features: + * - Type-safe selector function + * - Proper TypeScript support + * - Consistent with Redux Toolkit patterns + */ +export const useAppSelector: TypedUseSelectorHook = useSelector; + +// ============================================================================ +// AUTH HOOKS +// ============================================================================ + +/** + * Use Auth Hook + * + * Purpose: Get authentication state and actions + * + * @returns Authentication state and actions + */ +export const useAuth = () => { + const auth = useAppSelector((state) => state.auth); + const dispatch = useAppDispatch(); + + return { + // State + user: auth.user, + isAuthenticated: auth.isAuthenticated, + isLoading: auth.isLoading, + isLoggingIn: auth.isLoggingIn, + isLoggingOut: auth.isLoggingOut, + error: auth.error, + loginError: auth.loginError, + + // Actions + dispatch, + }; +}; + +/** + * Use User Hook + * + * Purpose: Get current user information + * + * @returns Current user data + */ +export const useUser = () => { + const user = useAppSelector((state) => state.auth.user); + + return { + user, + isLoggedIn: !!user, + hasPermission: (permission: string) => user?.permissions?.includes(permission) || false, + }; +}; + +// ============================================================================ +// DASHBOARD HOOKS +// ============================================================================ + +/** + * Use Dashboard Hook + * + * Purpose: Get dashboard state and actions + * + * @returns Dashboard state and actions + */ +export const useDashboard = () => { + const dashboard = useAppSelector((state) => state.dashboard); + const dispatch = useAppDispatch(); + + return { + // State + dashboard: dashboard.dashboard, + isLoading: dashboard.isLoading, + isRefreshing: dashboard.isRefreshing, + error: dashboard.error, + lastUpdated: dashboard.lastUpdated, + isConnected: dashboard.isConnected, + selectedFilter: dashboard.selectedFilter, + sortBy: dashboard.sortBy, + sortOrder: dashboard.sortOrder, + + // Actions + dispatch, + }; +}; + +// ============================================================================ +// ALERTS HOOKS +// ============================================================================ + +/** + * Use Alerts Hook + * + * Purpose: Get alerts state and actions + * + * @returns Alerts state and actions + */ +export const useAlerts = () => { + const alerts = useAppSelector((state) => state.alerts); + const dispatch = useAppDispatch(); + + return { + // State + alerts: alerts.alerts, + isLoading: alerts.isLoading, + isRefreshing: alerts.isRefreshing, + error: alerts.error, + lastUpdated: alerts.lastUpdated, + unreadCount: alerts.unreadCount, + criticalCount: alerts.criticalCount, + selectedFilter: alerts.selectedFilter, + sortBy: alerts.sortBy, + sortOrder: alerts.sortOrder, + + // Computed values + criticalAlerts: alerts.alerts.filter(alert => alert.priority === 'CRITICAL'), + unreadAlerts: alerts.alerts.filter(alert => !alert.isRead), + acknowledgedAlerts: alerts.alerts.filter(alert => alert.isAcknowledged), + + // Actions + dispatch, + }; +}; + +// ============================================================================ +// PATIENT CARE HOOKS +// ============================================================================ + +/** + * Use Patient Care Hook + * + * Purpose: Get patient care state and actions + * + * @returns Patient care state and actions + */ +export const usePatientCare = () => { + const patientCare = useAppSelector((state) => state.patientCare); + const dispatch = useAppDispatch(); + + return { + // State + patients: patientCare.patients, + currentPatient: patientCare.currentPatient, + isLoading: patientCare.isLoading, + isRefreshing: patientCare.isRefreshing, + isLoadingPatientDetails: patientCare.isLoadingPatientDetails, + error: patientCare.error, + searchQuery: patientCare.searchQuery, + selectedFilter: patientCare.selectedFilter, + sortBy: patientCare.sortBy, + sortOrder: patientCare.sortOrder, + currentPage: patientCare.currentPage, + itemsPerPage: patientCare.itemsPerPage, + totalItems: patientCare.totalItems, + lastUpdated: patientCare.lastUpdated, + + // Computed values + activePatients: patientCare.patients.filter(patient => patient.status === 'ACTIVE'), + criticalPatients: patientCare.patients.filter(patient => patient.priority === 'CRITICAL'), + dischargedPatients: patientCare.patients.filter(patient => patient.status === 'DISCHARGED'), + + // Actions + dispatch, + }; +}; + +// ============================================================================ +// SETTINGS HOOKS +// ============================================================================ + +/** + * Use Settings Hook + * + * Purpose: Get settings state and actions + * + * @returns Settings state and actions + */ +export const useSettings = () => { + const settings = useAppSelector((state) => state.settings); + const dispatch = useAppDispatch(); + + return { + // State + userProfile: settings.userProfile, + userPreferences: settings.userPreferences, + isLoading: settings.isLoading, + isUpdatingProfile: settings.isUpdatingProfile, + isUpdatingPreferences: settings.isUpdatingPreferences, + error: settings.error, + isProfileValid: settings.isProfileValid, + isPreferencesValid: settings.isPreferencesValid, + profileLastUpdated: settings.profileLastUpdated, + preferencesLastUpdated: settings.preferencesLastUpdated, + + // Actions + dispatch, + }; +}; + +// ============================================================================ +// UI HOOKS +// ============================================================================ + +/** + * Use UI Hook + * + * Purpose: Get UI state and actions + * + * @returns UI state and actions + */ +export const useUI = () => { + const ui = useAppSelector((state) => state.ui); + const dispatch = useAppDispatch(); + + return { + // State + isLoading: ui.isLoading, + loadingMessage: ui.loadingMessage, + isModalOpen: ui.isModalOpen, + modalType: ui.modalType, + modalData: ui.modalData, + isOverlayVisible: ui.isOverlayVisible, + overlayType: ui.overlayType, + currentScreen: ui.currentScreen, + navigationStack: ui.navigationStack, + isDarkMode: ui.isDarkMode, + fontSize: ui.fontSize, + highContrast: ui.highContrast, + isRefreshing: ui.isRefreshing, + isScrolling: ui.isScrolling, + lastInteraction: ui.lastInteraction, + hasError: ui.hasError, + errorMessage: ui.errorMessage, + errorType: ui.errorType, + + // Actions + dispatch, + }; +}; + +// ============================================================================ +// UTILITY HOOKS +// ============================================================================ + +/** + * Use Store Hook + * + * Purpose: Get access to the entire store state + * + * @returns Entire store state + */ +export const useStore = () => { + return useAppSelector((state) => state); +}; + +/** + * Use Persistence Status Hook + * + * Purpose: Get Redux Persist status + * + * @returns Persistence status + */ +export const usePersistenceStatus = () => { + // This would need to be implemented with a custom selector + // that accesses the persistor state + return { + isRehydrated: true, // Placeholder + isPersisting: false, // Placeholder + }; +}; + +/* + * End of File: hooks.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/store/index.ts b/app/store/index.ts new file mode 100644 index 0000000..081d0c2 --- /dev/null +++ b/app/store/index.ts @@ -0,0 +1,242 @@ +/* + * File: index.ts + * Description: Main Redux store configuration with Redux Persist + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import { configureStore, combineReducers } from '@reduxjs/toolkit'; +import { persistStore, persistReducer, FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER } from 'redux-persist'; +import AsyncStorage from '@react-native-async-storage/async-storage'; + +// Import all slice reducers from their respective modules +import authReducer from '../modules/Auth/redux/authSlice'; +import dashboardReducer from '../modules/Dashboard/redux/dashboardSlice'; +import patientCareReducer from '../modules/PatientCare/redux/patientCareSlice'; +import alertsReducer from '../modules/Dashboard/redux/alertsSlice'; +import settingsReducer from '../modules/Settings/redux/settingsSlice'; +import uiReducer from '../modules/Dashboard/redux/uiSlice'; +import hospitalReducer from '../modules/Auth/redux/hospitalSlice'; + +// ============================================================================ +// REDUX PERSIST CONFIGURATION +// ============================================================================ + +/** + * Redux Persist Configuration + * + * Purpose: Configure how Redux state is persisted to AsyncStorage + * + * Features: + * - AsyncStorage as storage engine + * - Selective persistence (not all state needs to be persisted) + * - Migration support for app updates + * - Debug mode for development + */ +const persistConfig = { + // Storage engine (AsyncStorage for React Native) + storage: AsyncStorage, + + // Key for the persisted state in AsyncStorage + key: 'neoscan_physician_store', + + // Version for migration support + version: 1, + + // Whitelist: Only persist these reducers + whitelist: [ + 'auth', // Authentication state (user login, tokens) + 'settings', // User preferences and settings + 'patientCare', // Patient data cache + ], + + // Blacklist: Don't persist these reducers + blacklist: [ + 'ui', // UI state (loading, modals, etc.) + 'alerts', // Temporary alerts and notifications + 'dashboard', // Real-time dashboard data + 'hospital', // Hospital data (fetched fresh each time) + ], + + // Migration configuration for app updates + migrate: (state: any) => { + // Handle state migrations when app version changes + return Promise.resolve(state); + }, + + // Serialization options + serialize: true, + + // Timeout for storage operations + timeout: 10000, +}; + +// ============================================================================ +// ROOT REDUCER +// ============================================================================ + +/** + * Root Reducer + * + * Purpose: Combine all slice reducers into a single root reducer + * + * Structure: + * - auth: Authentication and user management + * - dashboard: ER dashboard data and statistics + * - patientCare: Patient information and medical records + * - alerts: Critical alerts and notifications + * - settings: User preferences and app settings + * - ui: User interface state (loading, modals, etc.) + */ +const rootReducer = combineReducers({ + auth: authReducer, + dashboard: dashboardReducer, + patientCare: patientCareReducer, + alerts: alertsReducer, + settings: settingsReducer, + ui: uiReducer, + hospital: hospitalReducer, +}); + +// ============================================================================ +// PERSISTED REDUCER +// ============================================================================ + +/** + * Persisted Reducer + * + * Purpose: Wrap the root reducer with Redux Persist functionality + * + * Features: + * - Automatic state persistence to AsyncStorage + * - State rehydration on app startup + * - Selective persistence based on whitelist/blacklist + */ +const persistedReducer = persistReducer(persistConfig, rootReducer); + +// ============================================================================ +// STORE CONFIGURATION +// ============================================================================ + +/** + * Redux Store Configuration + * + * Purpose: Configure the Redux store with middleware and persistence + * + * Features: + * - Redux Toolkit for simplified Redux setup + * - Redux Persist for state persistence + * - Development tools integration + * - Error handling and logging + */ +export const store = configureStore({ + // Use the persisted reducer + reducer: persistedReducer, + + // Middleware configuration + middleware: (getDefaultMiddleware) => + getDefaultMiddleware({ + // Configure serializable check for Redux Persist actions + serializableCheck: { + ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER], + }, + + }), + + // Preloaded state (for SSR or initial state) + preloadedState: undefined, +}); + +// ============================================================================ +// PERSISTOR CONFIGURATION +// ============================================================================ + +/** + * Redux Persistor + * + * Purpose: Handle state persistence and rehydration + * + * Features: + * - Automatic state saving to AsyncStorage + * - State restoration on app startup + * - Migration handling for app updates + * - Error handling for storage operations + */ +export const persistor = persistStore(store); + +// ============================================================================ +// TYPE EXPORTS +// ============================================================================ + +// Infer the `RootState` and `AppDispatch` types from the store itself +export type RootState = ReturnType; +export type AppDispatch = typeof store.dispatch; + +// ============================================================================ +// STORE UTILITIES +// ============================================================================ + +/** + * Get Store State + * + * Purpose: Get the current state from the store + * + * @returns Current Redux state + */ +export const getStoreState = (): RootState => store.getState(); + +/** + * Dispatch Action + * + * Purpose: Dispatch an action to the store + * + * @param action - Redux action to dispatch + * @returns Dispatched action result + */ +export const dispatchAction = (action: any) => store.dispatch(action); + +/** + * Subscribe to Store Changes + * + * Purpose: Subscribe to store state changes + * + * @param listener - Function to call when state changes + * @returns Unsubscribe function + */ +export const subscribeToStore = (listener: () => void) => store.subscribe(listener); + +/** + * Clear Persisted State + * + * Purpose: Clear all persisted state from AsyncStorage + * + * @returns Promise that resolves when state is cleared + */ +export const clearPersistedState = async (): Promise => { + try { + await persistor.purge(); + console.log('Persisted state cleared successfully'); + } catch (error) { + console.error('Failed to clear persisted state:', error); + throw error; + } +}; + +/** + * Get Persistence Status + * + * Purpose: Get the current status of Redux Persist + * + * @returns Persistence status object + */ +export const getPersistenceStatus = () => { + return { + isRehydrated: persistor.getState().bootstrapped, + }; +}; + +/* + * End of File: index.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/theme/animations.ts b/app/theme/animations.ts new file mode 100644 index 0000000..c86249d --- /dev/null +++ b/app/theme/animations.ts @@ -0,0 +1,32 @@ +/* + * File: animations.ts + * Description: Animation system definitions for the Physician App + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +export const animations = { + durations: { + fast: 150, + normal: 300, + slow: 500, + }, + easing: { + standard: 'cubic-bezier(0.4, 0.0, 0.2, 1)', + deceleration: 'cubic-bezier(0.0, 0.0, 0.2, 1)', + acceleration: 'cubic-bezier(0.4, 0.0, 1, 1)', + }, + transitions: { + fade: { opacity: [0, 1], duration: 300 }, + slide: { transform: [{ translateY: [20, 0] }], duration: 300 }, + scale: { transform: [{ scale: [0.95, 1] }], duration: 200 }, + }, +} as const; + +export type AnimationKey = keyof typeof animations; + +/* + * End of File: animations.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/theme/colors.ts b/app/theme/colors.ts new file mode 100644 index 0000000..a38ded8 --- /dev/null +++ b/app/theme/colors.ts @@ -0,0 +1,58 @@ +/* + * File: colors.ts + * Description: Color palette definitions for the Physician App + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +export const colors = { + // Primary Colors + primary: '#2196F3', + secondary: '#1976D2', + tertiary: '#E3F2FD', + quaternary: '#0D47A1', + + // Text Colors + textPrimary: '#212121', + textSecondary: '#757575', + textMuted: '#9E9E9E', + + // Background Colors + background: '#FFFFFF', + backgroundAlt: '#FAFAFA', + backgroundAccent: '#F5F5F5', + + // Status Colors + success: '#4CAF50', + warning: '#FF9800', + error: '#F44336', + info: '#2196F3', + + // UI Elements + border: '#E0E0E0', + cardBackground: '#FFFFFF', + shadow: 'rgba(0, 0, 0, 0.1)', + + // Alert Colors + critical: '#F44336', + criticalBackground: '#FFEBEE', + warningBackground: '#FFF3E0', + successBackground: '#E8F5E8', + infoBackground: '#E3F2FD', + + // Patient Status Colors + active: '#2196F3', + activeBackground: '#E3F2FD', + pending: '#FF9800', + pendingBackground: '#FFF3E0', + completed: '#4CAF50', + completedBackground: '#E8F5E8', +} as const; + +export type ColorKey = keyof typeof colors; + +/* + * End of File: colors.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/theme/index.ts b/app/theme/index.ts new file mode 100644 index 0000000..baa6efd --- /dev/null +++ b/app/theme/index.ts @@ -0,0 +1,19 @@ +/* + * File: index.ts + * Description: Theme module exports for the Physician App + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +export * from './colors'; +export * from './typography'; +export * from './spacing'; +export * from './shadows'; +export * from './animations'; +export * from './theme'; + +/* + * End of File: index.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/theme/shadows.ts b/app/theme/shadows.ts new file mode 100644 index 0000000..53f3047 --- /dev/null +++ b/app/theme/shadows.ts @@ -0,0 +1,54 @@ +/* + * File: shadows.ts + * Description: Shadow system definitions for the Physician App + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import { colors } from './colors'; + +export const shadows = { + small: { + shadowColor: colors.shadow, + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.1, + shadowRadius: 4, + elevation: 2, + }, + medium: { + shadowColor: colors.shadow, + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.15, + shadowRadius: 8, + elevation: 4, + }, + large: { + shadowColor: colors.shadow, + shadowOffset: { width: 0, height: 8 }, + shadowOpacity: 0.2, + shadowRadius: 16, + elevation: 8, + }, + critical: { + shadowColor: colors.critical, + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.15, + shadowRadius: 8, + elevation: 4, + }, + primary: { + shadowColor: colors.primary, + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.3, + shadowRadius: 8, + elevation: 6, + }, +} as const; + +export type ShadowKey = keyof typeof shadows; + +/* + * End of File: shadows.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/theme/spacing.ts b/app/theme/spacing.ts new file mode 100644 index 0000000..9a1d72e --- /dev/null +++ b/app/theme/spacing.ts @@ -0,0 +1,41 @@ +/* + * File: spacing.ts + * Description: Spacing and layout system definitions for the Physician App + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +export const spacing = { + xs: 4, + sm: 8, + md: 16, + lg: 24, + xl: 32, + xxl: 48, + xxxl: 64, +} as const; + +export const borderRadius = { + small: 4, + medium: 8, + large: 12, + xlarge: 16, + round: 50, +} as const; + +export const breakpoints = { + mobile: 375, + tablet: 768, + desktop: 1024, + largeDesktop: 1440, +} as const; + +export type SpacingKey = keyof typeof spacing; +export type BorderRadiusKey = keyof typeof borderRadius; +export type BreakpointKey = keyof typeof breakpoints; + +/* + * End of File: spacing.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/theme/theme.ts b/app/theme/theme.ts new file mode 100644 index 0000000..426c0e9 --- /dev/null +++ b/app/theme/theme.ts @@ -0,0 +1,66 @@ +/* + * File: theme.ts + * Description: Main theme configuration combining all design system elements + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import { colors } from './colors'; +import { typography } from './typography'; +import { spacing, borderRadius, breakpoints } from './spacing'; +import { shadows } from './shadows'; +import { animations } from './animations'; + +/** + * Main Theme Object + * + * Purpose: Centralized design system that provides consistent styling across the entire app + * + * Design System Components: + * 1. Colors: Healthcare-focused color palette with status indicators + * 2. Typography: Font families, sizes, weights, and line heights + * 3. Spacing: Consistent spacing scale for layouts and components + * 4. Border Radius: Standardized corner radius values + * 5. Breakpoints: Responsive design breakpoints for different screen sizes + * 6. Shadows: Elevation and depth system for UI components + * 7. Animations: Standardized animation durations and easing functions + * + * Usage: + * - Import this theme object in components: import { theme } from '../theme/theme' + * - Access design tokens: theme.colors.primary, theme.spacing.md, etc. + * - Ensures consistency and maintainability across the app + * + * Benefits: + * - Single source of truth for all design decisions + * - Easy to update and maintain design system + * - Consistent user experience across all screens + * - Supports dark mode and accessibility requirements + */ +export const theme = { + colors, // Color palette with primary, secondary, status, and background colors + typography, // Font system with sizes, weights, and families + spacing, // Spacing scale for margins, padding, and layouts + borderRadius, // Border radius values for rounded corners + breakpoints, // Responsive breakpoints for different screen sizes + shadows, // Shadow system for elevation and depth + animations, // Animation durations and easing functions +} as const; // Make theme immutable for type safety + +/** + * Theme Type + * + * Purpose: TypeScript type definition for the theme object + * + * Benefits: + * - Provides type safety when using theme properties + * - Enables autocomplete in IDEs + * - Prevents typos and invalid property access + * - Supports theme customization and extension + */ +export type Theme = typeof theme; + +/* + * End of File: theme.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/theme/typography.ts b/app/theme/typography.ts new file mode 100644 index 0000000..ec15fc4 --- /dev/null +++ b/app/theme/typography.ts @@ -0,0 +1,59 @@ +/* + * File: typography.ts + * Description: Typography system definitions for the Physician App + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +export const typography = { + // Font Families + fontFamily: { + bold: 'Roboto-Bold', + medium: 'Roboto-Medium', + regular: 'Roboto-Regular', + light: 'Roboto-Light', + semibold: 'Roboto-SemiBold', + extrabold: 'Roboto-ExtraBold', + }, + + // Font Weights + fontWeight: { + light: '300', + regular: '400', + medium: '500', + bold: '700', + }, + + // Font Sizes + fontSize: { + displayLarge: 32, + displayMedium: 24, + displaySmall: 20, + bodyLarge: 16, + bodyMedium: 14, + bodySmall: 12, + caption: 10, + }, + + // Line Heights + lineHeight: { + tight: 1.2, + normal: 1.4, + relaxed: 1.6, + }, + + // Letter Spacing + letterSpacing: { + tight: -0.5, + normal: 0, + wide: 0.5, + }, +} as const; + +export type TypographyKey = keyof typeof typography; + +/* + * End of File: typography.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/types/react-native-vector-icons.d.ts b/app/types/react-native-vector-icons.d.ts new file mode 100644 index 0000000..fce721f --- /dev/null +++ b/app/types/react-native-vector-icons.d.ts @@ -0,0 +1,51 @@ +/* + * File: react-native-vector-icons.d.ts + * Description: Type declarations for react-native-vector-icons + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +declare module 'react-native-vector-icons/Feather' { + import { Component } from 'react'; + import { TextProps } from 'react-native'; + + interface IconProps extends TextProps { + name: string; + size?: number; + color?: string; + } + + export default class Icon extends Component {} +} + +declare module 'react-native-vector-icons/MaterialIcons' { + import { Component } from 'react'; + import { TextProps } from 'react-native'; + + interface IconProps extends TextProps { + name: string; + size?: number; + color?: string; + } + + export default class Icon extends Component {} +} + +declare module 'react-native-vector-icons/Ionicons' { + import { Component } from 'react'; + import { TextProps } from 'react-native'; + + interface IconProps extends TextProps { + name: string; + size?: number; + color?: string; + } + + export default class Icon extends Component {} +} + +/* + * End of File: react-native-vector-icons.d.ts + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/babel.config.js b/babel.config.js index f7b3da3..02c7d13 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,3 +1,4 @@ module.exports = { presets: ['module:@react-native/babel-preset'], + plugins: ['react-native-reanimated/plugin'], }; diff --git a/index.js b/index.js index a850d03..7a02d48 100644 --- a/index.js +++ b/index.js @@ -3,7 +3,7 @@ */ import {AppRegistry} from 'react-native'; -import App from './App'; +import App from './app/App'; import {name as appName} from './app.json'; AppRegistry.registerComponent(appName, () => App); diff --git a/ios/NeoScan_Physician.xcodeproj/project.pbxproj b/ios/NeoScan_Physician.xcodeproj/project.pbxproj index 278bdf5..141f354 100644 --- a/ios/NeoScan_Physician.xcodeproj/project.pbxproj +++ b/ios/NeoScan_Physician.xcodeproj/project.pbxproj @@ -11,6 +11,14 @@ 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; }; 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; + 9BFF7E8F967043379EA34EF5 /* Roboto-Black.ttf in Resources */ = {isa = PBXBuildFile; fileRef = C93B020BF2D44311BFAF317D /* Roboto-Black.ttf */; }; + 64ADC498A16B4DD2979F9EC6 /* Roboto-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = E8DE31AC3A3B46AFBAB8E623 /* Roboto-Bold.ttf */; }; + 9A05BCBBB57F42CF891B4E76 /* Roboto-ExtraBold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 6D158A6D27784CB3AAFC4572 /* Roboto-ExtraBold.ttf */; }; + E9AB985237534D1A92E3A5C8 /* Roboto-ExtraLight.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 6787284EF58F40B4A29BE810 /* Roboto-ExtraLight.ttf */; }; + A345282A09764F4B8935E1CE /* Roboto-Light.ttf in Resources */ = {isa = PBXBuildFile; fileRef = F3B7B24AB39048E28AA013C6 /* Roboto-Light.ttf */; }; + 6C3A045CF24641D79616809F /* Roboto-Medium.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 57615910A7564380B1D23731 /* Roboto-Medium.ttf */; }; + A79B0AFEC3DA42AAAA56DC75 /* Roboto-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = B9D41D27862846A8A6563185 /* Roboto-Regular.ttf */; }; + 264B0BD7896E430EB2761212 /* Roboto-SemiBold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7539BBD08F0743178F5517A3 /* Roboto-SemiBold.ttf */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -35,6 +43,14 @@ 761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = NeoScan_Physician/AppDelegate.swift; sourceTree = ""; }; 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = NeoScan_Physician/LaunchScreen.storyboard; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; + C93B020BF2D44311BFAF317D /* Roboto-Black.ttf */ = {isa = PBXFileReference; name = "Roboto-Black.ttf"; path = "../app/assets/fonts/Roboto-Black.ttf"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + E8DE31AC3A3B46AFBAB8E623 /* Roboto-Bold.ttf */ = {isa = PBXFileReference; name = "Roboto-Bold.ttf"; path = "../app/assets/fonts/Roboto-Bold.ttf"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + 6D158A6D27784CB3AAFC4572 /* Roboto-ExtraBold.ttf */ = {isa = PBXFileReference; name = "Roboto-ExtraBold.ttf"; path = "../app/assets/fonts/Roboto-ExtraBold.ttf"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + 6787284EF58F40B4A29BE810 /* Roboto-ExtraLight.ttf */ = {isa = PBXFileReference; name = "Roboto-ExtraLight.ttf"; path = "../app/assets/fonts/Roboto-ExtraLight.ttf"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + F3B7B24AB39048E28AA013C6 /* Roboto-Light.ttf */ = {isa = PBXFileReference; name = "Roboto-Light.ttf"; path = "../app/assets/fonts/Roboto-Light.ttf"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + 57615910A7564380B1D23731 /* Roboto-Medium.ttf */ = {isa = PBXFileReference; name = "Roboto-Medium.ttf"; path = "../app/assets/fonts/Roboto-Medium.ttf"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + B9D41D27862846A8A6563185 /* Roboto-Regular.ttf */ = {isa = PBXFileReference; name = "Roboto-Regular.ttf"; path = "../app/assets/fonts/Roboto-Regular.ttf"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + 7539BBD08F0743178F5517A3 /* Roboto-SemiBold.ttf */ = {isa = PBXFileReference; name = "Roboto-SemiBold.ttf"; path = "../app/assets/fonts/Roboto-SemiBold.ttf"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -93,6 +109,7 @@ 83CBBA001A601CBA00E9B192 /* Products */, 2D16E6871FA4F8E400B85C8A /* Frameworks */, BBD78D7AC51CEA395F1C20DB /* Pods */, + C38CA987921A4CA4AEDBB3E5 /* Resources */, ); indentWidth = 2; sourceTree = ""; @@ -116,6 +133,22 @@ path = Pods; sourceTree = ""; }; + C38CA987921A4CA4AEDBB3E5 /* Resources */ = { + isa = "PBXGroup"; + children = ( + C93B020BF2D44311BFAF317D /* Roboto-Black.ttf */, + E8DE31AC3A3B46AFBAB8E623 /* Roboto-Bold.ttf */, + 6D158A6D27784CB3AAFC4572 /* Roboto-ExtraBold.ttf */, + 6787284EF58F40B4A29BE810 /* Roboto-ExtraLight.ttf */, + F3B7B24AB39048E28AA013C6 /* Roboto-Light.ttf */, + 57615910A7564380B1D23731 /* Roboto-Medium.ttf */, + B9D41D27862846A8A6563185 /* Roboto-Regular.ttf */, + 7539BBD08F0743178F5517A3 /* Roboto-SemiBold.ttf */, + ); + name = Resources; + sourceTree = ""; + path = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -185,6 +218,14 @@ files = ( 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */, 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, + 9BFF7E8F967043379EA34EF5 /* Roboto-Black.ttf in Resources */, + 64ADC498A16B4DD2979F9EC6 /* Roboto-Bold.ttf in Resources */, + 9A05BCBBB57F42CF891B4E76 /* Roboto-ExtraBold.ttf in Resources */, + E9AB985237534D1A92E3A5C8 /* Roboto-ExtraLight.ttf in Resources */, + A345282A09764F4B8935E1CE /* Roboto-Light.ttf in Resources */, + 6C3A045CF24641D79616809F /* Roboto-Medium.ttf in Resources */, + A79B0AFEC3DA42AAAA56DC75 /* Roboto-Regular.ttf in Resources */, + 264B0BD7896E430EB2761212 /* Roboto-SemiBold.ttf in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ios/NeoScan_Physician/Info.plist b/ios/NeoScan_Physician/Info.plist index 45fe821..bdd43aa 100644 --- a/ios/NeoScan_Physician/Info.plist +++ b/ios/NeoScan_Physician/Info.plist @@ -26,14 +26,13 @@ NSAppTransportSecurity - NSAllowsArbitraryLoads NSAllowsLocalNetworking NSLocationWhenInUseUsageDescription - + UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities @@ -48,5 +47,16 @@ UIViewControllerBasedStatusBarAppearance + UIAppFonts + + Roboto-Black.ttf + Roboto-Bold.ttf + Roboto-ExtraBold.ttf + Roboto-ExtraLight.ttf + Roboto-Light.ttf + Roboto-Medium.ttf + Roboto-Regular.ttf + Roboto-SemiBold.ttf + diff --git a/ios/link-assets-manifest.json b/ios/link-assets-manifest.json new file mode 100644 index 0000000..441aa21 --- /dev/null +++ b/ios/link-assets-manifest.json @@ -0,0 +1,37 @@ +{ + "migIndex": 1, + "data": [ + { + "path": "app/assets/fonts/Roboto-Black.ttf", + "sha1": "d1678489a8d5645f16486ec52d77b651ff0bf327" + }, + { + "path": "app/assets/fonts/Roboto-Bold.ttf", + "sha1": "508c35dee818addce6cc6d1fb6e42f039da5a7cf" + }, + { + "path": "app/assets/fonts/Roboto-ExtraBold.ttf", + "sha1": "3dbfd71b6fbcfbd8e7ee8a8dd033dc5aaad63249" + }, + { + "path": "app/assets/fonts/Roboto-ExtraLight.ttf", + "sha1": "df556e64732e5c272349e13cb5f87591a1ae779b" + }, + { + "path": "app/assets/fonts/Roboto-Light.ttf", + "sha1": "318b44c0a32848f78bf11d4fbf3355d00647a796" + }, + { + "path": "app/assets/fonts/Roboto-Medium.ttf", + "sha1": "fa5192203f85ddb667579e1bdf26f12098bb873b" + }, + { + "path": "app/assets/fonts/Roboto-Regular.ttf", + "sha1": "3bff51436aa7eb995d84cfc592cc63e1316bb400" + }, + { + "path": "app/assets/fonts/Roboto-SemiBold.ttf", + "sha1": "9ca139684fe902c8310dd82991648376ac9838db" + } + ] +} diff --git a/package-lock.json b/package-lock.json index fd127d8..a9785d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,43 @@ "name": "NeoScan_Physician", "version": "0.0.1", "dependencies": { + "@react-native-async-storage/async-storage": "^2.1.0", + "@react-native-clipboard/clipboard": "^1.16.1", + "@react-native-community/netinfo": "^11.4.1", + "@react-navigation/bottom-tabs": "^7.2.0", + "@react-navigation/native": "^7.0.14", + "@react-navigation/native-stack": "^7.2.0", + "@react-navigation/stack": "^7.1.1", + "@reduxjs/toolkit": "^2.5.1", + "@testing-library/react-native": "^13.2.0", + "apisauce": "^3.1.0", + "lottie-react-native": "^7.2.2", "react": "19.0.0", - "react-native": "0.79.0" + "react-native": "0.79.0", + "react-native-biometrics": "^3.0.1", + "react-native-blob-util": "^0.22.2", + "react-native-chart-kit": "^6.12.0", + "react-native-config": "^1.5.5", + "react-native-device-info": "^10.11.0", + "react-native-element-dropdown": "^2.12.4", + "react-native-gesture-handler": "^2.22.1", + "react-native-image-picker": "^7.2.3", + "react-native-keychain": "^10.0.0", + "react-native-linear-gradient": "^2.8.3", + "react-native-permissions": "^5.2.4", + "react-native-raw-bottom-sheet": "^3.0.0", + "react-native-reanimated": "^3.18.0", + "react-native-render-html": "^6.3.4", + "react-native-responsive-dimensions": "^3.1.1", + "react-native-safe-area-context": "^5.1.0", + "react-native-screens": "^4.5.0", + "react-native-share": "^12.0.9", + "react-native-svg": "^15.11.1", + "react-native-toast-message": "^2.2.1", + "react-native-tts": "^4.1.1", + "react-native-vector-icons": "^10.2.0", + "react-redux": "^9.2.0", + "redux-persist": "^6.0.0" }, "devDependencies": { "@babel/core": "^7.25.2", @@ -140,7 +175,6 @@ "version": "7.27.3", "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.27.3" @@ -169,7 +203,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz", "integrity": "sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", @@ -191,7 +224,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz", "integrity": "sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", @@ -235,7 +267,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.27.1", @@ -279,7 +310,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.27.1" @@ -319,7 +349,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-member-expression-to-functions": "^7.27.1", @@ -337,7 +366,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.27.1", @@ -685,7 +713,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -803,7 +830,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -836,7 +862,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -920,7 +945,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-create-class-features-plugin": "^7.27.1", @@ -954,7 +978,6 @@ "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.0.tgz", "integrity": "sha512-IjM1IoJNw72AZFlj33Cu8X0q2XK/6AaVC3jQu+cgQ5lThWD5ajnuUAml80dqRmOhmPkTH8uAwnpMu9Rvj0LTRA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", @@ -1257,7 +1280,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-transforms": "^7.27.1", @@ -1343,7 +1365,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -1428,7 +1449,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz", "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", @@ -1650,7 +1670,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -1699,7 +1718,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -1731,7 +1749,6 @@ "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.0.tgz", "integrity": "sha512-4AEiDEBPIZvLQaWlc9liCavE0xRM0dNca41WtBeM3jgFptfUOSG9z0uteLhq6+3rq+WB6jIvUwKDTpXEHPJ2Vg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", @@ -1784,7 +1801,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", @@ -1914,6 +1930,25 @@ "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" } }, + "node_modules/@babel/preset-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz", + "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/runtime": { "version": "7.27.6", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz", @@ -1991,9 +2026,21 @@ "version": "0.2.3", "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true, + "devOptional": true, "license": "MIT" }, + "node_modules/@egjs/hammerjs": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz", + "integrity": "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==", + "license": "MIT", + "dependencies": { + "@types/hammerjs": "^2.0.36" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", @@ -2173,6 +2220,102 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@isaacs/ttlcache": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", @@ -2294,7 +2437,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", @@ -2312,7 +2455,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jest/console": "^29.7.0", @@ -2360,7 +2503,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=10" @@ -2373,7 +2516,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jest/schemas": "^29.6.3", @@ -2388,7 +2531,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@jest/create-cache-key-function": { @@ -2403,6 +2546,15 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/@jest/diff-sequences": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", + "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==", + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@jest/environment": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", @@ -2422,7 +2574,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "expect": "^29.7.0", @@ -2436,7 +2588,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "jest-get-type": "^29.6.3" @@ -2462,11 +2614,20 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/@jest/get-type": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.0.1.tgz", + "integrity": "sha512-AyYdemXCptSRFirI5EPazNxyPwAL0jXt3zceFjaj8NFiKP9pOi0bfXonf6qkf82z2t3QWPeLCWWw4stPBzctLw==", + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@jest/globals": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jest/environment": "^29.7.0", @@ -2482,7 +2643,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^0.2.3", @@ -2538,7 +2699,7 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.18", @@ -2553,7 +2714,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jest/console": "^29.7.0", @@ -2569,7 +2730,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jest/test-result": "^29.7.0", @@ -2669,6 +2830,57 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@jsamr/counter-style": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@jsamr/counter-style/-/counter-style-2.0.2.tgz", + "integrity": "sha512-2mXudGVtSzVxWEA7B9jZLKjoXUeUFYDDtFrQoC0IFX9/Dszz4t1vZOmafi3JSw/FxD+udMQ+4TAFR8Qs0J3URQ==", + "license": "MIT" + }, + "node_modules/@jsamr/react-native-li": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@jsamr/react-native-li/-/react-native-li-2.3.1.tgz", + "integrity": "sha512-Qbo4NEj48SQ4k8FZJHFE2fgZDKTWaUGmVxcIQh3msg5JezLdTMMHuRRDYctfdHI6L0FZGObmEv3haWbIvmol8w==", + "license": "MIT", + "peerDependencies": { + "@jsamr/counter-style": "^1.0.0 || ^2.0.0", + "react": "*", + "react-native": "*" + } + }, + "node_modules/@native-html/css-processor": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@native-html/css-processor/-/css-processor-1.11.0.tgz", + "integrity": "sha512-NnhBEbJX5M2gBGltPKOetiLlKhNf3OHdRafc8//e2ZQxXN8JaSW/Hy8cm94pnIckQxwaMKxrtaNT3x4ZcffoNQ==", + "license": "MIT", + "dependencies": { + "css-to-react-native": "^3.0.0", + "csstype": "^3.0.8" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-native": "*" + } + }, + "node_modules/@native-html/transient-render-engine": { + "version": "11.2.3", + "resolved": "https://registry.npmjs.org/@native-html/transient-render-engine/-/transient-render-engine-11.2.3.tgz", + "integrity": "sha512-zXwgA3gPUEmFs3I3syfnvDvS6WiUHXEE6jY09OBzK+trq7wkweOSFWIoyXiGkbXrozGYG0KY90YgPyr8Tg8Uyg==", + "license": "MIT", + "dependencies": { + "@native-html/css-processor": "1.11.0", + "@types/ramda": "^0.27.44", + "csstype": "^3.0.9", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.2", + "domutils": "^2.8.0", + "htmlparser2": "^7.1.2", + "ramda": "^0.27.2" + }, + "peerDependencies": { + "@types/react-native": "*", + "react-native": "^*" + } + }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", @@ -2717,6 +2929,51 @@ "node": ">= 8" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@react-native-async-storage/async-storage": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-2.1.0.tgz", + "integrity": "sha512-eAGQGPTAuFNEoIQSB5j2Jh1zm5NPyBRTfjRMfCN0W1OakC5WIB5vsDyIQhUweKN9XOE2/V07lqTMGsL0dGXNkA==", + "license": "MIT", + "dependencies": { + "merge-options": "^3.0.4" + }, + "peerDependencies": { + "react-native": "^0.0.0-0 || >=0.65 <1.0" + } + }, + "node_modules/@react-native-clipboard/clipboard": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/@react-native-clipboard/clipboard/-/clipboard-1.16.3.tgz", + "integrity": "sha512-cMIcvoZKIrShzJHEaHbTAp458R9WOv0fB6UyC7Ek4Qk561Ow/DrzmmJmH/rAZg21Z6ixJ4YSdFDC14crqIBmCQ==", + "license": "MIT", + "workspaces": [ + "example" + ], + "peerDependencies": { + "react": ">= 16.9.0", + "react-native": ">= 0.61.5", + "react-native-macos": ">= 0.61.0", + "react-native-windows": ">= 0.61.0" + }, + "peerDependenciesMeta": { + "react-native-macos": { + "optional": true + }, + "react-native-windows": { + "optional": true + } + } + }, "node_modules/@react-native-community/cli": { "version": "18.0.0", "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-18.0.0.tgz", @@ -2950,6 +3207,15 @@ "node": ">=10" } }, + "node_modules/@react-native-community/netinfo": { + "version": "11.4.1", + "resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-11.4.1.tgz", + "integrity": "sha512-B0BYAkghz3Q2V09BF88RA601XursIEA111tnc2JOaN7axJWmNefmfjZqw/KdSxKZp7CZUuPpjBmz/WCR9uaHYg==", + "license": "MIT", + "peerDependencies": { + "react-native": ">=0.59" + } + }, "node_modules/@react-native/assets-registry": { "version": "0.79.0", "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.79.0.tgz", @@ -3284,6 +3550,171 @@ "dev": true, "license": "MIT" }, + "node_modules/@react-native/virtualized-lists": { + "version": "0.72.8", + "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.72.8.tgz", + "integrity": "sha512-J3Q4Bkuo99k7mu+jPS9gSUSgq+lLRSI/+ahXNwV92XgJ/8UgOTxu2LPwhJnBk/sQKxq7E8WkZBnBiozukQMqrw==", + "license": "MIT", + "peer": true, + "dependencies": { + "invariant": "^2.2.4", + "nullthrows": "^1.1.1" + }, + "peerDependencies": { + "react-native": "*" + } + }, + "node_modules/@react-navigation/bottom-tabs": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@react-navigation/bottom-tabs/-/bottom-tabs-7.4.4.tgz", + "integrity": "sha512-/YEBu/cZUgYAaNoSfUnqoRjpbt8NOsb5YvDiKVyTcOOAF1GTbUw6kRi+AGW1Sm16CqzabO/TF2RvN1RmPS9VHg==", + "license": "MIT", + "dependencies": { + "@react-navigation/elements": "^2.6.1", + "color": "^4.2.3" + }, + "peerDependencies": { + "@react-navigation/native": "^7.1.16", + "react": ">= 18.2.0", + "react-native": "*", + "react-native-safe-area-context": ">= 4.0.0", + "react-native-screens": ">= 4.0.0" + } + }, + "node_modules/@react-navigation/core": { + "version": "7.12.3", + "resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-7.12.3.tgz", + "integrity": "sha512-oEz5sL8KTYmCv8SQX1A4k75A7VzYadOCudp/ewOBqRXOmZdxDQA9JuN7baE9IVyaRW0QTVDy+N/Wnqx9F4aW6A==", + "license": "MIT", + "dependencies": { + "@react-navigation/routers": "^7.5.1", + "escape-string-regexp": "^4.0.0", + "nanoid": "^3.3.11", + "query-string": "^7.1.3", + "react-is": "^19.1.0", + "use-latest-callback": "^0.2.4", + "use-sync-external-store": "^1.5.0" + }, + "peerDependencies": { + "react": ">= 18.2.0" + } + }, + "node_modules/@react-navigation/core/node_modules/react-is": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.1.tgz", + "integrity": "sha512-tr41fA15Vn8p4X9ntI+yCyeGSf1TlYaY5vlTZfQmeLBrFo3psOPX6HhTDnFNL9uj3EhP0KAQ80cugCl4b4BERA==", + "license": "MIT" + }, + "node_modules/@react-navigation/elements": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-2.6.1.tgz", + "integrity": "sha512-kVbIo+5FaqJv6MiYUR6nQHiw+10dmmH/P10C29wrH9S6fr7k69fImHGeiOI/h7SMDJ2vjWhftyEjqYO+c2LG/w==", + "license": "MIT", + "dependencies": { + "color": "^4.2.3", + "use-latest-callback": "^0.2.4", + "use-sync-external-store": "^1.5.0" + }, + "peerDependencies": { + "@react-native-masked-view/masked-view": ">= 0.2.0", + "@react-navigation/native": "^7.1.16", + "react": ">= 18.2.0", + "react-native": "*", + "react-native-safe-area-context": ">= 4.0.0" + }, + "peerDependenciesMeta": { + "@react-native-masked-view/masked-view": { + "optional": true + } + } + }, + "node_modules/@react-navigation/native": { + "version": "7.1.16", + "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.1.16.tgz", + "integrity": "sha512-JnnK81JYJ6PiMsuBEshPGHwfagRnH8W7SYdWNrPxQdNtakkHtG4u0O9FmrOnKiPl45DaftCcH1g+OVTFFgWa0Q==", + "license": "MIT", + "dependencies": { + "@react-navigation/core": "^7.12.3", + "escape-string-regexp": "^4.0.0", + "fast-deep-equal": "^3.1.3", + "nanoid": "^3.3.11", + "use-latest-callback": "^0.2.4" + }, + "peerDependencies": { + "react": ">= 18.2.0", + "react-native": "*" + } + }, + "node_modules/@react-navigation/native-stack": { + "version": "7.3.23", + "resolved": "https://registry.npmjs.org/@react-navigation/native-stack/-/native-stack-7.3.23.tgz", + "integrity": "sha512-WQBBnPrlM0vXj5YAFnJTyrkiCyANl2KnBV8ZmUG61HkqXFwuBbnHij6eoggXH1VZkEVRxW8k0E3qqfPtEZfUjQ==", + "license": "MIT", + "dependencies": { + "@react-navigation/elements": "^2.6.1", + "warn-once": "^0.1.1" + }, + "peerDependencies": { + "@react-navigation/native": "^7.1.16", + "react": ">= 18.2.0", + "react-native": "*", + "react-native-safe-area-context": ">= 4.0.0", + "react-native-screens": ">= 4.0.0" + } + }, + "node_modules/@react-navigation/routers": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-7.5.1.tgz", + "integrity": "sha512-pxipMW/iEBSUrjxz2cDD7fNwkqR4xoi0E/PcfTQGCcdJwLoaxzab5kSadBLj1MTJyT0YRrOXL9umHpXtp+Dv4w==", + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11" + } + }, + "node_modules/@react-navigation/stack": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@react-navigation/stack/-/stack-7.4.4.tgz", + "integrity": "sha512-2TjeTRCjE6W8OgbZ3dt88FwESVqbZLOQkVijNdj0xSQ67awDwNmunyp5vsvNsIlXUNsc21w/iddr25euK0YwkA==", + "license": "MIT", + "dependencies": { + "@react-navigation/elements": "^2.6.1", + "color": "^4.2.3" + }, + "peerDependencies": { + "@react-navigation/native": "^7.1.16", + "react": ">= 18.2.0", + "react-native": "*", + "react-native-gesture-handler": ">= 2.0.0", + "react-native-safe-area-context": ">= 4.0.0", + "react-native-screens": ">= 4.0.0" + } + }, + "node_modules/@reduxjs/toolkit": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.8.2.tgz", + "integrity": "sha512-MYlOhQ0sLdw4ud48FoC5w0dH9VfWQjtCjreKwYTT3l+r427qYC5Y8PihNutepr8XrNaBUDQo9khWUwQxZaqt5A==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", + "immer": "^10.0.3", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, "node_modules/@sideway/address": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", @@ -3332,6 +3763,124 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "license": "MIT" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, + "node_modules/@testing-library/react-native": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/@testing-library/react-native/-/react-native-13.2.1.tgz", + "integrity": "sha512-gryBmOI/PFvEJmMy6CNruulxXTW3zs7YyZHJEUlVYEt9vWo5v09FvV0RlYHurxs3yx3ZSc0vOsQOhld5a5SfPg==", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "jest-matcher-utils": "^30.0.2", + "pretty-format": "^30.0.2", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "jest": ">=29.0.0", + "react": ">=18.2.0", + "react-native": ">=0.71", + "react-test-renderer": ">=18.2.0" + }, + "peerDependenciesMeta": { + "jest": { + "optional": true + } + } + }, + "node_modules/@testing-library/react-native/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@testing-library/react-native/node_modules/@sinclair/typebox": { + "version": "0.34.38", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.38.tgz", + "integrity": "sha512-HpkxMmc2XmZKhvaKIZZThlHmx1L0I/V1hWK1NubtlFnr6ZqdiOpV72TKudZUNQjZNsyDBay72qFEhEvb+bcwcA==", + "license": "MIT" + }, + "node_modules/@testing-library/react-native/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@testing-library/react-native/node_modules/jest-diff": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.0.5.tgz", + "integrity": "sha512-1UIqE9PoEKaHcIKvq2vbibrCog4Y8G0zmOxgQUVEiTqwR5hJVMCoDsN1vFvI5JvwD37hjueZ1C4l2FyGnfpE0A==", + "license": "MIT", + "dependencies": { + "@jest/diff-sequences": "30.0.1", + "@jest/get-type": "30.0.1", + "chalk": "^4.1.2", + "pretty-format": "30.0.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@testing-library/react-native/node_modules/jest-matcher-utils": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.0.5.tgz", + "integrity": "sha512-uQgGWt7GOrRLP1P7IwNWwK1WAQbq+m//ZY0yXygyfWp0rJlksMSLQAA4wYQC3b6wl3zfnchyTx+k3HZ5aPtCbQ==", + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.0.1", + "chalk": "^4.1.2", + "jest-diff": "30.0.5", + "pretty-format": "30.0.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@testing-library/react-native/node_modules/pretty-format": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.5.tgz", + "integrity": "sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw==", + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@testing-library/react-native/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -3382,6 +3931,12 @@ "@types/node": "*" } }, + "node_modules/@types/hammerjs": { + "version": "2.0.46", + "resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.46.tgz", + "integrity": "sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==", + "license": "MIT" + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -3468,16 +4023,35 @@ "undici-types": "~7.8.0" } }, + "node_modules/@types/ramda": { + "version": "0.27.66", + "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.27.66.tgz", + "integrity": "sha512-i2YW+E2U6NfMt3dp0RxNcejox+bxJUNDjB7BpYuRuoHIzv5juPHkJkNgcUOu+YSQEmaWu8cnAo/8r63C0NnuVA==", + "license": "MIT", + "dependencies": { + "ts-toolbelt": "^6.15.1" + } + }, "node_modules/@types/react": { "version": "19.1.8", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz", "integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==", - "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.0.2" } }, + "node_modules/@types/react-native": { + "version": "0.72.8", + "resolved": "https://registry.npmjs.org/@types/react-native/-/react-native-0.72.8.tgz", + "integrity": "sha512-St6xA7+EoHN5mEYfdWnfYt0e8u6k2FR0P9s2arYgakQGFgU1f9FlPrIEcj0X24pLCF5c5i3WVuLCUdiCYHmOoA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@react-native/virtualized-lists": "^0.72.4", + "@types/react": "*" + } + }, "node_modules/@types/react-test-renderer": { "version": "19.1.0", "resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-19.1.0.tgz", @@ -3501,6 +4075,18 @@ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "license": "MIT" }, + "node_modules/@types/urijs": { + "version": "1.19.25", + "resolved": "https://registry.npmjs.org/@types/urijs/-/urijs-1.19.25.tgz", + "integrity": "sha512-XOfUup9r3Y06nFAZh3WvO0rBU4OtlfPB/vgxpjg+NRdGU6CN6djdc6OEiH+PcqHCY6eFLo9Ista73uarf4gnBg==", + "license": "MIT" + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, "node_modules/@types/yargs": { "version": "17.0.33", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", @@ -3841,7 +4427,7 @@ "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "type-fest": "^0.21.3" @@ -3857,7 +4443,7 @@ "version": "0.21.3", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, + "devOptional": true, "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" @@ -3938,6 +4524,15 @@ "node": ">= 8" } }, + "node_modules/apisauce": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/apisauce/-/apisauce-3.2.0.tgz", + "integrity": "sha512-uEvNyBl86g9znFzb5DsBN0kaC9cs9Seo6Ztippf1lJgMEj/mcwE7YMsv4NZeKHjpxGOQ4AHDPItnDRiEgNIdDA==", + "license": "MIT", + "dependencies": { + "axios": "^1.10.0" + } + }, "node_modules/appdirsjs": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/appdirsjs/-/appdirsjs-1.2.7.tgz", @@ -4132,6 +4727,12 @@ "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", "license": "MIT" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -4148,6 +4749,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/axios": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", + "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/babel-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", @@ -4325,6 +4937,11 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, + "node_modules/base-64": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz", + "integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==" + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -4399,11 +5016,16 @@ "devOptional": true, "license": "MIT" }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, "node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -4526,7 +5148,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "devOptional": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -4605,6 +5226,15 @@ "node": ">=6" } }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001727", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz", @@ -4645,12 +5275,32 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=10" } }, + "node_modules/character-entities-html4": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-1.1.4.tgz", + "integrity": "sha512-HRcDxZuZqMx3/a+qrzxdBKBPUpxWEq9xw2OPZ3a/174ihfrQKVsFhqtthBInFy1zZ9GgZyFXOatNujm8M+El3g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chrome-launcher": { "version": "0.15.2", "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", @@ -4726,7 +5376,7 @@ "version": "1.4.3", "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/cli-cursor": { @@ -4783,7 +5433,7 @@ "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "iojs": ">= 1.0.0", @@ -4794,9 +5444,22 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", - "dev": true, + "devOptional": true, "license": "MIT" }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -4815,6 +5478,16 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, "node_modules/colorette": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", @@ -4822,6 +5495,18 @@ "devOptional": true, "license": "MIT" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/command-exists": { "version": "1.2.9", "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", @@ -4985,7 +5670,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", @@ -5007,7 +5692,6 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "devOptional": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -5018,11 +5702,126 @@ "node": ">= 8" } }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "license": "ISC", + "engines": { + "node": ">=4" + } + }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-select/node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/css-select/node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/css-select/node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/css-select/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "license": "MIT", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, + "node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "devOptional": true, "license": "MIT" }, "node_modules/data-view-buffer": { @@ -5113,11 +5912,20 @@ "node": ">=0.10.0" } }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, "node_modules/dedent": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==", - "dev": true, + "devOptional": true, "license": "MIT", "peerDependencies": { "babel-plugin-macros": "^3.1.0" @@ -5194,6 +6002,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -5217,7 +6034,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -5227,7 +6044,7 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -5259,11 +6076,65 @@ "node": ">=6.0.0" } }, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "devOptional": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -5274,6 +6145,12 @@ "node": ">= 0.4" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -5290,7 +6167,7 @@ "version": "0.13.1", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=12" @@ -5314,6 +6191,15 @@ "node": ">= 0.8" } }, + "node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/env-paths": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", @@ -5442,7 +6328,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5452,7 +6337,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5490,7 +6374,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "devOptional": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -5503,7 +6386,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -6193,7 +7075,7 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">= 0.8.0" } @@ -6202,7 +7084,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jest/expect-utils": "^29.7.0", @@ -6225,7 +7107,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, "license": "MIT" }, "node_modules/fast-glob": { @@ -6334,6 +7215,15 @@ "node": ">=8" } }, + "node_modules/filter-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", + "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/finalhandler": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", @@ -6424,6 +7314,26 @@ "integrity": "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==", "license": "MIT" }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", @@ -6440,6 +7350,50 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -6488,7 +7442,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "devOptional": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6547,7 +7500,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "devOptional": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -6581,7 +7533,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "devOptional": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -6736,7 +7687,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -6813,7 +7763,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -6826,7 +7775,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -6842,7 +7790,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "devOptional": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -6866,13 +7813,59 @@ "hermes-estree": "0.25.1" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, + "devOptional": true, "license": "MIT" }, + "node_modules/htmlparser2": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.2.0.tgz", + "integrity": "sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.2", + "domutils": "^2.8.0", + "entities": "^3.0.1" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", + "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -6980,6 +7973,16 @@ "node": ">=16.x" } }, + "node_modules/immer": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", + "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -7001,7 +8004,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "pkg-dir": "^4.2.0", @@ -7026,6 +8029,15 @@ "node": ">=0.8.19" } }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -7161,7 +8173,7 @@ "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -7272,7 +8284,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6" @@ -7382,6 +8394,15 @@ "node": ">=8" } }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -7574,7 +8595,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "devOptional": true, "license": "ISC" }, "node_modules/istanbul-lib-coverage": { @@ -7590,7 +8610,7 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", - "dev": true, + "devOptional": true, "license": "BSD-3-Clause", "dependencies": { "@babel/core": "^7.23.9", @@ -7607,7 +8627,7 @@ "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, + "devOptional": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -7620,7 +8640,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, + "devOptional": true, "license": "BSD-3-Clause", "dependencies": { "istanbul-lib-coverage": "^3.0.0", @@ -7635,7 +8655,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, + "devOptional": true, "license": "BSD-3-Clause", "dependencies": { "debug": "^4.1.1", @@ -7650,7 +8670,7 @@ "version": "3.1.7", "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", - "dev": true, + "devOptional": true, "license": "BSD-3-Clause", "dependencies": { "html-escaper": "^2.0.0", @@ -7678,11 +8698,26 @@ "node": ">= 0.4" } }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jest/core": "^29.7.0", @@ -7709,7 +8744,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "execa": "^5.0.0", @@ -7724,7 +8759,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jest/environment": "^29.7.0", @@ -7756,7 +8791,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=10" @@ -7769,7 +8804,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jest/schemas": "^29.6.3", @@ -7784,14 +8819,14 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/jest-cli": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jest/core": "^29.7.0", @@ -7825,7 +8860,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/core": "^7.11.6", @@ -7871,7 +8906,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=10" @@ -7884,7 +8919,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jest/schemas": "^29.6.3", @@ -7899,14 +8934,14 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/jest-diff": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "chalk": "^4.0.0", @@ -7922,7 +8957,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=10" @@ -7935,7 +8970,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jest/schemas": "^29.6.3", @@ -7950,14 +8985,14 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/jest-docblock": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "detect-newline": "^3.0.0" @@ -7970,7 +9005,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", @@ -7987,7 +9022,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=10" @@ -8000,7 +9035,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jest/schemas": "^29.6.3", @@ -8015,7 +9050,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/jest-environment-node": { @@ -8073,7 +9108,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "jest-get-type": "^29.6.3", @@ -8087,7 +9122,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=10" @@ -8100,7 +9135,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jest/schemas": "^29.6.3", @@ -8115,14 +9150,14 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/jest-matcher-utils": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "chalk": "^4.0.0", @@ -8138,7 +9173,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=10" @@ -8151,7 +9186,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jest/schemas": "^29.6.3", @@ -8166,7 +9201,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/jest-message-util": { @@ -8239,7 +9274,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6" @@ -8266,7 +9301,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "chalk": "^4.0.0", @@ -8287,7 +9322,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "jest-regex-util": "^29.6.3", @@ -8301,7 +9336,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jest/console": "^29.7.0", @@ -8334,7 +9369,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jest/environment": "^29.7.0", @@ -8368,7 +9403,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/core": "^7.11.6", @@ -8400,7 +9435,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=10" @@ -8413,7 +9448,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jest/schemas": "^29.6.3", @@ -8428,14 +9463,14 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/jest-snapshot/node_modules/semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, + "devOptional": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -8526,7 +9561,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jest/test-result": "^29.7.0", @@ -8801,7 +9836,6 @@ "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true, "license": "MIT" }, "node_modules/lodash.debounce": { @@ -8995,6 +10029,26 @@ "loose-envify": "cli.js" } }, + "node_modules/lottie-react-native": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/lottie-react-native/-/lottie-react-native-7.2.5.tgz", + "integrity": "sha512-S90gdsQ71PCG9r2OW01guA2mlHAiWDrYQ0acLa6mzf4q8y8RTlug3cL/eFHKxkmSgyy+unEBdPUcii+3YNWktA==", + "license": "Apache-2.0", + "peerDependencies": { + "@lottiefiles/dotlottie-react": "^0.6.5", + "react": "*", + "react-native": ">=0.46", + "react-native-windows": ">=0.63.x" + }, + "peerDependenciesMeta": { + "@lottiefiles/dotlottie-react": { + "optional": true + }, + "react-native-windows": { + "optional": true + } + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -9008,7 +10062,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "semver": "^7.5.3" @@ -9024,7 +10078,7 @@ "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, + "devOptional": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -9052,12 +10106,17 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.4" } }, + "node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "license": "CC0-1.0" + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -9074,6 +10133,18 @@ "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", "license": "MIT" }, + "node_modules/merge-options": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz", + "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==", + "license": "MIT", + "dependencies": { + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -9582,11 +10653,19 @@ "node": ">=6" } }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -9598,6 +10677,15 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -9616,11 +10704,29 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/negotiator": { @@ -9691,6 +10797,18 @@ "node": ">=8" } }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, "node_modules/nullthrows": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", @@ -9713,7 +10831,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -9978,6 +11095,12 @@ "node": ">=6" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -10041,7 +11164,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -10051,9 +11173,31 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, + "devOptional": true, "license": "MIT" }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -10064,6 +11208,15 @@ "node": ">=8" } }, + "node_modules/paths-js": { + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/paths-js/-/paths-js-0.4.11.tgz", + "integrity": "sha512-3mqcLomDBXOo7Fo+UlaenG6f71bk1ZezPQy2JCmYHy2W2k5VKpP+Jbin9H0bjXynelTbglCqdFhSEkeIkKTYUA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.11.0" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -10095,7 +11248,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "find-up": "^4.0.0" @@ -10108,7 +11261,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "locate-path": "^5.0.0", @@ -10122,7 +11275,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "p-locate": "^4.1.0" @@ -10135,7 +11288,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "p-try": "^2.0.0" @@ -10151,7 +11304,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "p-limit": "^2.2.0" @@ -10160,6 +11313,12 @@ "node": ">=8" } }, + "node_modules/point-in-polygon": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/point-in-polygon/-/point-in-polygon-1.1.0.tgz", + "integrity": "sha512-3ojrFwjnnw8Q9242TzgXuTD+eKiutbzyslcq1ydfu82Db2y+Ogbmyrkpv0Hgj31qwT3lbS9+QAAO/pIQM35XRw==", + "license": "MIT" + }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -10170,6 +11329,12 @@ "node": ">= 0.4" } }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -10266,7 +11431,6 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.4.0", @@ -10278,7 +11442,12 @@ "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true, + "license": "MIT" + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "license": "MIT" }, "node_modules/punycode": { @@ -10295,7 +11464,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", - "dev": true, + "devOptional": true, "funding": [ { "type": "individual", @@ -10324,6 +11493,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/query-string": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz", + "integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==", + "license": "MIT", + "dependencies": { + "decode-uri-component": "^0.2.2", + "filter-obj": "^1.1.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/queue": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", @@ -10354,6 +11541,12 @@ ], "license": "MIT" }, + "node_modules/ramda": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.2.tgz", + "integrity": "sha512-SbiLPU40JuJniHexQSAgad32hfwd+DRUdwF2PlVuI5RZD0/vahUco7R8vD86J/tcEKKF9vZrUVwgtmGCqlCKyA==", + "license": "MIT" + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -10419,6 +11612,18 @@ } } }, + "node_modules/react-freeze": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/react-freeze/-/react-freeze-1.0.4.tgz", + "integrity": "sha512-r4F0Sec0BLxWicc7HEyo2x3/2icUTrRmDjaaRyzzn+7aDyFZliszMDOgLVwSnQnYENOlL1o569Ze2HZefk8clA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=17.0.0" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -10485,6 +11690,369 @@ } } }, + "node_modules/react-native-biometrics": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/react-native-biometrics/-/react-native-biometrics-3.0.1.tgz", + "integrity": "sha512-Ru80gXRa9KG04sl5AB9HyjLjVbduhqZVjA+AiOSGqr+fNqCDmCu9y5WEksnjbnniNLmq1yGcw+qcLXmR1ddLDQ==", + "license": "MIT", + "peerDependencies": { + "react-native": ">=0.60.0" + } + }, + "node_modules/react-native-blob-util": { + "version": "0.22.2", + "resolved": "https://registry.npmjs.org/react-native-blob-util/-/react-native-blob-util-0.22.2.tgz", + "integrity": "sha512-Czx01QMg7aLsm/4F/7+eqoRAi1q/qjLY2Kao16g+n2SRnTH1+qkD8Qhx2q9okB+VNQvZKB1LbiXhktzYQV52xQ==", + "license": "MIT", + "dependencies": { + "base-64": "0.1.0", + "glob": "^10.3.10" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native-blob-util/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/react-native-chart-kit": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/react-native-chart-kit/-/react-native-chart-kit-6.12.0.tgz", + "integrity": "sha512-nZLGyCFzZ7zmX0KjYeeSV1HKuPhl1wOMlTAqa0JhlyW62qV/1ZPXHgT8o9s8mkFaGxdqbspOeuaa6I9jUQDgnA==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.13", + "paths-js": "^0.4.10", + "point-in-polygon": "^1.0.1" + }, + "peerDependencies": { + "react": "> 16.7.0", + "react-native": ">= 0.50.0", + "react-native-svg": "> 6.4.1" + } + }, + "node_modules/react-native-config": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/react-native-config/-/react-native-config-1.5.5.tgz", + "integrity": "sha512-dGdLnBU0cd5xL5bF0ROTmHYbsstZnQKOEPfglvZi1vStvAjpld14X25K6mY3KGPTMWAzx6TbjKeq5dR+ILuMMA==", + "license": "MIT", + "peerDependencies": { + "react-native-windows": ">=0.61" + }, + "peerDependenciesMeta": { + "react-native-windows": { + "optional": true + } + } + }, + "node_modules/react-native-device-info": { + "version": "10.14.0", + "resolved": "https://registry.npmjs.org/react-native-device-info/-/react-native-device-info-10.14.0.tgz", + "integrity": "sha512-9NnTGfhEU4UgQtz4p6COk2Gbqly0dpSWrJtp+dw5rNAi96KtYbaNnO5yoOHDlJ1SVIzh8+hFu3WxVbnWkFU9gA==", + "license": "MIT", + "peerDependencies": { + "react-native": "*" + } + }, + "node_modules/react-native-element-dropdown": { + "version": "2.12.4", + "resolved": "https://registry.npmjs.org/react-native-element-dropdown/-/react-native-element-dropdown-2.12.4.tgz", + "integrity": "sha512-abZc5SVji9FIt7fjojRYrbuvp03CoeZJrgvezQoDoSOrpiTqkX69ix5m+j06W2AVncA0VWvbT+vCMam8SoVadw==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.21" + }, + "engines": { + "node": ">= 16.0.0" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native-gesture-handler": { + "version": "2.27.2", + "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.27.2.tgz", + "integrity": "sha512-+kNaY2m7uQu5+5ls8os6z92DTk9expsEAYsaPv30n08mrqX2r64G8iVGDwNWzZcId54+P7RlDnhyszTql0sQ0w==", + "license": "MIT", + "dependencies": { + "@egjs/hammerjs": "^2.0.17", + "hoist-non-react-statics": "^3.3.0", + "invariant": "^2.2.4" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native-image-picker": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/react-native-image-picker/-/react-native-image-picker-7.2.3.tgz", + "integrity": "sha512-zKIZUlQNU3EtqizsXSH92zPeve4vpUrsqHu2kkpCxWE9TZhJFZBb+irDsBOY8J21k0+Edgt06TMQGJ+iPUIXyA==", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native-is-edge-to-edge": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.1.7.tgz", + "integrity": "sha512-EH6i7E8epJGIcu7KpfXYXiV2JFIYITtq+rVS8uEb+92naMRBdxhTuS8Wn2Q7j9sqyO0B+Xbaaf9VdipIAmGW4w==", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native-keychain": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/react-native-keychain/-/react-native-keychain-10.0.0.tgz", + "integrity": "sha512-YzPKSAnSzGEJ12IK6CctNLU79T1W15WDrElRQ+1/FsOazGX9ucFPTQwgYe8Dy8jiSEDJKM4wkVa3g4lD2Z+Pnw==", + "license": "MIT", + "workspaces": [ + "KeychainExample", + "website" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/react-native-linear-gradient": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/react-native-linear-gradient/-/react-native-linear-gradient-2.8.3.tgz", + "integrity": "sha512-KflAXZcEg54PXkLyflaSZQ3PJp4uC4whM7nT/Uot9m0e/qxFV3p6uor1983D1YOBJbJN7rrWdqIjq0T42jOJyA==", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native-permissions": { + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/react-native-permissions/-/react-native-permissions-5.4.2.tgz", + "integrity": "sha512-XNMoG1fxrB9q73MLn/ZfTaP7pS8qPu0KWypbeFKVTvoR+JJ3O7uedMOTH/mts9bTG+GKhShOoZ+k0CR63q9jwA==", + "license": "MIT", + "peerDependencies": { + "react": ">=18.1.0", + "react-native": ">=0.70.0", + "react-native-windows": ">=0.70.0" + }, + "peerDependenciesMeta": { + "react-native-windows": { + "optional": true + } + } + }, + "node_modules/react-native-raw-bottom-sheet": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/react-native-raw-bottom-sheet/-/react-native-raw-bottom-sheet-3.0.0.tgz", + "integrity": "sha512-kHR7j2ExCLqf/AO3MECozMJXi48O1+YxUYSRgRo/5Ftm7mEcrxJEzvjqMmqUbVhhKlfk5hLCGFnEQ5Z9OHCUtg==", + "license": "MIT" + }, + "node_modules/react-native-reanimated": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.19.0.tgz", + "integrity": "sha512-FNfqLuPuVHsW9KcsZtnJqIPlMQvuySnSFJXgSt9fVDPqptbSUkiAF6MthUwd4Mxt05hCRcbV+T65CENgVS5iCg==", + "license": "MIT", + "dependencies": { + "@babel/plugin-transform-arrow-functions": "^7.0.0-0", + "@babel/plugin-transform-class-properties": "^7.0.0-0", + "@babel/plugin-transform-classes": "^7.0.0-0", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.0.0-0", + "@babel/plugin-transform-optional-chaining": "^7.0.0-0", + "@babel/plugin-transform-shorthand-properties": "^7.0.0-0", + "@babel/plugin-transform-template-literals": "^7.0.0-0", + "@babel/plugin-transform-unicode-regex": "^7.0.0-0", + "@babel/preset-typescript": "^7.16.7", + "convert-source-map": "^2.0.0", + "invariant": "^2.2.4", + "react-native-is-edge-to-edge": "1.1.7" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0", + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native-render-html": { + "version": "6.3.4", + "resolved": "https://registry.npmjs.org/react-native-render-html/-/react-native-render-html-6.3.4.tgz", + "integrity": "sha512-H2jSMzZjidE+Wo3qCWPUMU1nm98Vs2SGCvQCz/i6xf0P3Y9uVtG/b0sDbG/cYFir2mSYBYCIlS1Dv0WC1LjYig==", + "license": "BSD-2-Clause", + "dependencies": { + "@jsamr/counter-style": "^2.0.1", + "@jsamr/react-native-li": "^2.3.0", + "@native-html/transient-render-engine": "11.2.3", + "@types/ramda": "^0.27.40", + "@types/urijs": "^1.19.15", + "prop-types": "^15.5.7", + "ramda": "^0.27.2", + "stringify-entities": "^3.1.0", + "urijs": "^1.19.6" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native-responsive-dimensions": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/react-native-responsive-dimensions/-/react-native-responsive-dimensions-3.1.1.tgz", + "integrity": "sha512-Vo2OhWsphq0HgKsmeZOeyW+c+vsFn1ZaCFkGDgdeCEEiLriT76jGA1JlUjtrj27hvyo/xzeTlBZ+vBso1A84fw==", + "license": "MIT", + "peerDependencies": { + "react-native": ">=0.44.1" + } + }, + "node_modules/react-native-safe-area-context": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.5.2.tgz", + "integrity": "sha512-t4YVbHa9uAGf+pHMabGrb0uHrD5ogAusSu842oikJ3YKXcYp6iB4PTGl0EZNkUIR3pCnw/CXKn42OCfhsS0JIw==", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native-screens": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.13.1.tgz", + "integrity": "sha512-EESsMAtyzYcL3gpAI2NKKiIo+Ew0fnX4P4b3Zy/+MTc6SJIo3foJbZwdIWd/SUBswOf7IYCvWBppg+D8tbwnsw==", + "license": "MIT", + "dependencies": { + "react-freeze": "^1.0.0", + "react-native-is-edge-to-edge": "^1.2.1", + "warn-once": "^0.1.0" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native-screens/node_modules/react-native-is-edge-to-edge": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.2.1.tgz", + "integrity": "sha512-FLbPWl/MyYQWz+KwqOZsSyj2JmLKglHatd3xLZWskXOpRaio4LfEDEz8E/A6uD8QoTHW6Aobw1jbEwK7KMgR7Q==", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native-share": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/react-native-share/-/react-native-share-12.1.1.tgz", + "integrity": "sha512-6nCdK2NYnvbsjMs9XEjnM0tCUF7AzoauxosL7mvBXSJWpjAfATZTJAMR7y5bjni0zDRJlI1bg2xt+0R1cF+sWA==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/react-native-svg": { + "version": "15.12.0", + "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.12.0.tgz", + "integrity": "sha512-iE25PxIJ6V0C6krReLquVw6R0QTsRTmEQc4K2Co3P6zsimU/jltcDBKYDy1h/5j9S/fqmMeXnpM+9LEWKJKI6A==", + "license": "MIT", + "dependencies": { + "css-select": "^5.1.0", + "css-tree": "^1.1.3", + "warn-once": "0.1.1" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native-toast-message": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/react-native-toast-message/-/react-native-toast-message-2.3.3.tgz", + "integrity": "sha512-4IIUHwUPvKHu4gjD0Vj2aGQzqPATiblL1ey8tOqsxOWRPGGu52iIbL8M/mCz4uyqecvPdIcMY38AfwRuUADfQQ==", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native-tts": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/react-native-tts/-/react-native-tts-4.1.1.tgz", + "integrity": "sha512-VL0TgCwkUWggbbFGIXAPKC3rM1baluAYtgOdgnaTm7UYsWf/y8n5VgmVB0J2Wa8qt1dldZ1cSsdQY9iz3evcAg==", + "license": "MIT" + }, + "node_modules/react-native-vector-icons": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/react-native-vector-icons/-/react-native-vector-icons-10.3.0.tgz", + "integrity": "sha512-IFQ0RE57819hOUdFvgK4FowM5aMXg7C7XKsuGLevqXkkIJatc3QopN0wYrb2IrzUgmdpfP+QVIbI3S6h7M0btw==", + "deprecated": "react-native-vector-icons package has moved to a new model of per-icon-family packages. See the https://github.com/oblador/react-native-vector-icons/blob/master/MIGRATION.md on how to migrate", + "license": "MIT", + "dependencies": { + "prop-types": "^15.7.2", + "yargs": "^16.1.1" + }, + "bin": { + "fa-upgrade.sh": "bin/fa-upgrade.sh", + "fa5-upgrade": "bin/fa5-upgrade.sh", + "fa6-upgrade": "bin/fa6-upgrade.sh", + "generate-icon": "bin/generate-icon.js" + } + }, + "node_modules/react-native-vector-icons/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/react-native-vector-icons/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/react-native-vector-icons/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/react-native/node_modules/@react-native/virtualized-lists": { "version": "0.79.0", "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.79.0.tgz", @@ -10561,6 +12129,29 @@ "node": ">=10" } }, + "node_modules/react-redux": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", + "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, "node_modules/react-refresh": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", @@ -10574,7 +12165,6 @@ "version": "19.0.0", "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-19.0.0.tgz", "integrity": "sha512-oX5u9rOQlHzqrE/64CNr0HB0uWxkCQmZNSfozlYvwE71TLVgeZxVf0IjouGEr1v7r1kcDifdAJBeOhdhxsG/DA==", - "dev": true, "license": "MIT", "dependencies": { "react-is": "^19.0.0", @@ -10588,7 +12178,6 @@ "version": "19.1.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.0.tgz", "integrity": "sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==", - "dev": true, "license": "MIT" }, "node_modules/readable-stream": { @@ -10606,6 +12195,43 @@ "node": ">= 6" } }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/redux-persist": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/redux-persist/-/redux-persist-6.0.0.tgz", + "integrity": "sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==", + "license": "MIT", + "peerDependencies": { + "redux": ">4.0.0" + } + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -10633,14 +12259,12 @@ "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "dev": true, "license": "MIT" }, "node_modules/regenerate-unicode-properties": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", - "dev": true, "license": "MIT", "dependencies": { "regenerate": "^1.4.2" @@ -10680,7 +12304,6 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", - "dev": true, "license": "MIT", "dependencies": { "regenerate": "^1.4.2", @@ -10698,14 +12321,12 @@ "version": "0.8.0", "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", - "dev": true, "license": "MIT" }, "node_modules/regjsparser": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "jsesc": "~3.0.2" @@ -10718,7 +12339,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", - "dev": true, "license": "MIT", "bin": { "jsesc": "bin/jsesc" @@ -10743,11 +12363,17 @@ "devOptional": true, "license": "ISC" }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT" + }, "node_modules/resolve": { "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "is-core-module": "^2.16.0", @@ -10768,7 +12394,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "resolve-from": "^5.0.0" @@ -10781,7 +12407,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -10801,7 +12427,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=10" @@ -11129,7 +12755,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "devOptional": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -11142,7 +12767,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -11242,6 +12866,21 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "license": "ISC" }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/simple-swizzle/node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT" + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -11316,13 +12955,22 @@ "version": "0.5.13", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, + "node_modules/split-on-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -11400,6 +13048,15 @@ "node": ">= 0.4" } }, + "node_modules/strict-uri-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -11414,7 +13071,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "char-regex": "^1.0.2", @@ -11445,6 +13102,30 @@ "node": ">=8" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/string-width/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -11552,6 +13233,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/stringify-entities": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-3.1.0.tgz", + "integrity": "sha512-3FP+jGMmMV/ffZs86MoghGqAoqXAdxLrJP4GUdrDN1aIScYih5tuIO3eF4To5AJZ79KDZ8Fpdy7QJnK8SsL1Vg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "xtend": "^4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -11564,11 +13260,24 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -11584,11 +13293,23 @@ "node": ">=6" } }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -11626,7 +13347,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -11758,6 +13479,12 @@ "typescript": ">=4.2.0" } }, + "node_modules/ts-toolbelt": { + "version": "6.15.5", + "resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-6.15.5.tgz", + "integrity": "sha512-FZIXf1ksVyLcfr7M317jbB67XFJhOO1YqdTcuGaq9q5jLUoTikukZ+98TPjKiP2jC5CgmYdWWYs0s2nLSU0/1A==", + "license": "Apache-2.0" + }, "node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -11951,7 +13678,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -11961,7 +13687,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "dev": true, "license": "MIT", "dependencies": { "unicode-canonical-property-names-ecmascript": "^2.0.0", @@ -11975,7 +13700,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -11985,7 +13709,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -12050,6 +13773,30 @@ "punycode": "^2.1.0" } }, + "node_modules/urijs": { + "version": "1.19.11", + "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.11.tgz", + "integrity": "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==", + "license": "MIT" + }, + "node_modules/use-latest-callback": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/use-latest-callback/-/use-latest-callback-0.2.4.tgz", + "integrity": "sha512-LS2s2n1usUUnDq4oVh1ca6JFX9uSqUncTfAm44WMg0v6TxL7POUTk1B044NH8TeLkFbNajIsgDHcgNpNzZucdg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", + "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -12070,7 +13817,7 @@ "version": "9.3.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", @@ -12106,6 +13853,12 @@ "makeerror": "1.0.12" } }, + "node_modules/warn-once": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/warn-once/-/warn-once-0.1.1.tgz", + "integrity": "sha512-VkQZJbO8zVImzYFteBXvBOZEl1qL175WH8VmZcxF2fZAoudNhNDvHi+doCaAEdU2l2vtcIwa2zn0QK5+I1HQ3Q==", + "license": "MIT" + }, "node_modules/wcwidth": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", @@ -12126,7 +13879,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "devOptional": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -12261,6 +14013,24 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -12289,6 +14059,15 @@ "async-limiter": "~1.0.0" } }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index b03555e..e3ff13a 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,43 @@ "test": "jest" }, "dependencies": { + "@react-native-async-storage/async-storage": "^2.1.0", + "@react-native-clipboard/clipboard": "^1.16.1", + "@react-native-community/netinfo": "^11.4.1", + "@react-navigation/bottom-tabs": "^7.2.0", + "@react-navigation/native": "^7.0.14", + "@react-navigation/native-stack": "^7.2.0", + "@react-navigation/stack": "^7.1.1", + "@reduxjs/toolkit": "^2.5.1", + "@testing-library/react-native": "^13.2.0", + "apisauce": "^3.1.0", + "lottie-react-native": "^7.2.2", "react": "19.0.0", - "react-native": "0.79.0" + "react-native": "0.79.0", + "react-native-biometrics": "^3.0.1", + "react-native-blob-util": "^0.22.2", + "react-native-chart-kit": "^6.12.0", + "react-native-config": "^1.5.5", + "react-native-device-info": "^10.11.0", + "react-native-element-dropdown": "^2.12.4", + "react-native-gesture-handler": "^2.22.1", + "react-native-image-picker": "^7.2.3", + "react-native-keychain": "^10.0.0", + "react-native-linear-gradient": "^2.8.3", + "react-native-permissions": "^5.2.4", + "react-native-raw-bottom-sheet": "^3.0.0", + "react-native-reanimated": "^3.18.0", + "react-native-render-html": "^6.3.4", + "react-native-responsive-dimensions": "^3.1.1", + "react-native-safe-area-context": "^5.1.0", + "react-native-screens": "^4.5.0", + "react-native-share": "^12.0.9", + "react-native-svg": "^15.11.1", + "react-native-toast-message": "^2.2.1", + "react-native-tts": "^4.1.1", + "react-native-vector-icons": "^10.2.0", + "react-redux": "^9.2.0", + "redux-persist": "^6.0.0" }, "devDependencies": { "@babel/core": "^7.25.2", @@ -36,4 +71,4 @@ "engines": { "node": ">=18" } -} \ No newline at end of file +} diff --git a/react-native.config.js b/react-native.config.js new file mode 100644 index 0000000..a1ab960 --- /dev/null +++ b/react-native.config.js @@ -0,0 +1,7 @@ +module.exports = { + project: { + ios: {}, + android: {}, + }, + assets: ['./app/assets/fonts'], // adjust according to your path + }; \ No newline at end of file diff --git a/setup.bat b/setup.bat new file mode 100644 index 0000000..943e39f --- /dev/null +++ b/setup.bat @@ -0,0 +1,32 @@ +@echo off +echo ๐Ÿš€ Setting up NeoScan Physician App... + +REM Check if Node.js is installed +node --version >nul 2>&1 +if %errorlevel% neq 0 ( + echo โŒ Node.js is not installed. Please install Node.js ^>= 18 first. + pause + exit /b 1 +) + +echo โœ… Node.js version: +node --version + +REM Install dependencies +echo ๐Ÿ“ฆ Installing dependencies... +npm install + +if %errorlevel% neq 0 ( + echo โŒ Failed to install dependencies. + pause + exit /b 1 +) + +echo โœ… Setup completed successfully! +echo. +echo ๐ŸŽฏ Next steps: +echo 1. Start the development server: npm start +echo 2. Run on Android: npm run android +echo. +echo ๐Ÿ“ฑ The app will open with a login screen. Use any credentials to proceed to the dashboard. +pause \ No newline at end of file diff --git a/setup.sh b/setup.sh new file mode 100644 index 0000000..4b68cef --- /dev/null +++ b/setup.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +echo "๐Ÿš€ Setting up NeoScan Physician App..." + +# Check if Node.js is installed +if ! command -v node &> /dev/null; then + echo "โŒ Node.js is not installed. Please install Node.js >= 18 first." + exit 1 +fi + +# Check Node.js version +NODE_VERSION=$(node -v | cut -d'v' -f2 | cut -d'.' -f1) +if [ "$NODE_VERSION" -lt 18 ]; then + echo "โŒ Node.js version 18 or higher is required. Current version: $(node -v)" + exit 1 +fi + +echo "โœ… Node.js version: $(node -v)" + +# Install dependencies +echo "๐Ÿ“ฆ Installing dependencies..." +npm install + +# iOS setup (macOS only) +if [[ "$OSTYPE" == "darwin"* ]]; then + echo "๐ŸŽ Setting up iOS dependencies..." + cd ios + pod install + cd .. + echo "โœ… iOS setup completed" +else + echo "โ„น๏ธ Skipping iOS setup (not on macOS)" +fi + +echo "โœ… Setup completed successfully!" +echo "" +echo "๐ŸŽฏ Next steps:" +echo "1. Start the development server: npm start" +echo "2. Run on Android: npm run android" +echo "3. Run on iOS (macOS only): npm run ios" +echo "" +echo "๐Ÿ“ฑ The app will open with a login screen. Use any credentials to proceed to the dashboard." \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 304ab4e..ee46d0f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,3 +1,11 @@ { - "extends": "@react-native/typescript-config/tsconfig.json" + "extends": "@react-native/typescript-config/tsconfig.json", + "compilerOptions": { + "typeRoots": ["./app/types", "./node_modules/@types"] + }, + "include": [ + "**/*.ts", + "**/*.tsx", + "app/types/*.d.ts" + ] }