signup flow added for radiologist
This commit is contained in:
parent
df5616eaf5
commit
29940278e6
165
.cursor/rules/appflow.mdc
Normal file
165
.cursor/rules/appflow.mdc
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
---
|
||||||
|
description:
|
||||||
|
globs:
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
Radiologist_Rule1
|
||||||
|
1. RADIOLOGIST APP - DETAILED WORKFLOW
|
||||||
|
1.1 App Launch & Authentication
|
||||||
|
App Launch → Biometric Auth → Dashboard Loading
|
||||||
|
↓
|
||||||
|
Load Case Queue → Priority Sort → Display Cases
|
||||||
|
↓
|
||||||
|
Critical (RED) → Urgent (ORANGE) → Routine (GREEN)
|
||||||
|
1.2 Critical Finding Workflow (Acute Subdural Hemorrhage)
|
||||||
|
STEP 1: Alert Reception
|
||||||
|
Push Notification → "CRITICAL - Acute Subdural Hemorrhage"
|
||||||
|
↓
|
||||||
|
Tap Notification → App Opens → Case Preview
|
||||||
|
↓
|
||||||
|
Patient: John Doe, Age 45, Head Trauma
|
||||||
|
AI Confidence: 93%
|
||||||
|
Midline Shift: 7mm
|
||||||
|
|
||||||
|
STEP 2: Full Case Review
|
||||||
|
Case Preview → Open DICOM Viewer
|
||||||
|
↓
|
||||||
|
AI Overlay ON → Hemorrhage Highlighted
|
||||||
|
↓
|
||||||
|
Review Images → Confirm Finding → Add Observations
|
||||||
|
↓
|
||||||
|
"Large acute subdural hemorrhage with mass effect"
|
||||||
|
|
||||||
|
STEP 3: Report Generation
|
||||||
|
Voice-to-Text → "Acute subdural hemorrhage..."
|
||||||
|
↓
|
||||||
|
Review Text → Edit if needed → Submit Report
|
||||||
|
↓
|
||||||
|
Preliminary Report Sent (< 5 minutes)
|
||||||
|
|
||||||
|
STEP 4: System Learning
|
||||||
|
System Logs → Radiologist Confirmation
|
||||||
|
↓
|
||||||
|
Time Metrics Recorded → Model Improvement Data
|
||||||
|
1.3 Routine Scan Workflow (Negative Finding)
|
||||||
|
STEP 1: Queue Processing
|
||||||
|
Dashboard → Routine Queue → Select Case
|
||||||
|
↓
|
||||||
|
Patient: Jane Smith, Age 30, Headache
|
||||||
|
AI: No acute abnormalities (99% confidence)
|
||||||
|
|
||||||
|
STEP 2: Confirmation Review
|
||||||
|
DICOM Viewer → Quick Review → Confirm Negative
|
||||||
|
↓
|
||||||
|
"No acute intracranial abnormality"
|
||||||
|
|
||||||
|
STEP 3: Report & Documentation
|
||||||
|
Submit Report → System Logs Concordance
|
||||||
|
↓
|
||||||
|
Next Case in Queue
|
||||||
|
1.4 System Uncertainty Protocol
|
||||||
|
AI Confidence 70-85% → "REVIEW RECOMMENDED"
|
||||||
|
↓
|
||||||
|
Special Yellow Flag → Enhanced Attention Required
|
||||||
|
↓
|
||||||
|
Detailed Review → Feedback to System
|
||||||
|
|
||||||
|
3. RADIOLOGIST APP WIREFRAMES
|
||||||
|
3.1 Login Screen
|
||||||
|
+------------------------+
|
||||||
|
| [HOSPITAL LOGO] |
|
||||||
|
| |
|
||||||
|
| Radiologist Portal |
|
||||||
|
| |
|
||||||
|
| [👤 Dr. Smith] |
|
||||||
|
| [🔒 Biometric Login] |
|
||||||
|
| |
|
||||||
|
| [Face ID Icon] |
|
||||||
|
| Touch to Login |
|
||||||
|
| |
|
||||||
|
| OR |
|
||||||
|
| |
|
||||||
|
| PIN: [****] |
|
||||||
|
| [LOGIN] |
|
||||||
|
| |
|
||||||
|
| Emergency Access |
|
||||||
|
+------------------------+
|
||||||
|
3.2 Dashboard - Case Queue
|
||||||
|
+------------------------+
|
||||||
|
| Dr. Smith [🔔3][⚙️] |
|
||||||
|
| On-Call Status: ACTIVE |
|
||||||
|
| |
|
||||||
|
| CRITICAL CASES |
|
||||||
|
| 🔴 Bed 3 - Hemorrhage |
|
||||||
|
| 📱 2 min ago |
|
||||||
|
| AI: 93% confidence |
|
||||||
|
| [REVIEW NOW] |
|
||||||
|
| |
|
||||||
|
| 🔴 Bed 7 - Stroke |
|
||||||
|
| 📱 5 min ago |
|
||||||
|
| AI: 87% confidence |
|
||||||
|
| [REVIEW NOW] |
|
||||||
|
| |
|
||||||
|
| URGENT CASES (4) |
|
||||||
|
| 🟡 Show All |
|
||||||
|
| |
|
||||||
|
| ROUTINE CASES (12) |
|
||||||
|
| 🟢 Show All |
|
||||||
|
+------------------------+
|
||||||
|
3.3 Critical Case Details
|
||||||
|
+------------------------+
|
||||||
|
| ← CRITICAL CASE |
|
||||||
|
| |
|
||||||
|
| Patient: John Doe, 45M |
|
||||||
|
| Bed: 3 | Time: 14:35 |
|
||||||
|
| Clinical: Head trauma |
|
||||||
|
| |
|
||||||
|
| AI ANALYSIS: |
|
||||||
|
| 🔴 Acute Subdural |
|
||||||
|
| Confidence: 93% |
|
||||||
|
| Midline Shift: 7mm |
|
||||||
|
| Mass Effect: Present |
|
||||||
|
| |
|
||||||
|
| [VIEW DICOM IMAGES] |
|
||||||
|
| [VOICE REPORT] |
|
||||||
|
| [QUICK REPORT] |
|
||||||
|
| |
|
||||||
|
| Status: PENDING REVIEW |
|
||||||
|
+------------------------+
|
||||||
|
3.4 DICOM Viewer
|
||||||
|
+------------------------+
|
||||||
|
| ← John Doe - CT Brain |
|
||||||
|
| |
|
||||||
|
| [ CT SCAN IMAGE ]|
|
||||||
|
| [ WITH AI OVERLAY ]|
|
||||||
|
| [ HEMORRHAGE ]|
|
||||||
|
| [ HIGHLIGHTED ]|
|
||||||
|
| |
|
||||||
|
| Tools: [🔍][📏][✏️] |
|
||||||
|
| AI: [ON] Conf: 93% |
|
||||||
|
| |
|
||||||
|
| Measurements: |
|
||||||
|
| • Hemorrhage: 3.2cm |
|
||||||
|
| • Midline: 7mm shift |
|
||||||
|
| |
|
||||||
|
| [🎤 START REPORT] |
|
||||||
|
+------------------------+
|
||||||
|
3.5 Voice Report Interface
|
||||||
|
+------------------------+
|
||||||
|
| ← Voice Report |
|
||||||
|
| |
|
||||||
|
| [🎤 RECORDING 01:23] |
|
||||||
|
| |
|
||||||
|
| "There is a large |
|
||||||
|
| acute subdural |
|
||||||
|
| hemorrhage in the |
|
||||||
|
| right frontoparietal |
|
||||||
|
| region..." |
|
||||||
|
| |
|
||||||
|
| [PAUSE] [STOP] [PLAY] |
|
||||||
|
| |
|
||||||
|
| [SAVE DRAFT] |
|
||||||
|
| [SUBMIT REPORT] |
|
||||||
|
| |
|
||||||
|
| Estimated: 2 min left |
|
||||||
|
+------------------------+
|
||||||
34
.cursor/rules/filestructure.mdc
Normal file
34
.cursor/rules/filestructure.mdc
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
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.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.
|
||||||
@ -1,3 +0,0 @@
|
|||||||
---
|
|
||||||
alwaysApply: true
|
|
||||||
---
|
|
||||||
397
.cursor/rules/projectstructure.mdc
Normal file
397
.cursor/rules/projectstructure.mdc
Normal file
@ -0,0 +1,397 @@
|
|||||||
|
---
|
||||||
|
description:
|
||||||
|
globs:
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
Project Structure
|
||||||
|
🏥 RADIOLOGIST APP STRUCTURE
|
||||||
|
NeoScan_Radiologist/
|
||||||
|
│
|
||||||
|
├── app/
|
||||||
|
│ ├── modules/ # 🌐 Feature-wise modular architecture
|
||||||
|
│ │ ├── Auth/ # 🔐 Authentication Module
|
||||||
|
│ │ │ ├── components/
|
||||||
|
│ │ │ │ ├── BiometricLogin.tsx
|
||||||
|
│ │ │ │ ├── PinInput.tsx
|
||||||
|
│ │ │ │ ├── EmergencyAccess.tsx
|
||||||
|
│ │ │ │ └── index.ts
|
||||||
|
│ │ │ ├── screens/
|
||||||
|
│ │ │ │ ├── LoginScreen.tsx
|
||||||
|
│ │ │ │ ├── SetupBiometricScreen.tsx
|
||||||
|
│ │ │ │ └── index.ts
|
||||||
|
│ │ │ ├── hooks/
|
||||||
|
│ │ │ │ ├── useAuth.ts
|
||||||
|
│ │ │ │ ├── useBiometric.ts
|
||||||
|
│ │ │ │ └── index.ts
|
||||||
|
│ │ │ ├── redux/
|
||||||
|
│ │ │ │ ├── authSlice.ts
|
||||||
|
│ │ │ │ ├── authActions.ts
|
||||||
|
│ │ │ │ ├── authSelectors.ts
|
||||||
|
│ │ │ │ └── index.ts
|
||||||
|
│ │ │ ├── services/
|
||||||
|
│ │ │ │ ├── authAPI.ts
|
||||||
|
│ │ │ │ ├── biometricService.ts
|
||||||
|
│ │ │ │ └── index.ts
|
||||||
|
│ │ │ ├── __tests__/
|
||||||
|
│ │ │ │ ├── AuthService.test.ts
|
||||||
|
│ │ │ │ ├── LoginScreen.test.tsx
|
||||||
|
│ │ │ │ └── authSlice.test.ts
|
||||||
|
│ │ │ └── index.ts
|
||||||
|
│ │ │
|
||||||
|
│ │ ├── Dashboard/ # 📊 Dashboard Module
|
||||||
|
│ │ │ ├── components/
|
||||||
|
│ │ │ │ ├── CaseQueue.tsx
|
||||||
|
│ │ │ │ ├── CaseCard.tsx
|
||||||
|
│ │ │ │ ├── PriorityIndicator.tsx
|
||||||
|
│ │ │ │ ├── StatsPanel.tsx
|
||||||
|
│ │ │ │ ├── FilterBar.tsx
|
||||||
|
│ │ │ │ └── index.ts
|
||||||
|
│ │ │ ├── screens/
|
||||||
|
│ │ │ │ ├── DashboardScreen.tsx
|
||||||
|
│ │ │ │ ├── CaseListScreen.tsx
|
||||||
|
│ │ │ │ └── index.ts
|
||||||
|
│ │ │ ├── hooks/
|
||||||
|
│ │ │ │ ├── useCaseQueue.ts
|
||||||
|
│ │ │ │ ├── useRealTimeUpdates.ts
|
||||||
|
│ │ │ │ ├── useFilterCases.ts
|
||||||
|
│ │ │ │ └── index.ts
|
||||||
|
│ │ │ ├── redux/
|
||||||
|
│ │ │ │ ├── dashboardSlice.ts
|
||||||
|
│ │ │ │ ├── caseQueueSlice.ts
|
||||||
|
│ │ │ │ ├── dashboardActions.ts
|
||||||
|
│ │ │ │ ├── dashboardSelectors.ts
|
||||||
|
│ │ │ │ └── index.ts
|
||||||
|
│ │ │ ├── services/
|
||||||
|
│ │ │ │ ├── caseAPI.ts
|
||||||
|
│ │ │ │ ├── websocketService.ts
|
||||||
|
│ │ │ │ ├── notificationService.ts
|
||||||
|
│ │ │ │ └── index.ts
|
||||||
|
│ │ │ ├── __tests__/
|
||||||
|
│ │ │ │ ├── CaseQueue.test.tsx
|
||||||
|
│ │ │ │ ├── dashboardSlice.test.ts
|
||||||
|
│ │ │ │ └── caseAPI.test.ts
|
||||||
|
│ │ │ └── index.ts
|
||||||
|
│ │ │
|
||||||
|
│ │ ├── CaseReview/ # 🔍 Case Review Module
|
||||||
|
│ │ │ ├── components/
|
||||||
|
│ │ │ │ ├── DICOMViewer.tsx
|
||||||
|
│ │ │ │ ├── AIOverlay.tsx
|
||||||
|
│ │ │ │ ├── MeasurementTools.tsx
|
||||||
|
│ │ │ │ ├── AnnotationTools.tsx
|
||||||
|
│ │ │ │ ├── CaseMetadata.tsx
|
||||||
|
│ │ │ │ ├── PriorStudyComparison.tsx
|
||||||
|
│ │ │ │ └── index.ts
|
||||||
|
│ │ │ ├── screens/
|
||||||
|
│ │ │ │ ├── CaseDetailsScreen.tsx
|
||||||
|
│ │ │ │ ├── DICOMViewerScreen.tsx
|
||||||
|
│ │ │ │ ├── ComparisonScreen.tsx
|
||||||
|
│ │ │ │ └── index.ts
|
||||||
|
│ │ │ ├── hooks/
|
||||||
|
│ │ │ │ ├── useDICOMViewer.ts
|
||||||
|
│ │ │ │ ├── useAIOverlay.ts
|
||||||
|
│ │ │ │ ├── useMeasurements.ts
|
||||||
|
│ │ │ │ ├── useAnnotations.ts
|
||||||
|
│ │ │ │ └── index.ts
|
||||||
|
│ │ │ ├── redux/
|
||||||
|
│ │ │ │ ├── caseReviewSlice.ts
|
||||||
|
│ │ │ │ ├── dicomSlice.ts
|
||||||
|
│ │ │ │ ├── aiAnalysisSlice.ts
|
||||||
|
│ │ │ │ ├── caseReviewActions.ts
|
||||||
|
│ │ │ │ ├── caseReviewSelectors.ts
|
||||||
|
│ │ │ │ └── index.ts
|
||||||
|
│ │ │ ├── services/
|
||||||
|
│ │ │ │ ├── dicomAPI.ts
|
||||||
|
│ │ │ │ ├── aiAnalysisAPI.ts
|
||||||
|
│ │ │ │ ├── dicomParser.ts
|
||||||
|
│ │ │ │ ├── imageProcessor.ts
|
||||||
|
│ │ │ │ └── index.ts
|
||||||
|
│ │ │ ├── __tests__/
|
||||||
|
│ │ │ │ ├── DICOMViewer.test.tsx
|
||||||
|
│ │ │ │ ├── AIOverlay.test.tsx
|
||||||
|
│ │ │ │ ├── dicomParser.test.ts
|
||||||
|
│ │ │ │ └── caseReviewSlice.test.ts
|
||||||
|
│ │ │ └── index.ts
|
||||||
|
│ │ │
|
||||||
|
│ │ ├── Reporting/ # 📝 Reporting Module
|
||||||
|
│ │ │ ├── components/
|
||||||
|
│ │ │ │ ├── VoiceRecorder.tsx
|
||||||
|
│ │ │ │ ├── ReportEditor.tsx
|
||||||
|
│ │ │ │ ├── ReportTemplate.tsx
|
||||||
|
│ │ │ │ ├── QuickReportButtons.tsx
|
||||||
|
│ │ │ │ ├── ReportPreview.tsx
|
||||||
|
│ │ │ │ └── index.ts
|
||||||
|
│ │ │ ├── screens/
|
||||||
|
│ │ │ │ ├── ReportingScreen.tsx
|
||||||
|
│ │ │ │ ├── VoiceReportScreen.tsx
|
||||||
|
│ │ │ │ ├── ReportHistoryScreen.tsx
|
||||||
|
│ │ │ │ └── index.ts
|
||||||
|
│ │ │ ├── hooks/
|
||||||
|
│ │ │ │ ├── useVoiceRecording.ts
|
||||||
|
│ │ │ │ ├── useReportGeneration.ts
|
||||||
|
│ │ │ │ ├── useSpeechToText.ts
|
||||||
|
│ │ │ │ └── index.ts
|
||||||
|
│ │ │ ├── redux/
|
||||||
|
│ │ │ │ ├── reportingSlice.ts
|
||||||
|
│ │ │ │ ├── voiceRecordingSlice.ts
|
||||||
|
│ │ │ │ ├── reportingActions.ts
|
||||||
|
│ │ │ │ ├── reportingSelectors.ts
|
||||||
|
│ │ │ │ └── index.ts
|
||||||
|
│ │ │ ├── services/
|
||||||
|
│ │ │ │ ├── reportAPI.ts
|
||||||
|
│ │ │ │ ├── voiceToTextAPI.ts
|
||||||
|
│ │ │ │ ├── reportTemplateService.ts
|
||||||
|
│ │ │ │ └── index.ts
|
||||||
|
│ │ │ ├── __tests__/
|
||||||
|
│ │ │ │ ├── VoiceRecorder.test.tsx
|
||||||
|
│ │ │ │ ├── reportingSlice.test.ts
|
||||||
|
│ │ │ │ └── voiceToTextAPI.test.ts
|
||||||
|
│ │ │ └── index.ts
|
||||||
|
│ │ │
|
||||||
|
│ │ ├── Notifications/ # 🔔 Notifications Module
|
||||||
|
│ │ │ ├── components/
|
||||||
|
│ │ │ │ ├── NotificationPanel.tsx
|
||||||
|
│ │ │ │ ├── AlertBanner.tsx
|
||||||
|
│ │ │ │ ├── CriticalAlert.tsx
|
||||||
|
│ │ │ │ ├── NotificationSettings.tsx
|
||||||
|
│ │ │ │ └── index.ts
|
||||||
|
│ │ │ ├── screens/
|
||||||
|
│ │ │ │ ├── NotificationsScreen.tsx
|
||||||
|
│ │ │ │ ├── AlertDetailsScreen.tsx
|
||||||
|
│ │ │ │ └── index.ts
|
||||||
|
│ │ │ ├── hooks/
|
||||||
|
│ │ │ │ ├── useNotifications.ts
|
||||||
|
│ │ │ │ ├── usePushNotifications.ts
|
||||||
|
│ │ │ │ ├── useAlertHandling.ts
|
||||||
|
│ │ │ │ └── index.ts
|
||||||
|
│ │ │ ├── redux/
|
||||||
|
│ │ │ │ ├── notificationsSlice.ts
|
||||||
|
│ │ │ │ ├── alertsSlice.ts
|
||||||
|
│ │ │ │ ├── notificationActions.ts
|
||||||
|
│ │ │ │ ├── notificationSelectors.ts
|
||||||
|
│ │ │ │ └── index.ts
|
||||||
|
│ │ │ ├── services/
|
||||||
|
│ │ │ │ ├── pushNotificationService.ts
|
||||||
|
│ │ │ │ ├── alertService.ts
|
||||||
|
│ │ │ │ ├── notificationAPI.ts
|
||||||
|
│ │ │ │ └── index.ts
|
||||||
|
│ │ │ ├── __tests__/
|
||||||
|
│ │ │ │ ├── NotificationPanel.test.tsx
|
||||||
|
│ │ │ │ ├── alertService.test.ts
|
||||||
|
│ │ │ │ └── notificationsSlice.test.ts
|
||||||
|
│ │ │ └── index.ts
|
||||||
|
│ │ │
|
||||||
|
│ │ ├── Analytics/ # 📈 Analytics Module
|
||||||
|
│ │ │ ├── components/
|
||||||
|
│ │ │ │ ├── PerformanceMetrics.tsx
|
||||||
|
│ │ │ │ ├── ResponseTimeChart.tsx
|
||||||
|
│ │ │ │ ├── ConcordanceStats.tsx
|
||||||
|
│ │ │ │ ├── WorkloadAnalysis.tsx
|
||||||
|
│ │ │ │ └── index.ts
|
||||||
|
│ │ │ ├── screens/
|
||||||
|
│ │ │ │ ├── AnalyticsScreen.tsx
|
||||||
|
│ │ │ │ ├── PerformanceScreen.tsx
|
||||||
|
│ │ │ │ └── index.ts
|
||||||
|
│ │ │ ├── hooks/
|
||||||
|
│ │ │ │ ├── useAnalytics.ts
|
||||||
|
│ │ │ │ ├── usePerformanceMetrics.ts
|
||||||
|
│ │ │ │ └── index.ts
|
||||||
|
│ │ │ ├── redux/
|
||||||
|
│ │ │ │ ├── analyticsSlice.ts
|
||||||
|
│ │ │ │ ├── analyticsActions.ts
|
||||||
|
│ │ │ │ ├── analyticsSelectors.ts
|
||||||
|
│ │ │ │ └── index.ts
|
||||||
|
│ │ │ ├── services/
|
||||||
|
│ │ │ │ ├── analyticsAPI.ts
|
||||||
|
│ │ │ │ ├── metricsCollector.ts
|
||||||
|
│ │ │ │ └── index.ts
|
||||||
|
│ │ │ ├── __tests__/
|
||||||
|
│ │ │ │ ├── PerformanceMetrics.test.tsx
|
||||||
|
│ │ │ │ ├── analyticsSlice.test.ts
|
||||||
|
│ │ │ │ └── metricsCollector.test.ts
|
||||||
|
│ │ │ └── index.ts
|
||||||
|
│ │ │
|
||||||
|
│ │ └── Profile/ # 👤 User Profile Module
|
||||||
|
│ │ ├── components/
|
||||||
|
│ │ │ ├── ProfileHeader.tsx
|
||||||
|
│ │ │ ├── SettingsPanel.tsx
|
||||||
|
│ │ │ ├── PreferencesForm.tsx
|
||||||
|
│ │ │ ├── SecuritySettings.tsx
|
||||||
|
│ │ │ └── index.ts
|
||||||
|
│ │ ├── screens/
|
||||||
|
│ │ │ ├── ProfileScreen.tsx
|
||||||
|
│ │ │ ├── SettingsScreen.tsx
|
||||||
|
│ │ │ ├── PreferencesScreen.tsx
|
||||||
|
│ │ │ └── index.ts
|
||||||
|
│ │ ├── hooks/
|
||||||
|
│ │ │ ├── useProfile.ts
|
||||||
|
│ │ │ ├── useSettings.ts
|
||||||
|
│ │ │ └── index.ts
|
||||||
|
│ │ ├── redux/
|
||||||
|
│ │ │ ├── profileSlice.ts
|
||||||
|
│ │ │ ├── settingsSlice.ts
|
||||||
|
│ │ │ ├── profileActions.ts
|
||||||
|
│ │ │ ├── profileSelectors.ts
|
||||||
|
│ │ │ └── index.ts
|
||||||
|
│ │ ├── services/
|
||||||
|
│ │ │ ├── profileAPI.ts
|
||||||
|
│ │ │ ├── settingsAPI.ts
|
||||||
|
│ │ │ └── index.ts
|
||||||
|
│ │ ├── __tests__/
|
||||||
|
│ │ │ ├── ProfileHeader.test.tsx
|
||||||
|
│ │ │ ├── profileSlice.test.ts
|
||||||
|
│ │ │ └── profileAPI.test.ts
|
||||||
|
│ │ └── index.ts
|
||||||
|
│ │
|
||||||
|
│ ├── navigation/ # 🧭 Navigation Setup
|
||||||
|
│ │ ├── AppNavigator.tsx
|
||||||
|
│ │ ├── AuthNavigator.tsx
|
||||||
|
│ │ ├── MainNavigator.tsx
|
||||||
|
│ │ ├── TabNavigator.tsx
|
||||||
|
│ │ ├── ModalNavigator.tsx
|
||||||
|
│ │ ├── navigationTypes.ts
|
||||||
|
│ │ └── index.ts
|
||||||
|
│ │
|
||||||
|
│ ├── redux/ # 🗃️ Redux Store Setup
|
||||||
|
│ │ ├── store.ts
|
||||||
|
│ │ ├── rootReducer.ts
|
||||||
|
│ │ ├── middleware.ts
|
||||||
|
│ │ ├── persistConfig.ts
|
||||||
|
│ │ └── index.ts
|
||||||
|
│ │
|
||||||
|
│ ├── constants/ # 📋 Global Constants
|
||||||
|
│ │ ├── apiEndpoints.ts
|
||||||
|
│ │ ├── appConstants.ts
|
||||||
|
│ │ ├── errorMessages.ts
|
||||||
|
│ │ ├── notificationTypes.ts
|
||||||
|
│ │ ├── priorityLevels.ts
|
||||||
|
│ │ └── index.ts
|
||||||
|
│ │
|
||||||
|
│ ├── App.tsx # 🎯 Root Component
|
||||||
|
│ └── index.ts # 🚀 App Bootstrap
|
||||||
|
│
|
||||||
|
├── shared/ # ✅ Shared Components & Utilities
|
||||||
|
│ ├── src/
|
||||||
|
│ │ ├── components/ # 🎨 Global UI Components
|
||||||
|
│ │ │ ├── Button/
|
||||||
|
│ │ │ │ ├── Button.tsx
|
||||||
|
│ │ │ │ ├── Button.styles.ts
|
||||||
|
│ │ │ │ ├── Button.types.ts
|
||||||
|
│ │ │ │ └── index.ts
|
||||||
|
│ │ │ ├── Input/
|
||||||
|
│ │ │ │ ├── TextInput.tsx
|
||||||
|
│ │ │ │ ├── SearchInput.tsx
|
||||||
|
│ │ │ │ ├── Input.styles.ts
|
||||||
|
│ │ │ │ ├── Input.types.ts
|
||||||
|
│ │ │ │ └── index.ts
|
||||||
|
│ │ │ ├── Modal/
|
||||||
|
│ │ │ │ ├── Modal.tsx
|
||||||
|
│ │ │ │ ├── ConfirmModal.tsx
|
||||||
|
│ │ │ │ ├── AlertModal.tsx
|
||||||
|
│ │ │ │ ├── Modal.styles.ts
|
||||||
|
│ │ │ │ ├── Modal.types.ts
|
||||||
|
│ │ │ │ └── index.ts
|
||||||
|
│ │ │ ├── Card/
|
||||||
|
│ │ │ │ ├── Card.tsx
|
||||||
|
│ │ │ │ ├── InfoCard.tsx
|
||||||
|
│ │ │ │ ├── Card.styles.ts
|
||||||
|
│ │ │ │ ├── Card.types.ts
|
||||||
|
│ │ │ │ └── index.ts
|
||||||
|
│ │ │ ├── Loading/
|
||||||
|
│ │ │ │ ├── Spinner.tsx
|
||||||
|
│ │ │ │ ├── LoadingOverlay.tsx
|
||||||
|
│ │ │ │ ├── Loading.styles.ts
|
||||||
|
│ │ │ │ └── index.ts
|
||||||
|
│ │ │ ├── Icons/
|
||||||
|
│ │ │ │ ├── CustomIcon.tsx
|
||||||
|
│ │ │ │ ├── IconButton.tsx
|
||||||
|
│ │ │ │ ├── Icons.types.ts
|
||||||
|
│ │ │ │ └── index.ts
|
||||||
|
│ │ │ └── index.ts
|
||||||
|
│ │ │
|
||||||
|
│ │ ├── theme/ # 🎨 Design System
|
||||||
|
│ │ │ ├── colors.ts
|
||||||
|
│ │ │ ├── spacing.ts
|
||||||
|
│ │ │ ├── typography.ts
|
||||||
|
│ │ │ ├── shadows.ts
|
||||||
|
│ │ │ ├── borderRadius.ts
|
||||||
|
│ │ │ ├── animations.ts
|
||||||
|
│ │ │ └── index.ts
|
||||||
|
│ │ │
|
||||||
|
│ │ ├── assets/ # 📁 Static Assets
|
||||||
|
│ │ │ ├── icons/
|
||||||
|
│ │ │ │ ├── critical-alert.svg
|
||||||
|
│ │ │ │ ├── urgent-alert.svg
|
||||||
|
│ │ │ │ ├── routine-alert.svg
|
||||||
|
│ │ │ │ └── index.ts
|
||||||
|
│ │ │ ├── images/
|
||||||
|
│ │ │ │ ├── logo.png
|
||||||
|
│ │ │ │ ├── placeholder.png
|
||||||
|
│ │ │ │ └── index.ts
|
||||||
|
│ │ │ ├── fonts/
|
||||||
|
│ │ │ │ ├── Roboto-Regular.ttf
|
||||||
|
│ │ │ │ ├── Roboto-Bold.ttf
|
||||||
|
│ │ │ │ └── index.ts
|
||||||
|
│ │ │ └── sounds/
|
||||||
|
│ │ │ ├── critical-alert.wav
|
||||||
|
│ │ │ ├── urgent-alert.wav
|
||||||
|
│ │ │ └── routine-alert.wav
|
||||||
|
│ │ │
|
||||||
|
│ │ ├── utils/ # 🛠️ Utility Functions
|
||||||
|
│ │ │ ├── dateTime/
|
||||||
|
│ │ │ │ ├── formatDate.ts
|
||||||
|
│ │ │ │ ├── timeAgo.ts
|
||||||
|
│ │ │ │ └── index.ts
|
||||||
|
│ │ │ ├── validation/
|
||||||
|
│ │ │ │ ├── validators.ts
|
||||||
|
│ │ │ │ ├── schemas.ts
|
||||||
|
│ │ │ │ └── index.ts
|
||||||
|
│ │ │ ├── helpers/
|
||||||
|
│ │ │ │ ├── debounce.ts
|
||||||
|
│ │ │ │ ├── throttle.ts
|
||||||
|
│ │ │ │ ├── formatters.ts
|
||||||
|
│ │ │ │ └── index.ts
|
||||||
|
│ │ │ ├── constants/
|
||||||
|
│ │ │ │ ├── regex.ts
|
||||||
|
│ │ │ │ ├── formats.ts
|
||||||
|
│ │ │ │ └── index.ts
|
||||||
|
│ │ │ └── index.ts
|
||||||
|
│ │ │
|
||||||
|
│ │ ├── hooks/ # 🎣 Shared Custom Hooks
|
||||||
|
│ │ │ ├── useTheme.ts
|
||||||
|
│ │ │ ├── useDebounce.ts
|
||||||
|
│ │ │ ├── useKeyboard.ts
|
||||||
|
│ │ │ ├── useNetworkStatus.ts
|
||||||
|
│ │ │ ├── useOrientation.ts
|
||||||
|
│ │ │ └── index.ts
|
||||||
|
│ │ │
|
||||||
|
│ │ ├── types/ # 📝 TypeScript Types
|
||||||
|
│ │ │ ├── common.ts
|
||||||
|
│ │ │ ├── api.ts
|
||||||
|
│ │ │ ├── navigation.ts
|
||||||
|
│ │ │ ├── theme.ts
|
||||||
|
│ │ │ └── index.ts
|
||||||
|
│ │ │
|
||||||
|
│ │ └── index.ts # 📦 Barrel Export
|
||||||
|
│ │
|
||||||
|
│ ├── package.json # 📦 Shared Package Config
|
||||||
|
│ └── tsconfig.json # ⚙️ TypeScript Config
|
||||||
|
│
|
||||||
|
├── __tests__/ # 🧪 Global Tests
|
||||||
|
│ ├── App.test.tsx
|
||||||
|
│ ├── Navigation.test.tsx
|
||||||
|
│ ├── Redux.test.tsx
|
||||||
|
│ └── Integration.test.tsx
|
||||||
|
│
|
||||||
|
├── assets/ # 🖼️ App-wide Assets
|
||||||
|
│ ├── images/
|
||||||
|
│ ├── videos/
|
||||||
|
│ └── audio/
|
||||||
|
│
|
||||||
|
├── android/ # 🤖 Android Native
|
||||||
|
├── ios/ # 🍎 iOS Native
|
||||||
|
├── index.js # 🎯 RN Entry Point
|
||||||
|
├── package.json # 📦 Root Dependencies
|
||||||
|
├── tsconfig.json # ⚙️ Base TypeScript Config
|
||||||
|
├── metro.config.js # 📦 Metro Bundler Config
|
||||||
|
├── babel.config.js # 🔄 Babel Config
|
||||||
|
└── .eslintrc.js # 📏 ESLint Config
|
||||||
66
.cursor/rules/themestructure.mdc
Normal file
66
.cursor/rules/themestructure.mdc
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
---
|
||||||
|
description:
|
||||||
|
globs:
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
Radiologist_Rule3
|
||||||
|
Theme and commonly used node_module packages
|
||||||
|
|
||||||
|
Theme
|
||||||
|
Radiologist App - "Clinical Blue Interface"
|
||||||
|
Primary: #5B7CE6 (Blue Purple - from the selected buttons)
|
||||||
|
Secondary: #7B94F0 (Light Blue Purple)
|
||||||
|
Tertiary: #E8EFFF (Very Light Blue)
|
||||||
|
Quaternary: #3B5998 (Deep Blue)
|
||||||
|
Text Primary: #1A1D29 (Almost Black)
|
||||||
|
Text Secondary: #4A5568 (Medium Gray)
|
||||||
|
Text Muted: #9CA3AF (Light Gray)
|
||||||
|
Background: #FFFFFF (White)
|
||||||
|
Background Alt: #F8FAFF (Very Light Blue Tint)
|
||||||
|
Background Accent: #F1F5FF (Soft Blue)
|
||||||
|
Card Background: #FFFFFF (White with shadow)
|
||||||
|
Success: #10B981 (Green - from the security icon)
|
||||||
|
Warning: #F59E0B (Amber)
|
||||||
|
Error: #EF4444 (Red)
|
||||||
|
Info: #3B82F6 (Blue)
|
||||||
|
Border: #E5E7EB (Light Gray)
|
||||||
|
Selected State: #5B7CE6 (Same as Primary)
|
||||||
|
Inactive State: #F3F4F6 (Light Gray)
|
||||||
|
Shadow: rgba(91, 124, 230, 0.1) (Blue Shadow)
|
||||||
|
Gradient Background: linear-gradient(135deg, #667eea 0%, #764ba2 100%)
|
||||||
|
|
||||||
|
Packages Used
|
||||||
|
|
||||||
|
|
||||||
|
"@gorhom/bottom-sheet": "^5.1.2",
|
||||||
|
"@react-native-async-storage/async-storage": "^2.1.2",
|
||||||
|
"@react-native-community/masked-view": "^0.1.11",
|
||||||
|
"@react-native-community/netinfo": "^11.4.1",
|
||||||
|
"@react-navigation/bottom-tabs": "^7.3.10",
|
||||||
|
"@react-navigation/native": "^7.1.6",
|
||||||
|
"@react-navigation/native-stack": "^7.3.9",
|
||||||
|
"@reduxjs/toolkit": "^2.6.1",
|
||||||
|
"@testing-library/react-native": "^13.2.0",
|
||||||
|
"apisauce": "^2.1.6",
|
||||||
|
"axios": "^1.8.4",
|
||||||
|
"react": "19.0.0",
|
||||||
|
"react-native": "0.79.0",
|
||||||
|
"react-native-chart-kit": "^6.12.0",
|
||||||
|
"react-native-config": "^1.5.5",
|
||||||
|
"react-native-device-info": "^14.0.4",
|
||||||
|
"react-native-gesture-handler": "^2.25.0",
|
||||||
|
"react-native-linear-gradient": "^2.8.3",
|
||||||
|
"react-native-paper": "^5.13.1",
|
||||||
|
"react-native-permissions": "^5.4.0",
|
||||||
|
"react-native-raw-bottom-sheet": "^3.0.0",
|
||||||
|
"react-native-reanimated": "^3.17.4",
|
||||||
|
"react-native-safe-area-context": "^5.3.0",
|
||||||
|
"react-native-screens": "^4.10.0",
|
||||||
|
"react-native-skeleton-content": "^1.0.28",
|
||||||
|
"react-native-sound": "^0.11.2",
|
||||||
|
"react-native-svg": "^15.11.2",
|
||||||
|
"react-native-tab-view": "^4.0.10",
|
||||||
|
"react-native-toast-message": "^2.2.1",
|
||||||
|
"react-native-vector-icons": "^10.2.0",
|
||||||
|
"react-redux": "^9.2.0",
|
||||||
|
"redux-persist": "^6.0.0"
|
||||||
4
.env
4
.env
@ -1,2 +1,2 @@
|
|||||||
BASE_URL='https://neoscan-backend.tech4bizsolutions.com'
|
# BASE_URL='https://neoscan-backend.tech4bizsolutions.com'
|
||||||
# BASE_URL='http://192.168.1.87:3000'
|
BASE_URL='http://192.168.1.87:3000'
|
||||||
10
App.tsx
10
App.tsx
@ -16,13 +16,14 @@ import {
|
|||||||
View,
|
View,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { Provider } from 'react-redux'; // Redux Provider
|
import { Provider } from 'react-redux'; // Redux Provider
|
||||||
import store from './app/redux/store'; // Redux store
|
import {store,persistor} from './app/redux/store'; // Redux store
|
||||||
import {
|
import {
|
||||||
Colors,
|
Colors,
|
||||||
} from 'react-native/Libraries/NewAppScreen';
|
} from 'react-native/Libraries/NewAppScreen';
|
||||||
import TabNavigator from './app/navigation/TabNavigator';
|
import TabNavigator from './app/navigation/TabNavigator';
|
||||||
import AppNavigator from './app/navigation/AppNavigator';
|
import AppNavigator from './app/navigation/AppNavigator';
|
||||||
import Toast from 'react-native-toast-message'; // Import Toast
|
import Toast from 'react-native-toast-message'; // Import Toast
|
||||||
|
import { PersistGate } from 'redux-persist/integration/react';
|
||||||
|
|
||||||
type SectionProps = PropsWithChildren<{
|
type SectionProps = PropsWithChildren<{
|
||||||
title: string;
|
title: string;
|
||||||
@ -77,6 +78,7 @@ function App(): React.JSX.Element {
|
|||||||
// Wrap the app with Redux Provider to enable global state management
|
// Wrap the app with Redux Provider to enable global state management
|
||||||
return (
|
return (
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
|
<PersistGate loading={null} persistor={persistor}>
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<StatusBar
|
<StatusBar
|
||||||
barStyle={isDarkMode ? 'light-content' : 'dark-content'}
|
barStyle={isDarkMode ? 'light-content' : 'dark-content'}
|
||||||
@ -84,7 +86,11 @@ function App(): React.JSX.Element {
|
|||||||
/>
|
/>
|
||||||
<AppNavigator />
|
<AppNavigator />
|
||||||
</View>
|
</View>
|
||||||
<Toast />
|
<Toast
|
||||||
|
position='bottom'
|
||||||
|
bottomOffset={20}
|
||||||
|
/>
|
||||||
|
</PersistGate>
|
||||||
</Provider>
|
</Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,8 @@
|
|||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||||
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".MainApplication"
|
android:name=".MainApplication"
|
||||||
|
|||||||
53
app/modules/Auth/components/BackButton.tsx
Normal file
53
app/modules/Auth/components/BackButton.tsx
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* File: BackButton.tsx
|
||||||
|
* Description: Reusable back button component with consistent styling.
|
||||||
|
* Design & Developed by Tech4Biz Solutions
|
||||||
|
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type React from "react"
|
||||||
|
import { TouchableOpacity, StyleSheet } from "react-native"
|
||||||
|
import Icon from "react-native-vector-icons/Feather"
|
||||||
|
import { Colors } from "../../../../shared/src/theme"
|
||||||
|
|
||||||
|
interface BackButtonProps {
|
||||||
|
onPress: () => void
|
||||||
|
style?: any
|
||||||
|
}
|
||||||
|
|
||||||
|
const BackButton: React.FC<BackButtonProps> = ({ onPress, style }) => {
|
||||||
|
return (
|
||||||
|
<TouchableOpacity style={[styles.backButton, style]} onPress={onPress} activeOpacity={0.7}>
|
||||||
|
<Icon name="arrow-left" size={20} color={Colors.textPrimary} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
backButton: {
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
borderRadius: 20,
|
||||||
|
backgroundColor: Colors.backButtonBackground,
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
shadowColor: Colors.backButtonShadow,
|
||||||
|
shadowOffset: {
|
||||||
|
width: 0,
|
||||||
|
height: 2,
|
||||||
|
},
|
||||||
|
shadowOpacity: 0.1,
|
||||||
|
shadowRadius: 4,
|
||||||
|
elevation: 3,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export default BackButton
|
||||||
|
|
||||||
|
/*
|
||||||
|
* End of File: BackButton.tsx
|
||||||
|
* Design & Developed by Tech4Biz Solutions
|
||||||
|
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||||
|
*/
|
||||||
401
app/modules/Auth/components/DocumentUploadScreen.tsx
Normal file
401
app/modules/Auth/components/DocumentUploadScreen.tsx
Normal file
@ -0,0 +1,401 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* File: DocumentUploadScreen.tsx
|
||||||
|
* Description: Image upload screen for onboarding flow with react-native-image-picker.
|
||||||
|
* Design & Developed by Tech4Biz Solutions
|
||||||
|
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type React from "react"
|
||||||
|
import { useState } from "react"
|
||||||
|
import {
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
StyleSheet,
|
||||||
|
TouchableWithoutFeedback,
|
||||||
|
Keyboard,
|
||||||
|
TouchableOpacity,
|
||||||
|
Alert,
|
||||||
|
PermissionsAndroid,
|
||||||
|
Platform,
|
||||||
|
Image
|
||||||
|
} from "react-native"
|
||||||
|
import {
|
||||||
|
launchImageLibrary,
|
||||||
|
launchCamera,
|
||||||
|
ImagePickerResponse,
|
||||||
|
MediaType,
|
||||||
|
ImagePickerOptions
|
||||||
|
} from 'react-native-image-picker'
|
||||||
|
import { Button } from "../../../../shared/src/components/Button"
|
||||||
|
import { Colors, Spacing, Typography } from "../../../../shared/src/theme"
|
||||||
|
import { showError, showSuccess } from "../../../../shared/src/utils/helpers/Toast"
|
||||||
|
import Icon from "react-native-vector-icons/Feather"
|
||||||
|
import OnboardingContainer from "./OnboardingContainer"
|
||||||
|
|
||||||
|
interface DocumentUploadScreenProps {
|
||||||
|
onContinue: (imageData: {
|
||||||
|
uri: string
|
||||||
|
name: string
|
||||||
|
type: string
|
||||||
|
size?: number
|
||||||
|
}) => void
|
||||||
|
onBack: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImageData {
|
||||||
|
uri: string
|
||||||
|
name: string
|
||||||
|
type: string
|
||||||
|
size?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const DocumentUploadScreen: React.FC<DocumentUploadScreenProps> = ({ onContinue, onBack }) => {
|
||||||
|
const [selectedImage, setSelectedImage] = useState<ImageData | null>(null)
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
|
||||||
|
// Request camera permission for Android
|
||||||
|
const requestCameraPermission = async (): Promise<boolean> => {
|
||||||
|
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(err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleImagePicker = () => {
|
||||||
|
Alert.alert("Select Image", "Choose how you want to upload your image", [
|
||||||
|
{
|
||||||
|
text: "Camera",
|
||||||
|
onPress: () => handleCameraCapture(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "Gallery",
|
||||||
|
onPress: () => handleGalleryPicker(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "Cancel",
|
||||||
|
style: "cancel",
|
||||||
|
},
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCameraCapture = async () => {
|
||||||
|
const hasPermission = await requestCameraPermission()
|
||||||
|
|
||||||
|
if (!hasPermission) {
|
||||||
|
showError("Permission Error", "Camera permission is required to capture images.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const options: ImagePickerOptions = {
|
||||||
|
mediaType: 'photo' as MediaType,
|
||||||
|
quality: 0.8,
|
||||||
|
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 || `image_${Date.now()}.jpg`,
|
||||||
|
type: asset.type || 'image/jpeg',
|
||||||
|
size: asset.fileSize,
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelectedImage(imageData)
|
||||||
|
showSuccess("Success", "Image captured successfully!")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleGalleryPicker = () => {
|
||||||
|
const options: ImagePickerOptions = {
|
||||||
|
mediaType: 'photo' as MediaType,
|
||||||
|
quality: 0.8,
|
||||||
|
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 || `image_${Date.now()}.jpg`,
|
||||||
|
type: asset.type || 'image/jpeg',
|
||||||
|
size: asset.fileSize,
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelectedImage(imageData)
|
||||||
|
showSuccess("Success", "Image selected from gallery!")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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]
|
||||||
|
}
|
||||||
|
|
||||||
|
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'
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleContinue = () => {
|
||||||
|
if (!selectedImage) {
|
||||||
|
showError("Validation Error", "Please upload an image to continue.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true)
|
||||||
|
setTimeout(() => {
|
||||||
|
setLoading(false)
|
||||||
|
onContinue(selectedImage)
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<OnboardingContainer onBack={onBack}>
|
||||||
|
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
|
||||||
|
<View style={styles.container}>
|
||||||
|
<View style={styles.content}>
|
||||||
|
<View style={styles.iconContainer}>
|
||||||
|
<View style={styles.iconWrapper}>
|
||||||
|
<Icon name="upload" size={32} color={Colors.primary} />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Text style={styles.title}>Upload Image</Text>
|
||||||
|
<Text style={styles.subtitle}>Please upload your profile picture or identification image.</Text>
|
||||||
|
|
||||||
|
<TouchableOpacity style={styles.uploadContainer} onPress={handleImagePicker}>
|
||||||
|
<View style={styles.uploadContent}>
|
||||||
|
{selectedImage ? (
|
||||||
|
<View style={styles.imagePreviewContainer}>
|
||||||
|
<Image source={{ uri: selectedImage.uri }} style={styles.imagePreview} />
|
||||||
|
<TouchableOpacity style={styles.imageOverlay} onPress={()=>setSelectedImage(null)}>
|
||||||
|
<Icon name="x" size={24} color={'#FFFFFF'} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
<Text style={styles.uploadedText}>Image Uploaded</Text>
|
||||||
|
<Text style={styles.fileName}>{selectedImage.name}</Text>
|
||||||
|
<View style={styles.fileDetails}>
|
||||||
|
<Text style={styles.fileType}>{getFileTypeDisplay(selectedImage.type)}</Text>
|
||||||
|
{selectedImage.size && (
|
||||||
|
<Text style={styles.fileSize}> • {formatFileSize(selectedImage.size)}</Text>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Icon name="image" size={48} color={Colors.textSecondary} />
|
||||||
|
<Text style={styles.uploadText}>Tap to upload image</Text>
|
||||||
|
<Text style={styles.uploadSubtext}>JPG, PNG supported</Text>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
{selectedImage && (
|
||||||
|
<TouchableOpacity style={styles.changeButton} onPress={handleImagePicker}>
|
||||||
|
<Text style={styles.changeButtonText}>Change Image</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
title="Continue"
|
||||||
|
onPress={handleContinue}
|
||||||
|
style={[styles.button, !selectedImage && styles.buttonDisabled]}
|
||||||
|
loading={loading}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</TouchableWithoutFeedback>
|
||||||
|
</OnboardingContainer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: "center",
|
||||||
|
},
|
||||||
|
iconContainer: {
|
||||||
|
alignItems: "center",
|
||||||
|
marginBottom: Spacing.xl,
|
||||||
|
},
|
||||||
|
iconWrapper: {
|
||||||
|
width: 80,
|
||||||
|
height: 80,
|
||||||
|
borderRadius: 20,
|
||||||
|
backgroundColor: Colors.backgroundAlt,
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: Colors.border,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontFamily: Typography.fontFamily.bold,
|
||||||
|
fontSize: Typography.fontSize.title,
|
||||||
|
color: Colors.textPrimary,
|
||||||
|
textAlign: "center",
|
||||||
|
marginBottom: Spacing.sm,
|
||||||
|
},
|
||||||
|
subtitle: {
|
||||||
|
fontFamily: Typography.fontFamily.regular,
|
||||||
|
fontSize: Typography.fontSize.md,
|
||||||
|
color: Colors.textSecondary,
|
||||||
|
textAlign: "center",
|
||||||
|
marginBottom: Spacing.xl,
|
||||||
|
},
|
||||||
|
uploadContainer: {
|
||||||
|
backgroundColor: Colors.backgroundAlt,
|
||||||
|
borderWidth: 2,
|
||||||
|
borderColor: Colors.border,
|
||||||
|
borderStyle: "dashed",
|
||||||
|
borderRadius: 12,
|
||||||
|
padding: Spacing.xl,
|
||||||
|
marginBottom: Spacing.md,
|
||||||
|
minHeight: 200,
|
||||||
|
},
|
||||||
|
uploadContent: {
|
||||||
|
alignItems: "center",
|
||||||
|
},
|
||||||
|
imagePreviewContainer: {
|
||||||
|
alignItems: "center",
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
imagePreview: {
|
||||||
|
width: '100%',
|
||||||
|
height: 150,
|
||||||
|
borderRadius: 12,
|
||||||
|
marginBottom: Spacing.sm,
|
||||||
|
resizeMode: 'contain',
|
||||||
|
},
|
||||||
|
imageOverlay: {
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
right: 20,
|
||||||
|
marginRight: -20, // Half of image width minus half of overlay
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.4)',
|
||||||
|
borderRadius: 17,
|
||||||
|
width: 34,
|
||||||
|
height: 34,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
padding:2
|
||||||
|
},
|
||||||
|
uploadText: {
|
||||||
|
fontFamily: Typography.fontFamily.medium,
|
||||||
|
fontSize: Typography.fontSize.md,
|
||||||
|
color: Colors.textPrimary,
|
||||||
|
marginTop: Spacing.sm,
|
||||||
|
},
|
||||||
|
uploadSubtext: {
|
||||||
|
fontFamily: Typography.fontFamily.regular,
|
||||||
|
fontSize: Typography.fontSize.sm,
|
||||||
|
color: Colors.textSecondary,
|
||||||
|
marginTop: Spacing.xs,
|
||||||
|
},
|
||||||
|
uploadedText: {
|
||||||
|
fontFamily: Typography.fontFamily.medium,
|
||||||
|
fontSize: Typography.fontSize.md,
|
||||||
|
color: Colors.success,
|
||||||
|
marginTop: Spacing.sm,
|
||||||
|
},
|
||||||
|
fileName: {
|
||||||
|
fontFamily: Typography.fontFamily.regular,
|
||||||
|
fontSize: Typography.fontSize.sm,
|
||||||
|
color: Colors.textSecondary,
|
||||||
|
marginTop: Spacing.xs,
|
||||||
|
textAlign: "center",
|
||||||
|
maxWidth: '80%',
|
||||||
|
},
|
||||||
|
fileDetails: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
marginTop: Spacing.xs,
|
||||||
|
},
|
||||||
|
fileType: {
|
||||||
|
fontFamily: Typography.fontFamily.regular,
|
||||||
|
fontSize: Typography.fontSize.xs,
|
||||||
|
color: Colors.textMuted,
|
||||||
|
},
|
||||||
|
fileSize: {
|
||||||
|
fontFamily: Typography.fontFamily.regular,
|
||||||
|
fontSize: Typography.fontSize.xs,
|
||||||
|
color: Colors.textMuted,
|
||||||
|
},
|
||||||
|
changeButton: {
|
||||||
|
alignSelf: "center",
|
||||||
|
marginBottom: Spacing.xl,
|
||||||
|
},
|
||||||
|
changeButtonText: {
|
||||||
|
fontFamily: Typography.fontFamily.medium,
|
||||||
|
fontSize: Typography.fontSize.md,
|
||||||
|
color: Colors.primary,
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
marginTop: Spacing.md,
|
||||||
|
},
|
||||||
|
buttonDisabled: {
|
||||||
|
opacity: 0.5,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export default DocumentUploadScreen
|
||||||
|
|
||||||
|
/*
|
||||||
|
* End of File: DocumentUploadScreen.tsx
|
||||||
|
* Design & Developed by Tech4Biz Solutions
|
||||||
|
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||||
|
*/
|
||||||
127
app/modules/Auth/components/EmailScreen.tsx
Normal file
127
app/modules/Auth/components/EmailScreen.tsx
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* File: EmailScreen.tsx
|
||||||
|
* Description: Email input screen for onboarding flow with gradient background.
|
||||||
|
* Design & Developed by Tech4Biz Solutions
|
||||||
|
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type React from "react"
|
||||||
|
import { useState } from "react"
|
||||||
|
import { View, Text, StyleSheet, TextInput } from "react-native"
|
||||||
|
import { Button } from "../../../../shared/src/components/Button"
|
||||||
|
import OnboardingContainer from "./OnboardingContainer"
|
||||||
|
import IconContainer from "./IconContainer"
|
||||||
|
import { Colors, Spacing, Typography } from "../../../../shared/src/theme"
|
||||||
|
import { showError } from "../../../../shared/src/utils/helpers/Toast"
|
||||||
|
import { validateEmail } from "../../../../shared/src/utils/validation/validators"
|
||||||
|
import Icon from "react-native-vector-icons/Feather"
|
||||||
|
|
||||||
|
interface EmailScreenProps {
|
||||||
|
onContinue: (email: string) => void
|
||||||
|
onBack: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const EmailScreen: React.FC<EmailScreenProps> = ({ onContinue, onBack }) => {
|
||||||
|
const [email, setEmail] = useState("")
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
|
||||||
|
const handleContinue = () => {
|
||||||
|
if (!email.trim()) {
|
||||||
|
showError("Validation Error", "Email address is required.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!validateEmail(email)) {
|
||||||
|
showError("Validation Error", "Please enter a valid email address.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true)
|
||||||
|
setTimeout(() => {
|
||||||
|
setLoading(false)
|
||||||
|
onContinue(email)
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<OnboardingContainer onBack={onBack}>
|
||||||
|
<IconContainer iconName="mail" />
|
||||||
|
|
||||||
|
<Text style={styles.title}>What's your email?</Text>
|
||||||
|
<Text style={styles.subtitle}>Please enter your email address.</Text>
|
||||||
|
|
||||||
|
<View style={styles.inputContainer}>
|
||||||
|
<Icon name="mail" size={20} color={Colors.textSecondary} style={styles.inputIcon} />
|
||||||
|
<TextInput
|
||||||
|
placeholder="Email address"
|
||||||
|
value={email}
|
||||||
|
onChangeText={setEmail}
|
||||||
|
style={styles.inputField}
|
||||||
|
autoCapitalize="none"
|
||||||
|
keyboardType="email-address"
|
||||||
|
placeholderTextColor={Colors.textMuted}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Button title="Continue" onPress={handleContinue} style={styles.button} loading={loading} />
|
||||||
|
</OnboardingContainer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
title: {
|
||||||
|
fontFamily: Typography.fontFamily.bold,
|
||||||
|
fontSize: Typography.fontSize.title,
|
||||||
|
color: Colors.textPrimary,
|
||||||
|
textAlign: "center",
|
||||||
|
marginBottom: Spacing.sm,
|
||||||
|
},
|
||||||
|
subtitle: {
|
||||||
|
fontFamily: Typography.fontFamily.regular,
|
||||||
|
fontSize: Typography.fontSize.md,
|
||||||
|
color: Colors.textSecondary,
|
||||||
|
textAlign: "center",
|
||||||
|
marginBottom: Spacing.xl,
|
||||||
|
},
|
||||||
|
inputContainer: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
backgroundColor: Colors.inputBackground,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: Colors.border,
|
||||||
|
borderRadius: 12,
|
||||||
|
marginBottom: Spacing.xl,
|
||||||
|
paddingHorizontal: Spacing.md,
|
||||||
|
paddingVertical: 2,
|
||||||
|
shadowColor: Colors.backButtonShadow,
|
||||||
|
shadowOffset: {
|
||||||
|
width: 0,
|
||||||
|
height: 1,
|
||||||
|
},
|
||||||
|
shadowOpacity: 0.05,
|
||||||
|
shadowRadius: 2,
|
||||||
|
elevation: 1,
|
||||||
|
},
|
||||||
|
inputIcon: {
|
||||||
|
marginRight: Spacing.sm,
|
||||||
|
},
|
||||||
|
inputField: {
|
||||||
|
flex: 1,
|
||||||
|
paddingVertical: Spacing.md,
|
||||||
|
fontSize: Typography.fontSize.md,
|
||||||
|
color: Colors.textPrimary,
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
marginTop: Spacing.md,
|
||||||
|
paddingVertical:Spacing.lg,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export default EmailScreen
|
||||||
|
|
||||||
|
/*
|
||||||
|
* End of File: EmailScreen.tsx
|
||||||
|
* Design & Developed by Tech4Biz Solutions
|
||||||
|
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||||
|
*/
|
||||||
231
app/modules/Auth/components/HospitalSelectionScreen.tsx
Normal file
231
app/modules/Auth/components/HospitalSelectionScreen.tsx
Normal file
@ -0,0 +1,231 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* File: HospitalSelectionScreen.tsx
|
||||||
|
* Description: Hospital selection screen with dropdown using react-native-element-dropdown.
|
||||||
|
* Design & Developed by Tech4Biz Solutions
|
||||||
|
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type React from "react"
|
||||||
|
import { useState } from "react"
|
||||||
|
import { View, Text, StyleSheet, TouchableWithoutFeedback, Keyboard } from "react-native"
|
||||||
|
import { Dropdown } from "react-native-element-dropdown"
|
||||||
|
import { Button } from "../../../../shared/src/components/Button"
|
||||||
|
import { Colors, Spacing, Typography } from "../../../../shared/src/theme"
|
||||||
|
import { showError } from "../../../../shared/src/utils/helpers/Toast"
|
||||||
|
import Icon from "react-native-vector-icons/Feather"
|
||||||
|
import OnboardingContainer from "./OnboardingContainer"
|
||||||
|
import { selectHospitals } from "../redux"
|
||||||
|
import { useSelector } from "react-redux"
|
||||||
|
|
||||||
|
interface HospitalSelectionScreenProps {
|
||||||
|
onContinue: (hospitalId: string) => void
|
||||||
|
onBack: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const hospitalData = [
|
||||||
|
{ label: "City General Hospital", value: "city_general" },
|
||||||
|
{ label: "St. Mary's Medical Center", value: "st_marys" },
|
||||||
|
{ label: "University Hospital", value: "university" },
|
||||||
|
{ label: "Regional Medical Center", value: "regional" },
|
||||||
|
{ label: "Metropolitan Hospital", value: "metropolitan" },
|
||||||
|
{ label: "Community Health Center", value: "community" },
|
||||||
|
{ label: "Children's Hospital", value: "childrens" },
|
||||||
|
{ label: "Veterans Medical Center", value: "veterans" },
|
||||||
|
]
|
||||||
|
|
||||||
|
const HospitalSelectionScreen: React.FC<HospitalSelectionScreenProps> = ({ onContinue, onBack }) => {
|
||||||
|
const [selectedHospital, setSelectedHospital] = useState<string | null>(null)
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
const [isFocus, setIsFocus] = useState(false)
|
||||||
|
const hospitals :any=useSelector(selectHospitals)
|
||||||
|
|
||||||
|
const handleContinue = () => {
|
||||||
|
if (!selectedHospital) {
|
||||||
|
showError("Validation Error", "Please select a hospital to continue.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true)
|
||||||
|
setTimeout(() => {
|
||||||
|
setLoading(false)
|
||||||
|
onContinue(selectedHospital)
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderLabel = () => {
|
||||||
|
if (selectedHospital || isFocus) {
|
||||||
|
return <Text style={[styles.label, isFocus && { color: Colors.primary }]}>Select Hospital</Text>
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<OnboardingContainer onBack={onBack}>
|
||||||
|
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
|
||||||
|
<View style={styles.container}>
|
||||||
|
{/* <View style={styles.header}>
|
||||||
|
<Icon name="arrow-left" size={24} color={Colors.textPrimary} onPress={onBack} />
|
||||||
|
</View> */}
|
||||||
|
|
||||||
|
<View style={styles.content}>
|
||||||
|
<View style={styles.iconContainer}>
|
||||||
|
<View style={styles.iconWrapper}>
|
||||||
|
<Icon name="map-pin" size={32} color={Colors.primary} />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Text style={styles.title}>Select Hospital</Text>
|
||||||
|
<Text style={styles.subtitle}>Choose the hospital you're affiliated with.</Text>
|
||||||
|
|
||||||
|
<View style={styles.dropdownContainer}>
|
||||||
|
{renderLabel()}
|
||||||
|
<Dropdown
|
||||||
|
style={[styles.dropdown, isFocus && { borderColor: Colors.primary }]}
|
||||||
|
placeholderStyle={styles.placeholderStyle}
|
||||||
|
selectedTextStyle={styles.selectedTextStyle}
|
||||||
|
inputSearchStyle={styles.inputSearchStyle}
|
||||||
|
iconStyle={styles.iconStyle}
|
||||||
|
data={hospitals}
|
||||||
|
search
|
||||||
|
maxHeight={300}
|
||||||
|
labelField="hospital_name"
|
||||||
|
valueField="hospital_id"
|
||||||
|
placeholder={!isFocus ? "Select Hospital" : "..."}
|
||||||
|
searchPlaceholder="Search hospitals..."
|
||||||
|
value={selectedHospital}
|
||||||
|
onFocus={() => setIsFocus(true)}
|
||||||
|
onBlur={() => setIsFocus(false)}
|
||||||
|
onChange={(item) => {
|
||||||
|
console.log('item',item)
|
||||||
|
setSelectedHospital(item.hospital_id)
|
||||||
|
setIsFocus(false)
|
||||||
|
}}
|
||||||
|
renderLeftIcon={() => (
|
||||||
|
<Icon
|
||||||
|
style={styles.icon}
|
||||||
|
color={isFocus ? Colors.primary : Colors.textSecondary}
|
||||||
|
name="map-pin"
|
||||||
|
size={20}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
title="Create Account"
|
||||||
|
onPress={handleContinue}
|
||||||
|
style={[styles.button, !selectedHospital && styles.buttonDisabled]}
|
||||||
|
loading={loading}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</TouchableWithoutFeedback>
|
||||||
|
</OnboardingContainer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
// backgroundColor: Colors.background,
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
paddingTop: Spacing.xl,
|
||||||
|
paddingHorizontal: Spacing.lg,
|
||||||
|
paddingBottom: Spacing.md,
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
flex: 1,
|
||||||
|
// paddingHorizontal: Spacing.lg,
|
||||||
|
justifyContent: "center",
|
||||||
|
},
|
||||||
|
iconContainer: {
|
||||||
|
alignItems: "center",
|
||||||
|
marginBottom: Spacing.xl,
|
||||||
|
},
|
||||||
|
iconWrapper: {
|
||||||
|
width: 80,
|
||||||
|
height: 80,
|
||||||
|
borderRadius: 20,
|
||||||
|
backgroundColor: Colors.backgroundAlt,
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: Colors.border,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontFamily: Typography.fontFamily.bold,
|
||||||
|
fontSize: Typography.fontSize.title,
|
||||||
|
color: Colors.textPrimary,
|
||||||
|
textAlign: "center",
|
||||||
|
marginBottom: Spacing.sm,
|
||||||
|
},
|
||||||
|
subtitle: {
|
||||||
|
fontFamily: Typography.fontFamily.regular,
|
||||||
|
fontSize: Typography.fontSize.md,
|
||||||
|
color: Colors.textSecondary,
|
||||||
|
textAlign: "center",
|
||||||
|
marginBottom: Spacing.xl,
|
||||||
|
},
|
||||||
|
dropdownContainer: {
|
||||||
|
marginBottom: Spacing.xl,
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
position: "absolute",
|
||||||
|
// backgroundColor: Colors.background,
|
||||||
|
left: 0,
|
||||||
|
top: -20,
|
||||||
|
zIndex: 999,
|
||||||
|
paddingHorizontal: 8,
|
||||||
|
fontSize: Typography.fontSize.sm,
|
||||||
|
color: Colors.textSecondary,
|
||||||
|
fontFamily: Typography.fontFamily.bold,
|
||||||
|
},
|
||||||
|
dropdown: {
|
||||||
|
height: 50,
|
||||||
|
borderColor: Colors.border,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderRadius: 12,
|
||||||
|
paddingHorizontal: Spacing.md,
|
||||||
|
backgroundColor: Colors.backgroundAlt,
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
marginRight: Spacing.sm,
|
||||||
|
},
|
||||||
|
placeholderStyle: {
|
||||||
|
fontSize: Typography.fontSize.md,
|
||||||
|
color: Colors.textMuted,
|
||||||
|
fontFamily: Typography.fontFamily.regular,
|
||||||
|
},
|
||||||
|
selectedTextStyle: {
|
||||||
|
fontSize: Typography.fontSize.md,
|
||||||
|
color: Colors.textPrimary,
|
||||||
|
fontFamily: Typography.fontFamily.regular,
|
||||||
|
},
|
||||||
|
iconStyle: {
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
},
|
||||||
|
inputSearchStyle: {
|
||||||
|
height: 40,
|
||||||
|
fontSize: Typography.fontSize.md,
|
||||||
|
color: Colors.textPrimary,
|
||||||
|
fontFamily: Typography.fontFamily.regular,
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
marginTop: Spacing.md,
|
||||||
|
},
|
||||||
|
buttonDisabled: {
|
||||||
|
opacity: 0.5,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export default HospitalSelectionScreen
|
||||||
|
|
||||||
|
/*
|
||||||
|
* End of File: HospitalSelectionScreen.tsx
|
||||||
|
* Design & Developed by Tech4Biz Solutions
|
||||||
|
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||||
|
*/
|
||||||
76
app/modules/Auth/components/IconContainer.tsx
Normal file
76
app/modules/Auth/components/IconContainer.tsx
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* File: IconContainer.tsx
|
||||||
|
* Description: Reusable icon container component for onboarding screens.
|
||||||
|
* Design & Developed by Tech4Biz Solutions
|
||||||
|
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type React from "react"
|
||||||
|
import { View, StyleSheet } from "react-native"
|
||||||
|
import Icon from "react-native-vector-icons/Feather"
|
||||||
|
import { Colors, Spacing } from "../../../../shared/src/theme"
|
||||||
|
|
||||||
|
interface IconContainerProps {
|
||||||
|
iconName: string
|
||||||
|
iconSize?: number
|
||||||
|
containerSize?: number
|
||||||
|
backgroundColor?: string
|
||||||
|
iconColor?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const IconContainer: React.FC<IconContainerProps> = ({
|
||||||
|
iconName,
|
||||||
|
iconSize = 32,
|
||||||
|
containerSize = 80,
|
||||||
|
backgroundColor = Colors.inputBackground,
|
||||||
|
iconColor = Colors.primary,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.iconWrapper,
|
||||||
|
{
|
||||||
|
width: containerSize,
|
||||||
|
height: containerSize,
|
||||||
|
borderRadius: containerSize * 0.25,
|
||||||
|
backgroundColor,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Icon name={iconName} size={iconSize} color={iconColor} />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
alignItems: "center",
|
||||||
|
marginBottom: Spacing.xl,
|
||||||
|
},
|
||||||
|
iconWrapper: {
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: Colors.border,
|
||||||
|
shadowColor: Colors.backButtonShadow,
|
||||||
|
shadowOffset: {
|
||||||
|
width: 0,
|
||||||
|
height: 2,
|
||||||
|
},
|
||||||
|
shadowOpacity: 0.1,
|
||||||
|
shadowRadius: 4,
|
||||||
|
elevation: 2,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export default IconContainer
|
||||||
|
|
||||||
|
/*
|
||||||
|
* End of File: IconContainer.tsx
|
||||||
|
* Design & Developed by Tech4Biz Solutions
|
||||||
|
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||||
|
*/
|
||||||
164
app/modules/Auth/components/NameScreen.tsx
Normal file
164
app/modules/Auth/components/NameScreen.tsx
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* File: NameScreen.tsx
|
||||||
|
* Description: Name input screen with gradient background and shared components.
|
||||||
|
* Design & Developed by Tech4Biz Solutions
|
||||||
|
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type React from "react"
|
||||||
|
import { useState } from "react"
|
||||||
|
import { View, Text, StyleSheet, TextInput } from "react-native"
|
||||||
|
import { Button } from "../../../../shared/src/components/Button"
|
||||||
|
import OnboardingContainer from "./OnboardingContainer"
|
||||||
|
import IconContainer from "./IconContainer"
|
||||||
|
import { Colors, Spacing, Typography } from "../../../../shared/src/theme"
|
||||||
|
import { showError } from "../../../../shared/src/utils/helpers/Toast"
|
||||||
|
import Icon from "react-native-vector-icons/Feather"
|
||||||
|
|
||||||
|
interface NameScreenProps {
|
||||||
|
onContinue: (firstName: string, lastName: string, username: string) => void
|
||||||
|
onBack: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const NameScreen: React.FC<NameScreenProps> = ({ onContinue, onBack }) => {
|
||||||
|
const [firstName, setFirstName] = useState("")
|
||||||
|
const [lastName, setLastName] = useState("")
|
||||||
|
const [username, setUsername] = useState("")
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
|
||||||
|
const handleContinue = () => {
|
||||||
|
if (!firstName.trim() || !lastName.trim() || !username.trim()) {
|
||||||
|
showError("Validation Error", "First name, last name, and username are required.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstName.trim().length < 2 ) {
|
||||||
|
showError("Validation Error", "Names must be at least 2 characters long.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (username.trim().length < 3) {
|
||||||
|
showError("Validation Error", "Username must be at least 3 characters long.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Basic username validation (alphanumeric and underscores only)
|
||||||
|
const usernameRegex = /^[a-zA-Z0-9_]+$/
|
||||||
|
if (!usernameRegex.test(username.trim())) {
|
||||||
|
showError("Validation Error", "Username can only contain letters, numbers, and underscores.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true)
|
||||||
|
setTimeout(() => {
|
||||||
|
setLoading(false)
|
||||||
|
onContinue(firstName.trim(), lastName.trim(), username.trim())
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<OnboardingContainer onBack={onBack}>
|
||||||
|
<IconContainer iconName="user" />
|
||||||
|
|
||||||
|
<Text style={styles.title}>What's your name?</Text>
|
||||||
|
<Text style={styles.subtitle}>Please enter your details to get started.</Text>
|
||||||
|
|
||||||
|
<View style={styles.inputContainer}>
|
||||||
|
<Icon name="user" size={20} color={Colors.textSecondary} style={styles.inputIcon} />
|
||||||
|
<TextInput
|
||||||
|
placeholder="First Name"
|
||||||
|
value={firstName}
|
||||||
|
onChangeText={setFirstName}
|
||||||
|
style={styles.inputField}
|
||||||
|
autoCapitalize="words"
|
||||||
|
placeholderTextColor={Colors.textMuted}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.inputContainer}>
|
||||||
|
<Icon name="user" size={20} color={Colors.textSecondary} style={styles.inputIcon} />
|
||||||
|
<TextInput
|
||||||
|
placeholder="Last Name"
|
||||||
|
value={lastName}
|
||||||
|
onChangeText={setLastName}
|
||||||
|
style={styles.inputField}
|
||||||
|
autoCapitalize="words"
|
||||||
|
placeholderTextColor={Colors.textMuted}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.inputContainer}>
|
||||||
|
<Icon name="at-sign" size={20} color={Colors.textSecondary} style={styles.inputIcon} />
|
||||||
|
<TextInput
|
||||||
|
placeholder="Username"
|
||||||
|
value={username}
|
||||||
|
onChangeText={setUsername}
|
||||||
|
style={styles.inputField}
|
||||||
|
autoCapitalize="none"
|
||||||
|
autoCorrect={false}
|
||||||
|
placeholderTextColor={Colors.textMuted}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Button title="Continue" onPress={handleContinue} style={styles.button} loading={loading} />
|
||||||
|
</OnboardingContainer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
title: {
|
||||||
|
fontFamily: Typography.fontFamily.bold,
|
||||||
|
fontSize: Typography.fontSize.title,
|
||||||
|
color: Colors.textPrimary,
|
||||||
|
textAlign: "center",
|
||||||
|
marginBottom: Spacing.sm,
|
||||||
|
},
|
||||||
|
subtitle: {
|
||||||
|
fontFamily: Typography.fontFamily.regular,
|
||||||
|
fontSize: Typography.fontSize.md,
|
||||||
|
color: Colors.textSecondary,
|
||||||
|
textAlign: "center",
|
||||||
|
marginBottom: Spacing.xl,
|
||||||
|
},
|
||||||
|
inputContainer: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
backgroundColor: Colors.inputBackground,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: Colors.border,
|
||||||
|
borderRadius: 12,
|
||||||
|
marginBottom: Spacing.md,
|
||||||
|
paddingHorizontal: Spacing.md,
|
||||||
|
paddingVertical: 2,
|
||||||
|
shadowColor: Colors.backButtonShadow,
|
||||||
|
shadowOffset: {
|
||||||
|
width: 0,
|
||||||
|
height: 1,
|
||||||
|
},
|
||||||
|
shadowOpacity: 0.05,
|
||||||
|
shadowRadius: 2,
|
||||||
|
elevation: 1,
|
||||||
|
},
|
||||||
|
inputIcon: {
|
||||||
|
marginRight: Spacing.sm,
|
||||||
|
},
|
||||||
|
inputField: {
|
||||||
|
flex: 1,
|
||||||
|
paddingVertical: Spacing.md,
|
||||||
|
fontSize: Typography.fontSize.md,
|
||||||
|
color: Colors.textPrimary,
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
marginTop: Spacing.xl,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export default NameScreen
|
||||||
|
|
||||||
|
/*
|
||||||
|
* End of File: NameScreen.tsx
|
||||||
|
* Design & Developed by Tech4Biz Solutions
|
||||||
|
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||||
|
*/
|
||||||
64
app/modules/Auth/components/OnboardingContainer.tsx
Normal file
64
app/modules/Auth/components/OnboardingContainer.tsx
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* File: OnboardingContainer.tsx
|
||||||
|
* Description: Shared container component for onboarding screens with gradient background.
|
||||||
|
* Design & Developed by Tech4Biz Solutions
|
||||||
|
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type React from "react"
|
||||||
|
import { View, StyleSheet, TouchableWithoutFeedback, Keyboard } from "react-native"
|
||||||
|
import LinearGradient from "react-native-linear-gradient"
|
||||||
|
import { Colors, Spacing } from "../../../../shared/src/theme"
|
||||||
|
import BackButton from "./BackButton"
|
||||||
|
|
||||||
|
interface OnboardingContainerProps {
|
||||||
|
children: React.ReactNode
|
||||||
|
onBack: () => void
|
||||||
|
showBackButton?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const OnboardingContainer: React.FC<OnboardingContainerProps> = ({ children, onBack, showBackButton = true }) => {
|
||||||
|
return (
|
||||||
|
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
|
||||||
|
<LinearGradient
|
||||||
|
colors={Colors.backgroundGradient}
|
||||||
|
style={styles.container}
|
||||||
|
start={{ x: 0, y: 0 }}
|
||||||
|
end={{ x: 1, y: 1 }}
|
||||||
|
>
|
||||||
|
{showBackButton && (
|
||||||
|
<View style={styles.header}>
|
||||||
|
<BackButton onPress={onBack} />
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
<View style={styles.content}>{children}</View>
|
||||||
|
</LinearGradient>
|
||||||
|
</TouchableWithoutFeedback>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
paddingTop: Spacing.md,
|
||||||
|
paddingHorizontal: Spacing.lg,
|
||||||
|
paddingBottom: Spacing.md,
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
flex: 1,
|
||||||
|
paddingHorizontal: Spacing.lg,
|
||||||
|
justifyContent: "center",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export default OnboardingContainer
|
||||||
|
|
||||||
|
/*
|
||||||
|
* End of File: OnboardingContainer.tsx
|
||||||
|
* Design & Developed by Tech4Biz Solutions
|
||||||
|
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||||
|
*/
|
||||||
158
app/modules/Auth/components/PasswordScreen.tsx
Normal file
158
app/modules/Auth/components/PasswordScreen.tsx
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* File: PasswordScreen.tsx
|
||||||
|
* Description: Password creation screen with gradient background and shared components.
|
||||||
|
* Design & Developed by Tech4Biz Solutions
|
||||||
|
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type React from "react"
|
||||||
|
import { useState } from "react"
|
||||||
|
import { View, Text, StyleSheet, TextInput, TouchableOpacity } from "react-native"
|
||||||
|
import { Button } from "../../../../shared/src/components/Button"
|
||||||
|
import OnboardingContainer from "./OnboardingContainer"
|
||||||
|
import IconContainer from "./IconContainer"
|
||||||
|
import { Colors, Spacing, Typography } from "../../../../shared/src/theme"
|
||||||
|
import { showError } from "../../../../shared/src/utils/helpers/Toast"
|
||||||
|
import Icon from "react-native-vector-icons/Feather"
|
||||||
|
|
||||||
|
interface PasswordScreenProps {
|
||||||
|
onContinue: (password: string) => void
|
||||||
|
onBack: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const PasswordScreen: React.FC<PasswordScreenProps> = ({ onContinue, onBack }) => {
|
||||||
|
const [password, setPassword] = useState("")
|
||||||
|
const [confirmPassword, setConfirmPassword] = useState("")
|
||||||
|
const [isPasswordVisible, setPasswordVisible] = useState(false)
|
||||||
|
const [isConfirmPasswordVisible, setConfirmPasswordVisible] = useState(false)
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
|
||||||
|
const validatePassword = (pwd: string): boolean => {
|
||||||
|
return pwd.length >= 8
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleContinue = () => {
|
||||||
|
if (!password.trim() || !confirmPassword.trim()) {
|
||||||
|
showError("Validation Error", "Both password fields are required.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!validatePassword(password)) {
|
||||||
|
showError("Validation Error", "Password must be at least 8 characters long.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (password !== confirmPassword) {
|
||||||
|
showError("Validation Error", "Passwords do not match.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true)
|
||||||
|
setTimeout(() => {
|
||||||
|
setLoading(false)
|
||||||
|
onContinue(password)
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<OnboardingContainer onBack={onBack}>
|
||||||
|
<IconContainer iconName="lock" />
|
||||||
|
|
||||||
|
<Text style={styles.title}>Create a password</Text>
|
||||||
|
<Text style={styles.subtitle}>Password must be at least 8 characters</Text>
|
||||||
|
|
||||||
|
<View style={styles.inputContainer}>
|
||||||
|
<Icon name="lock" size={20} color={Colors.textSecondary} style={styles.inputIcon} />
|
||||||
|
<TextInput
|
||||||
|
placeholder="Password"
|
||||||
|
value={password}
|
||||||
|
onChangeText={setPassword}
|
||||||
|
style={styles.inputField}
|
||||||
|
secureTextEntry={!isPasswordVisible}
|
||||||
|
placeholderTextColor={Colors.textMuted}
|
||||||
|
/>
|
||||||
|
<TouchableOpacity onPress={() => setPasswordVisible(!isPasswordVisible)} style={styles.eyeIcon}>
|
||||||
|
<Icon name={isPasswordVisible ? "eye-off" : "eye"} size={22} color={Colors.textSecondary} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.inputContainer}>
|
||||||
|
<Icon name="lock" size={20} color={Colors.textSecondary} style={styles.inputIcon} />
|
||||||
|
<TextInput
|
||||||
|
placeholder="Confirm Password"
|
||||||
|
value={confirmPassword}
|
||||||
|
onChangeText={setConfirmPassword}
|
||||||
|
style={styles.inputField}
|
||||||
|
secureTextEntry={!isConfirmPasswordVisible}
|
||||||
|
placeholderTextColor={Colors.textMuted}
|
||||||
|
/>
|
||||||
|
<TouchableOpacity onPress={() => setConfirmPasswordVisible(!isConfirmPasswordVisible)} style={styles.eyeIcon}>
|
||||||
|
<Icon name={isConfirmPasswordVisible ? "eye-off" : "eye"} size={22} color={Colors.textSecondary} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Button title="Continue" onPress={handleContinue} style={styles.button} loading={loading} />
|
||||||
|
</OnboardingContainer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
title: {
|
||||||
|
fontFamily: Typography.fontFamily.bold,
|
||||||
|
fontSize: Typography.fontSize.title,
|
||||||
|
color: Colors.textPrimary,
|
||||||
|
textAlign: "center",
|
||||||
|
marginBottom: Spacing.sm,
|
||||||
|
},
|
||||||
|
subtitle: {
|
||||||
|
fontFamily: Typography.fontFamily.regular,
|
||||||
|
fontSize: Typography.fontSize.md,
|
||||||
|
color: Colors.textSecondary,
|
||||||
|
textAlign: "center",
|
||||||
|
marginBottom: Spacing.xl,
|
||||||
|
},
|
||||||
|
inputContainer: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
backgroundColor: Colors.inputBackground,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: Colors.border,
|
||||||
|
borderRadius: 12,
|
||||||
|
marginBottom: Spacing.md,
|
||||||
|
paddingHorizontal: Spacing.md,
|
||||||
|
paddingVertical: 2,
|
||||||
|
shadowColor: Colors.backButtonShadow,
|
||||||
|
shadowOffset: {
|
||||||
|
width: 0,
|
||||||
|
height: 1,
|
||||||
|
},
|
||||||
|
shadowOpacity: 0.05,
|
||||||
|
shadowRadius: 2,
|
||||||
|
elevation: 1,
|
||||||
|
},
|
||||||
|
inputIcon: {
|
||||||
|
marginRight: Spacing.sm,
|
||||||
|
},
|
||||||
|
inputField: {
|
||||||
|
flex: 1,
|
||||||
|
paddingVertical: Spacing.md,
|
||||||
|
fontSize: Typography.fontSize.md,
|
||||||
|
color: Colors.textPrimary,
|
||||||
|
},
|
||||||
|
eyeIcon: {
|
||||||
|
paddingLeft: Spacing.sm,
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
marginTop: Spacing.xl,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export default PasswordScreen
|
||||||
|
|
||||||
|
/*
|
||||||
|
* End of File: PasswordScreen.tsx
|
||||||
|
* Design & Developed by Tech4Biz Solutions
|
||||||
|
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||||
|
*/
|
||||||
@ -9,10 +9,12 @@ import React from 'react';
|
|||||||
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||||
import { LoginScreen, SetupBiometricScreen } from '../screens';
|
import { LoginScreen, SetupBiometricScreen } from '../screens';
|
||||||
import { Colors } from '../../../../shared/src/theme';
|
import { Colors } from '../../../../shared/src/theme';
|
||||||
|
import SignUpScreen from '../screens/SignUpScreen';
|
||||||
|
|
||||||
export type AuthStackParamList = {
|
export type AuthStackParamList = {
|
||||||
Login: undefined;
|
Login: undefined;
|
||||||
SetupBiometric: undefined;
|
SetupBiometric: undefined;
|
||||||
|
SignUpScreen:undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Stack = createNativeStackNavigator<AuthStackParamList>();
|
const Stack = createNativeStackNavigator<AuthStackParamList>();
|
||||||
@ -37,7 +39,12 @@ const AuthNavigator: React.FC = () => (
|
|||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name="SetupBiometric"
|
name="SetupBiometric"
|
||||||
component={SetupBiometricScreen}
|
component={SetupBiometricScreen}
|
||||||
options={{ title: 'Setup Biometric' }}
|
options={{ title: 'Setup Biometric' ,headerShown:false}}
|
||||||
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name="SignUpScreen"
|
||||||
|
component={SignUpScreen}
|
||||||
|
options={{ title: 'Setup Biometric' ,headerShown:false}}
|
||||||
/>
|
/>
|
||||||
</Stack.Navigator>
|
</Stack.Navigator>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||||
import { loginStart, loginSuccess, loginFailure } from './authSlice';
|
import { loginStart, loginSuccess, loginFailure } from './authSlice';
|
||||||
import { authAPI } from '../services/authAPI';
|
import { authAPI } from '../services/authAPI';
|
||||||
|
import { showError, showSuccess } from '../../../../shared/src/utils/helpers/Toast';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Thunk to login user
|
* Thunk to login user
|
||||||
@ -19,6 +20,12 @@ export const login = createAsyncThunk(
|
|||||||
dispatch(loginStart());
|
dispatch(loginStart());
|
||||||
const response:any = await authAPI.login(credentials.email, credentials.password,'web');
|
const response:any = await authAPI.login(credentials.email, credentials.password,'web');
|
||||||
console.log('user response',response)
|
console.log('user response',response)
|
||||||
|
if(response.data.message&& !response.data.success){
|
||||||
|
showError(response.data.message)
|
||||||
|
}
|
||||||
|
if(response.data.message&& response.data.success){
|
||||||
|
showSuccess(response.data.message)
|
||||||
|
}
|
||||||
if (response.ok && response.data &&response.data.data) {
|
if (response.ok && response.data &&response.data.data) {
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
dispatch(loginSuccess({...response.data.data.user,access_token:response.data.data.access_token}));
|
dispatch(loginSuccess({...response.data.data.user,access_token:response.data.data.access_token}));
|
||||||
@ -33,6 +40,7 @@ export const login = createAsyncThunk(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* End of File: authActions.ts
|
* End of File: authActions.ts
|
||||||
* Design & Developed by Tech4Biz Solutions
|
* Design & Developed by Tech4Biz Solutions
|
||||||
|
|||||||
@ -11,6 +11,7 @@ export const selectUser = (state: RootState) => state.auth.user;
|
|||||||
export const selectAuthLoading = (state: RootState) => state.auth.loading;
|
export const selectAuthLoading = (state: RootState) => state.auth.loading;
|
||||||
export const selectAuthError = (state: RootState) => state.auth.error;
|
export const selectAuthError = (state: RootState) => state.auth.error;
|
||||||
export const selectIsAuthenticated = (state: RootState) => state.auth.isAuthenticated;
|
export const selectIsAuthenticated = (state: RootState) => state.auth.isAuthenticated;
|
||||||
|
export const selectHospitals = (state: RootState) => state.hospital.hospitals;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* End of File: authSelectors.ts
|
* End of File: authSelectors.ts
|
||||||
|
|||||||
55
app/modules/Auth/redux/hospitalSlice.ts
Normal file
55
app/modules/Auth/redux/hospitalSlice.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* 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';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
interface Hospital {
|
||||||
|
hospital_id: string | null;
|
||||||
|
hospital_name: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auth state type
|
||||||
|
interface HospitalState {
|
||||||
|
hospitals: Hospital[] | null;
|
||||||
|
loading: boolean;
|
||||||
|
error: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: HospitalState = {
|
||||||
|
hospitals: null,
|
||||||
|
loading: false,
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auth slice for managing authentication state
|
||||||
|
*/
|
||||||
|
const hospitalSlice = createSlice({
|
||||||
|
name: 'hospital',
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
|
||||||
|
setHospitals(state, action: PayloadAction<Hospital[]>) {
|
||||||
|
state.hospitals = action.payload;
|
||||||
|
state.loading = false;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const {
|
||||||
|
setHospitals
|
||||||
|
} = hospitalSlice.actions;
|
||||||
|
|
||||||
|
export default hospitalSlice.reducer;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* End of File: authSlice.ts
|
||||||
|
* Design & Developed by Tech4Biz Solutions
|
||||||
|
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||||
|
*/
|
||||||
@ -22,6 +22,7 @@ import { login } from '../redux/authActions';
|
|||||||
import { showError } from '../../../../shared/src/utils/helpers/Toast';
|
import { showError } from '../../../../shared/src/utils/helpers/Toast';
|
||||||
import { validateEmail } from '../../../../shared/src/utils/validation/validators';
|
import { validateEmail } from '../../../../shared/src/utils/validation/validators';
|
||||||
import Icon from 'react-native-vector-icons/Feather';
|
import Icon from 'react-native-vector-icons/Feather';
|
||||||
|
import { useNavigation } from '@react-navigation/native';
|
||||||
|
|
||||||
const LoginScreen: React.FC = () => {
|
const LoginScreen: React.FC = () => {
|
||||||
const [email, setEmail] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
@ -29,6 +30,7 @@ const LoginScreen: React.FC = () => {
|
|||||||
const [isPasswordVisible, setPasswordVisible] = useState(false);
|
const [isPasswordVisible, setPasswordVisible] = useState(false);
|
||||||
const [loading, setLoading] = useState(false); // Add loading state
|
const [loading, setLoading] = useState(false); // Add loading state
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
const navigation=useNavigation();
|
||||||
|
|
||||||
const handleLogin = () => {
|
const handleLogin = () => {
|
||||||
if (!email.trim() || !password.trim()) {
|
if (!email.trim() || !password.trim()) {
|
||||||
@ -52,7 +54,7 @@ const LoginScreen: React.FC = () => {
|
|||||||
return (
|
return (
|
||||||
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
|
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<Text style={styles.title}>Radiologist Portal</Text>
|
<Text style={styles.title}>Radiologist</Text>
|
||||||
|
|
||||||
{/* Email Input */}
|
{/* Email Input */}
|
||||||
<View style={styles.inputContainer}>
|
<View style={styles.inputContainer}>
|
||||||
@ -92,6 +94,14 @@ const LoginScreen: React.FC = () => {
|
|||||||
style={styles.button}
|
style={styles.button}
|
||||||
loading={loading} // Pass loading state to button
|
loading={loading} // Pass loading state to button
|
||||||
/>
|
/>
|
||||||
|
<Text style={{alignSelf:'center',marginTop:15}}>-----OR------</Text>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
title="Sign Up"
|
||||||
|
onPress={()=>navigation.navigate('SignUpScreen')}
|
||||||
|
style={styles.button}
|
||||||
|
/>
|
||||||
|
|
||||||
</View>
|
</View>
|
||||||
</TouchableWithoutFeedback>
|
</TouchableWithoutFeedback>
|
||||||
);
|
);
|
||||||
|
|||||||
192
app/modules/Auth/screens/SignUpScreen.tsx
Normal file
192
app/modules/Auth/screens/SignUpScreen.tsx
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* File: SignUpScreen.tsx
|
||||||
|
* Description: Main signup screen that manages the onboarding flow through multiple steps.
|
||||||
|
* Design & Developed by Tech4Biz Solutions
|
||||||
|
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type React from "react"
|
||||||
|
import { useEffect, useState } from "react"
|
||||||
|
import { View, StyleSheet, StatusBar, Platform } from "react-native"
|
||||||
|
import { Colors } from "../../../../shared/src/theme"
|
||||||
|
import { useAppDispatch } from "../../../redux/hooks"
|
||||||
|
import { showError, showSuccess } from "../../../../shared/src/utils/helpers/Toast"
|
||||||
|
|
||||||
|
// Import all onboarding screens
|
||||||
|
import EmailScreen from "../components/EmailScreen"
|
||||||
|
import PasswordScreen from "../components/PasswordScreen"
|
||||||
|
import NameScreen from "../components/NameScreen"
|
||||||
|
import DocumentUploadScreen from "../components/DocumentUploadScreen"
|
||||||
|
import HospitalSelectionScreen from "../components/HospitalSelectionScreen"
|
||||||
|
import { authAPI } from "../services/authAPI"
|
||||||
|
import { setHospitals } from "../redux/hospitalSlice"
|
||||||
|
|
||||||
|
interface SignUpData {
|
||||||
|
email: string
|
||||||
|
password: string
|
||||||
|
first_name: string
|
||||||
|
last_name: string
|
||||||
|
username:string
|
||||||
|
id_photo_url: {}
|
||||||
|
hospital_id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SignUpScreenProps {
|
||||||
|
navigation: any
|
||||||
|
}
|
||||||
|
|
||||||
|
const SignUpScreen: React.FC<SignUpScreenProps> = ({ navigation }) => {
|
||||||
|
const [currentStep, setCurrentStep] = useState(0)
|
||||||
|
const [signUpData, setSignUpData] = useState<Partial<SignUpData>>({})
|
||||||
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
|
const steps = ["email", "password", "name", "document", "hospital"]
|
||||||
|
|
||||||
|
const handleEmailContinue = (email: string) => {
|
||||||
|
setSignUpData((prev) => ({ ...prev, email }))
|
||||||
|
setCurrentStep(1)
|
||||||
|
}
|
||||||
|
useEffect(()=>{
|
||||||
|
fetchHospitals();
|
||||||
|
},[])
|
||||||
|
console.log('signup called')
|
||||||
|
const fetchHospitals= async ()=>{
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
const response:any = await authAPI.gethospitals();
|
||||||
|
console.log('hospital response',response)
|
||||||
|
if(response.ok && response.data &&response.data.data) {
|
||||||
|
//@ts-ignore
|
||||||
|
dispatch(setHospitals(response.data.data))
|
||||||
|
} else {
|
||||||
|
showError('error while fetching hospital list')
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.log('error',error)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
const onSignUpComplete=async (payload:SignUpData)=>{
|
||||||
|
console.log('final payload',payload);
|
||||||
|
try {
|
||||||
|
const formData = new FormData();
|
||||||
|
let role='radiologist'
|
||||||
|
|
||||||
|
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 (Node.js or React Native: use File or Blob API accordingly)
|
||||||
|
const filePath = payload.id_photo_url.uri;
|
||||||
|
const file = {
|
||||||
|
uri: Platform.OS === 'android' ? filePath! : filePath!.replace('file://', ''), // for React Native
|
||||||
|
name: 'id_photo',
|
||||||
|
type: 'image/jpg',
|
||||||
|
};
|
||||||
|
formData.append('id_photo_url', file);
|
||||||
|
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')
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.log('error',error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const onBack=()=>{
|
||||||
|
navigation.goBack()
|
||||||
|
}
|
||||||
|
const handlePasswordContinue = (password: string) => {
|
||||||
|
setSignUpData((prev) => ({ ...prev, password }))
|
||||||
|
setCurrentStep(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleNameContinue = (firstName: string, lastName: string,userName:string) => {
|
||||||
|
setSignUpData((prev) => ({ ...prev, first_name : firstName, last_name : lastName ,username: userName}))
|
||||||
|
setCurrentStep(3)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDocumentContinue = (documentUri: string) => {
|
||||||
|
setSignUpData((prev) => ({ ...prev, id_photo_url:documentUri }))
|
||||||
|
setCurrentStep(4)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleHospitalContinue = (hospitalId: string) => {
|
||||||
|
const finalData: SignUpData = {
|
||||||
|
...signUpData,
|
||||||
|
hospital_id:hospitalId,
|
||||||
|
} as SignUpData
|
||||||
|
|
||||||
|
setSignUpData(finalData)
|
||||||
|
|
||||||
|
// Show success message
|
||||||
|
|
||||||
|
// Call completion handler
|
||||||
|
if (onSignUpComplete) {
|
||||||
|
onSignUpComplete(finalData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Here you would typically dispatch a signup action
|
||||||
|
// dispatch(signUp(finalData))
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleBack = () => {
|
||||||
|
if (currentStep > 0) {
|
||||||
|
setCurrentStep(currentStep - 1)
|
||||||
|
} else if (onBack) {
|
||||||
|
onBack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderCurrentStep = () => {
|
||||||
|
switch (currentStep) {
|
||||||
|
case 0:
|
||||||
|
return <EmailScreen onContinue={handleEmailContinue} onBack={handleBack} />
|
||||||
|
case 1:
|
||||||
|
return <PasswordScreen onContinue={handlePasswordContinue} onBack={handleBack} />
|
||||||
|
case 2:
|
||||||
|
return <NameScreen onContinue={handleNameContinue} onBack={handleBack} />
|
||||||
|
case 3:
|
||||||
|
return <DocumentUploadScreen onContinue={handleDocumentContinue} onBack={handleBack} />
|
||||||
|
case 4:
|
||||||
|
return <HospitalSelectionScreen onContinue={handleHospitalContinue} onBack={handleBack} />
|
||||||
|
default:
|
||||||
|
return <EmailScreen onContinue={handleEmailContinue} onBack={handleBack} />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<StatusBar barStyle="dark-content" backgroundColor={Colors.background} />
|
||||||
|
{renderCurrentStep()}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: Colors.background,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export default SignUpScreen
|
||||||
|
|
||||||
|
/*
|
||||||
|
* End of File: SignUpScreen.tsx
|
||||||
|
* Design & Developed by Tech4Biz Solutions
|
||||||
|
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||||
|
*/
|
||||||
@ -11,7 +11,6 @@ import { buildHeaders } from '../../../../shared/src/utils/helpers/Api';
|
|||||||
|
|
||||||
const api = create({
|
const api = create({
|
||||||
baseURL: BASE_URL, // TODO: Replace with actual endpoint
|
baseURL: BASE_URL, // TODO: Replace with actual endpoint
|
||||||
timeout: 3000,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -19,6 +18,10 @@ const api = create({
|
|||||||
*/
|
*/
|
||||||
export const authAPI = {
|
export const authAPI = {
|
||||||
login: (email: string, password: string,platform:string) => api.post('/api/auth/auth/login', { email, password,platform },buildHeaders()),
|
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' })),
|
||||||
// Add more endpoints as needed
|
// Add more endpoints as needed
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -37,7 +37,7 @@ export const fetchPatients = createAsyncThunk(
|
|||||||
try {
|
try {
|
||||||
dispatch(fetchCasesStart());
|
dispatch(fetchCasesStart());
|
||||||
const response :any = await caseAPI.getPatients(payload);
|
const response :any = await caseAPI.getPatients(payload);
|
||||||
console.log('response i got',response)
|
// console.log('response i got',response)
|
||||||
if (response.ok && response.data && response.data.data) {
|
if (response.ok && response.data && response.data.data) {
|
||||||
dispatch(fetchPatientsSuccess(response.data.data));
|
dispatch(fetchPatientsSuccess(response.data.data));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -364,7 +364,7 @@ const styles = StyleSheet.create({
|
|||||||
backgroundColor: Colors.background,
|
backgroundColor: Colors.background,
|
||||||
},
|
},
|
||||||
content: {
|
content: {
|
||||||
padding: Spacing.lg,
|
padding: Spacing.md,
|
||||||
},
|
},
|
||||||
summaryCard: {
|
summaryCard: {
|
||||||
marginBottom: Spacing.lg,
|
marginBottom: Spacing.lg,
|
||||||
|
|||||||
@ -101,7 +101,7 @@ const DashboardScreen: React.FC<{ navigation: any }> = ({ navigation }) => {
|
|||||||
|
|
||||||
filteredCases.forEach(case_ => {
|
filteredCases.forEach(case_ => {
|
||||||
const type = case_.type;
|
const type = case_.type;
|
||||||
const modality = case_.patientdetails.Modality;
|
const modality = case_.patientdetails?.Modality;
|
||||||
|
|
||||||
stats[type].total += 1;
|
stats[type].total += 1;
|
||||||
stats[type].modalityCounts[modality?.substring(1)] += 1;
|
stats[type].modalityCounts[modality?.substring(1)] += 1;
|
||||||
@ -151,7 +151,6 @@ const DashboardScreen: React.FC<{ navigation: any }> = ({ navigation }) => {
|
|||||||
const color = getCaseColor(type);
|
const color = getCaseColor(type);
|
||||||
|
|
||||||
if (stats.total === 0) return null; // Don't render card if no cases of this type
|
if (stats.total === 0) return null; // Don't render card if no cases of this type
|
||||||
console.log('counts data',stats.modalityCounts)
|
|
||||||
return (
|
return (
|
||||||
<Card key={type} style={styles.caseCard}>
|
<Card key={type} style={styles.caseCard}>
|
||||||
<View style={styles.cardHeader}>
|
<View style={styles.cardHeader}>
|
||||||
@ -187,7 +186,6 @@ console.log('counts data',stats.modalityCounts)
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('patients data', patientData);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
|
|||||||
@ -23,7 +23,6 @@ export const caseAPI = {
|
|||||||
getCases: (token:string) => api.get('/api/dicom/medpacks-sync/get-synced-medpacks-data',{},buildHeaders({token})),
|
getCases: (token:string) => api.get('/api/dicom/medpacks-sync/get-synced-medpacks-data',{},buildHeaders({token})),
|
||||||
// get patients data
|
// get patients data
|
||||||
getPatients: (token:string) =>{
|
getPatients: (token:string) =>{
|
||||||
console.log('token',token)
|
|
||||||
return api.get('/api/dicom/medpacks-sync/get-synced-medpacks-data',{},buildHeaders({token}))
|
return api.get('/api/dicom/medpacks-sync/get-synced-medpacks-data',{},buildHeaders({token}))
|
||||||
}
|
}
|
||||||
// Add more endpoints as needed
|
// Add more endpoints as needed
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import settingsReducer from '../modules/Profile/redux/settingsSlice';
|
|||||||
// Add other slices as needed, e.g.:
|
// Add other slices as needed, e.g.:
|
||||||
import dashboardReducer from '../modules/Dashboard/redux/dashboardSlice';
|
import dashboardReducer from '../modules/Dashboard/redux/dashboardSlice';
|
||||||
import authReducer from '../modules/Auth/redux/authSlice';
|
import authReducer from '../modules/Auth/redux/authSlice';
|
||||||
|
import hospitalReducer from '../modules/Auth/redux/hospitalSlice';
|
||||||
|
|
||||||
// RootState type for use throughout the app
|
// RootState type for use throughout the app
|
||||||
export type RootState = ReturnType<typeof rootReducer>;
|
export type RootState = ReturnType<typeof rootReducer>;
|
||||||
@ -21,7 +22,8 @@ const rootReducer = combineReducers({
|
|||||||
profile: profileReducer,
|
profile: profileReducer,
|
||||||
settings: settingsReducer,
|
settings: settingsReducer,
|
||||||
dashboard:dashboardReducer,
|
dashboard:dashboardReducer,
|
||||||
auth:authReducer
|
auth:authReducer,
|
||||||
|
hospital:hospitalReducer
|
||||||
// Add other reducers here
|
// Add other reducers here
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -7,16 +7,34 @@
|
|||||||
|
|
||||||
import { configureStore } from '@reduxjs/toolkit';
|
import { configureStore } from '@reduxjs/toolkit';
|
||||||
import rootReducer from './rootReducer';
|
import rootReducer from './rootReducer';
|
||||||
|
import { persistStore, persistReducer, PersistConfig } from 'redux-persist';
|
||||||
|
import storage from '@react-native-async-storage/async-storage';
|
||||||
|
|
||||||
|
|
||||||
|
const persistConfig: PersistConfig<any> = {
|
||||||
|
key: 'root',
|
||||||
|
storage,
|
||||||
|
version: 1,
|
||||||
|
whitelist: ['auth']
|
||||||
|
};
|
||||||
|
|
||||||
// Configure the Redux store
|
// Configure the Redux store
|
||||||
const store = configureStore({
|
const persistedReducer = persistReducer(persistConfig, rootReducer);
|
||||||
reducer: rootReducer,
|
|
||||||
// You can add middleware, devTools, and other options here if needed
|
export const store = configureStore({
|
||||||
|
reducer: persistedReducer,
|
||||||
|
middleware: (getDefaultMiddleware) =>
|
||||||
|
getDefaultMiddleware({
|
||||||
|
serializableCheck: {
|
||||||
|
ignoredActions: ['persist/PERSIST'], // Ignore persist actions to avoid the non-serializable warning
|
||||||
|
},
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// Export store and types for use throughout the app
|
// Export store and types for use throughout the app
|
||||||
export type AppDispatch = typeof store.dispatch;
|
export type AppDispatch = typeof store.dispatch;
|
||||||
export default store;
|
export const persistor = persistStore(store);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* End of File: store.ts
|
* End of File: store.ts
|
||||||
|
|||||||
50
package-lock.json
generated
50
package-lock.json
generated
@ -24,7 +24,9 @@
|
|||||||
"react-native": "0.79.0",
|
"react-native": "0.79.0",
|
||||||
"react-native-biometrics": "^3.0.1",
|
"react-native-biometrics": "^3.0.1",
|
||||||
"react-native-blob-util": "^0.22.2",
|
"react-native-blob-util": "^0.22.2",
|
||||||
|
"react-native-chart-kit": "^6.12.0",
|
||||||
"react-native-config": "^1.5.5",
|
"react-native-config": "^1.5.5",
|
||||||
|
"react-native-element-dropdown": "^2.12.4",
|
||||||
"react-native-gesture-handler": "^2.22.1",
|
"react-native-gesture-handler": "^2.22.1",
|
||||||
"react-native-image-picker": "^7.2.3",
|
"react-native-image-picker": "^7.2.3",
|
||||||
"react-native-keychain": "^10.0.0",
|
"react-native-keychain": "^10.0.0",
|
||||||
@ -9781,7 +9783,6 @@
|
|||||||
"version": "4.17.21",
|
"version": "4.17.21",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/lodash.debounce": {
|
"node_modules/lodash.debounce": {
|
||||||
@ -11154,6 +11155,15 @@
|
|||||||
"node": ">=8"
|
"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": {
|
"node_modules/picocolors": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||||
@ -11250,6 +11260,12 @@
|
|||||||
"node": ">=8"
|
"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": {
|
"node_modules/possible-typed-array-names": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
|
||||||
@ -11664,6 +11680,22 @@
|
|||||||
"url": "https://github.com/sponsors/isaacs"
|
"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": {
|
"node_modules/react-native-config": {
|
||||||
"version": "1.5.5",
|
"version": "1.5.5",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-config/-/react-native-config-1.5.5.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-config/-/react-native-config-1.5.5.tgz",
|
||||||
@ -11678,6 +11710,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"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": {
|
"node_modules/react-native-gesture-handler": {
|
||||||
"version": "2.27.1",
|
"version": "2.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.27.1.tgz",
|
||||||
|
|||||||
@ -26,7 +26,9 @@
|
|||||||
"react-native": "0.79.0",
|
"react-native": "0.79.0",
|
||||||
"react-native-biometrics": "^3.0.1",
|
"react-native-biometrics": "^3.0.1",
|
||||||
"react-native-blob-util": "^0.22.2",
|
"react-native-blob-util": "^0.22.2",
|
||||||
|
"react-native-chart-kit": "^6.12.0",
|
||||||
"react-native-config": "^1.5.5",
|
"react-native-config": "^1.5.5",
|
||||||
|
"react-native-element-dropdown": "^2.12.4",
|
||||||
"react-native-gesture-handler": "^2.22.1",
|
"react-native-gesture-handler": "^2.22.1",
|
||||||
"react-native-image-picker": "^7.2.3",
|
"react-native-image-picker": "^7.2.3",
|
||||||
"react-native-keychain": "^10.0.0",
|
"react-native-keychain": "^10.0.0",
|
||||||
|
|||||||
@ -43,9 +43,9 @@ const Button: React.FC<ButtonProps> = ({
|
|||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
button: {
|
button: {
|
||||||
backgroundColor: Colors.primary,
|
backgroundColor: Colors.primary,
|
||||||
paddingVertical: Spacing.md,
|
paddingVertical: Spacing.lg,
|
||||||
paddingHorizontal: Spacing.lg,
|
paddingHorizontal: Spacing.lg,
|
||||||
borderRadius: BorderRadius.md,
|
borderRadius: BorderRadius.xl,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
},
|
},
|
||||||
|
|||||||
@ -27,6 +27,33 @@ export const Colors = {
|
|||||||
inactiveState: '#F3F4F6', // Light Gray
|
inactiveState: '#F3F4F6', // Light Gray
|
||||||
shadow: 'rgba(91, 124, 230, 0.1)', // Blue Shadow
|
shadow: 'rgba(91, 124, 230, 0.1)', // Blue Shadow
|
||||||
gradient: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', // Gradient Background
|
gradient: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', // Gradient Background
|
||||||
|
|
||||||
|
// Primary colors
|
||||||
|
|
||||||
|
primaryDark: "#7C3AED",
|
||||||
|
primaryLight: "#A78BFA",
|
||||||
|
|
||||||
|
// Gradient colors
|
||||||
|
gradientStart: "#E0E7FF",
|
||||||
|
gradientMiddle: "#DDD6FE",
|
||||||
|
gradientEnd: "#E9D5FF",
|
||||||
|
|
||||||
|
// Background colors
|
||||||
|
backgroundGradient: ["#E0E7FF", "#DDD6FE", "#E9D5FF"],
|
||||||
|
|
||||||
|
// Text colors
|
||||||
|
|
||||||
|
textOnPrimary: "#FFFFFF",
|
||||||
|
|
||||||
|
// Border and input colors
|
||||||
|
borderFocus: "#8B5CF6",
|
||||||
|
inputBackground: "#FFFFFF",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Back button
|
||||||
|
backButtonBackground: "#FFFFFF",
|
||||||
|
backButtonShadow: "rgba(0, 0, 0, 0.1)",
|
||||||
};
|
};
|
||||||
|
|
||||||
// Usage: import { Colors } from './colors';
|
// Usage: import { Colors } from './colors';
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user