From 29940278e65c16bfbef142b9ae92f9f24f0162c7 Mon Sep 17 00:00:00 2001 From: yashwin-foxy Date: Thu, 24 Jul 2025 20:06:12 +0530 Subject: [PATCH] signup flow added for radiologist --- .cursor/rules/appflow.mdc | 165 +++++++ .cursor/rules/filestructure.mdc | 34 ++ .cursor/rules/folderstructure.mdc | 3 - .cursor/rules/projectstructure.mdc | 397 +++++++++++++++++ .cursor/rules/themestructure.mdc | 66 +++ .env | 4 +- App.tsx | 10 +- android/app/src/main/AndroidManifest.xml | 2 + app/modules/Auth/components/BackButton.tsx | 53 +++ .../Auth/components/DocumentUploadScreen.tsx | 401 ++++++++++++++++++ app/modules/Auth/components/EmailScreen.tsx | 127 ++++++ .../components/HospitalSelectionScreen.tsx | 231 ++++++++++ app/modules/Auth/components/IconContainer.tsx | 76 ++++ app/modules/Auth/components/NameScreen.tsx | 164 +++++++ .../Auth/components/OnboardingContainer.tsx | 64 +++ .../Auth/components/PasswordScreen.tsx | 158 +++++++ app/modules/Auth/navigation/AuthNavigator.tsx | 9 +- app/modules/Auth/redux/authActions.ts | 8 + app/modules/Auth/redux/authSelectors.ts | 1 + app/modules/Auth/redux/hospitalSlice.ts | 55 +++ app/modules/Auth/screens/LoginScreen.tsx | 12 +- app/modules/Auth/screens/SignUpScreen.tsx | 192 +++++++++ app/modules/Auth/services/authAPI.ts | 5 +- .../Dashboard/redux/dashboardActions.ts | 2 +- .../Dashboard/screens/DashBoardDetail.tsx | 2 +- .../Dashboard/screens/DashboardScreen.tsx | 4 +- app/modules/Dashboard/services/caseAPI.ts | 1 - app/redux/rootReducer.ts | 4 +- app/redux/store.ts | 26 +- package-lock.json | 50 ++- package.json | 2 + shared/src/components/Button/Button.tsx | 4 +- shared/src/theme/colors.ts | 27 ++ 33 files changed, 2335 insertions(+), 24 deletions(-) create mode 100644 .cursor/rules/appflow.mdc create mode 100644 .cursor/rules/filestructure.mdc delete mode 100644 .cursor/rules/folderstructure.mdc create mode 100644 .cursor/rules/projectstructure.mdc create mode 100644 .cursor/rules/themestructure.mdc create mode 100644 app/modules/Auth/components/BackButton.tsx create mode 100644 app/modules/Auth/components/DocumentUploadScreen.tsx create mode 100644 app/modules/Auth/components/EmailScreen.tsx create mode 100644 app/modules/Auth/components/HospitalSelectionScreen.tsx create mode 100644 app/modules/Auth/components/IconContainer.tsx create mode 100644 app/modules/Auth/components/NameScreen.tsx create mode 100644 app/modules/Auth/components/OnboardingContainer.tsx create mode 100644 app/modules/Auth/components/PasswordScreen.tsx create mode 100644 app/modules/Auth/redux/hospitalSlice.ts create mode 100644 app/modules/Auth/screens/SignUpScreen.tsx diff --git a/.cursor/rules/appflow.mdc b/.cursor/rules/appflow.mdc new file mode 100644 index 0000000..a1d35a7 --- /dev/null +++ b/.cursor/rules/appflow.mdc @@ -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 | ++------------------------+ \ No newline at end of file diff --git a/.cursor/rules/filestructure.mdc b/.cursor/rules/filestructure.mdc new file mode 100644 index 0000000..8bb80be --- /dev/null +++ b/.cursor/rules/filestructure.mdc @@ -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. \ No newline at end of file diff --git a/.cursor/rules/folderstructure.mdc b/.cursor/rules/folderstructure.mdc deleted file mode 100644 index 3dca909..0000000 --- a/.cursor/rules/folderstructure.mdc +++ /dev/null @@ -1,3 +0,0 @@ ---- -alwaysApply: true ---- diff --git a/.cursor/rules/projectstructure.mdc b/.cursor/rules/projectstructure.mdc new file mode 100644 index 0000000..3b32ae0 --- /dev/null +++ b/.cursor/rules/projectstructure.mdc @@ -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 \ No newline at end of file diff --git a/.cursor/rules/themestructure.mdc b/.cursor/rules/themestructure.mdc new file mode 100644 index 0000000..f673c3f --- /dev/null +++ b/.cursor/rules/themestructure.mdc @@ -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" diff --git a/.env b/.env index aa3551d..d568298 100644 --- a/.env +++ b/.env @@ -1,2 +1,2 @@ -BASE_URL='https://neoscan-backend.tech4bizsolutions.com' -# BASE_URL='http://192.168.1.87:3000' \ No newline at end of file +# BASE_URL='https://neoscan-backend.tech4bizsolutions.com' +BASE_URL='http://192.168.1.87:3000' \ No newline at end of file diff --git a/App.tsx b/App.tsx index c3b4b7b..fbd0bae 100644 --- a/App.tsx +++ b/App.tsx @@ -16,13 +16,14 @@ import { View, } from 'react-native'; 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 { Colors, } from 'react-native/Libraries/NewAppScreen'; import TabNavigator from './app/navigation/TabNavigator'; import AppNavigator from './app/navigation/AppNavigator'; import Toast from 'react-native-toast-message'; // Import Toast +import { PersistGate } from 'redux-persist/integration/react'; type SectionProps = PropsWithChildren<{ title: string; @@ -77,6 +78,7 @@ function App(): React.JSX.Element { // Wrap the app with Redux Provider to enable global state management return ( + - + + ); } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 92b52fb..0ef6108 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -3,6 +3,8 @@ + + void + style?: any +} + +const BackButton: React.FC = ({ onPress, style }) => { + return ( + + + + ) +} + +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. + */ diff --git a/app/modules/Auth/components/DocumentUploadScreen.tsx b/app/modules/Auth/components/DocumentUploadScreen.tsx new file mode 100644 index 0000000..502be43 --- /dev/null +++ b/app/modules/Auth/components/DocumentUploadScreen.tsx @@ -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 = ({ onContinue, onBack }) => { + const [selectedImage, setSelectedImage] = useState(null) + const [loading, setLoading] = useState(false) + + // Request camera permission for Android + const requestCameraPermission = async (): Promise => { + if (Platform.OS === 'android') { + try { + const granted = await PermissionsAndroid.request( + PermissionsAndroid.PERMISSIONS.CAMERA, + { + title: 'Camera Permission', + message: 'This app needs camera permission to capture images.', + buttonNeutral: 'Ask Me Later', + buttonNegative: 'Cancel', + buttonPositive: 'OK', + } + ) + return granted === PermissionsAndroid.RESULTS.GRANTED + } catch (err) { + console.warn(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 ( + + + + + + + + + + + Upload Image + Please upload your profile picture or identification image. + + + + {selectedImage ? ( + + + setSelectedImage(null)}> + + + Image Uploaded + {selectedImage.name} + + {getFileTypeDisplay(selectedImage.type)} + {selectedImage.size && ( + • {formatFileSize(selectedImage.size)} + )} + + + ) : ( + <> + + Tap to upload image + JPG, PNG supported + + )} + + + + {selectedImage && ( + + Change Image + + )} + +