diff --git a/.env b/.env
new file mode 100644
index 0000000..aa3551d
--- /dev/null
+++ b/.env
@@ -0,0 +1,2 @@
+BASE_URL='https://neoscan-backend.tech4bizsolutions.com'
+# BASE_URL='http://192.168.1.87:3000'
\ No newline at end of file
diff --git a/App.tsx b/App.tsx
index fdbe03f..c3b4b7b 100644
--- a/App.tsx
+++ b/App.tsx
@@ -15,15 +15,14 @@ import {
useColorScheme,
View,
} from 'react-native';
-
+import { Provider } from 'react-redux'; // Redux Provider
+import store from './app/redux/store'; // Redux store
import {
Colors,
- DebugInstructions,
- Header,
- LearnMoreLinks,
- ReloadInstructions,
} 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
type SectionProps = PropsWithChildren<{
title: string;
@@ -60,6 +59,8 @@ function App(): React.JSX.Element {
const backgroundStyle = {
backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
+ flex:1,
+ justifyContent:'center'
};
/*
@@ -73,18 +74,25 @@ function App(): React.JSX.Element {
*/
const safePadding = '5%';
+ // Wrap the app with Redux Provider to enable global state management
return (
-
-
-
-
+
+
+
+
+
+
+
);
}
const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ },
sectionContainer: {
marginTop: 32,
paddingHorizontal: 24,
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 5987a42..13fc140 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -1,6 +1,7 @@
apply plugin: "com.android.application"
apply plugin: "org.jetbrains.kotlin.android"
apply plugin: "com.facebook.react"
+apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle"
/**
* This is the configuration block to customize your React Native Android app.
@@ -117,3 +118,4 @@ dependencies {
implementation jscFlavor
}
}
+apply from: file("../../node_modules/react-native-vector-icons/fonts.gradle")
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index e189252..92b52fb 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -1,6 +1,8 @@
+
+
void;
- length?: number;
-}
-
-/**
- * PinInput - input for entering PIN code
- */
-const PinInput: React.FC = ({ value, onChange, length = 4 }) => {
- return (
-
- {Array.from({ length }).map((_, idx) => (
- {
- const newValue = value.substring(0, idx) + text + value.substring(idx + 1);
- onChange(newValue);
- }}
- maxLength={1}
- keyboardType="number-pad"
- secureTextEntry
- />
- ))}
-
- );
-};
-
-const styles = StyleSheet.create({
- container: {
- flexDirection: 'row',
- justifyContent: 'center',
- marginVertical: Spacing.md,
- },
- input: {
- width: 40,
- height: 48,
- marginHorizontal: Spacing.xs,
- backgroundColor: Colors.backgroundAlt,
- borderRadius: BorderRadius.md,
- textAlign: 'center',
- fontFamily: Typography.fontFamily.bold,
- fontSize: Typography.fontSize.lg,
- color: Colors.textPrimary,
- borderWidth: 1,
- borderColor: Colors.border,
- },
-});
-
-export default PinInput;
-
-/*
- * End of File: PinInput.tsx
- * Design & Developed by Tech4Biz Solutions
- * Copyright (c) Spurrin Innovations. All rights reserved.
- */
\ No newline at end of file
diff --git a/app/modules/Auth/navigation/AuthNavigator.tsx b/app/modules/Auth/navigation/AuthNavigator.tsx
index af5866c..3882bb7 100644
--- a/app/modules/Auth/navigation/AuthNavigator.tsx
+++ b/app/modules/Auth/navigation/AuthNavigator.tsx
@@ -6,7 +6,7 @@
*/
import React from 'react';
-import { createStackNavigator } from '@react-navigation/stack';
+import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { LoginScreen, SetupBiometricScreen } from '../screens';
import { Colors } from '../../../../shared/src/theme';
@@ -15,7 +15,7 @@ export type AuthStackParamList = {
SetupBiometric: undefined;
};
-const Stack = createStackNavigator();
+const Stack = createNativeStackNavigator();
/**
* AuthNavigator sets up stack navigation for Auth module
@@ -32,7 +32,7 @@ const AuthNavigator: React.FC = () => (
{
try {
dispatch(loginStart());
- const response = await authAPI.login(credentials.email, credentials.password);
- if (response.ok && response.data) {
- dispatch(loginSuccess(response.data));
+ const response:any = await authAPI.login(credentials.email, credentials.password,'web');
+ console.log('user response',response)
+ if (response.ok && response.data &&response.data.data) {
+ //@ts-ignore
+ dispatch(loginSuccess({...response.data.data.user,access_token:response.data.data.access_token}));
} else {
dispatch(loginFailure(response.problem || 'Unknown error'));
return rejectWithValue(response.problem || 'Unknown error');
diff --git a/app/modules/Auth/redux/authSelectors.ts b/app/modules/Auth/redux/authSelectors.ts
index 4dfccde..9fd5547 100644
--- a/app/modules/Auth/redux/authSelectors.ts
+++ b/app/modules/Auth/redux/authSelectors.ts
@@ -5,7 +5,7 @@
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
-import { RootState } from 'app/redux/rootReducer';
+import { RootState } from '../../../../app/redux/rootReducer';
export const selectUser = (state: RootState) => state.auth.user;
export const selectAuthLoading = (state: RootState) => state.auth.loading;
diff --git a/app/modules/Auth/redux/authSlice.ts b/app/modules/Auth/redux/authSlice.ts
index 301c6c9..4a3f0d7 100644
--- a/app/modules/Auth/redux/authSlice.ts
+++ b/app/modules/Auth/redux/authSlice.ts
@@ -8,15 +8,50 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
// Sample user type
-export interface User {
- id: string;
- name: string;
+
+interface NotificationPreferences {
+ critical_alerts: {
+ email: boolean;
+ in_app: boolean;
+ push: boolean;
+ sms: boolean;
+ };
+ system_notifications: {
+ push: boolean;
+ email: boolean;
+ in_app: boolean;
+ };
+}
+
+interface DashboardSettings {
+ theme: string;
+ language: string;
+ timezone: string;
+}
+
+interface UserProfile {
+ accent_color: string | null;
+ dashboard_role: string;
+ dashboard_settings: DashboardSettings;
+ display_name: string;
email: string;
+ first_name: string;
+ last_name: string;
+ hospital_id: string;
+ notification_preferences: NotificationPreferences;
+ onboarded: boolean;
+ onboarding_completed: boolean;
+ onboarding_message: string;
+ onboarding_step: number;
+ profile_photo_url: string | null;
+ theme_color: string | null;
+ user_id: string;
+ access_token: string;
}
// Auth state type
interface AuthState {
- user: User | null;
+ user: UserProfile | null;
loading: boolean;
error: string | null;
isAuthenticated: boolean;
@@ -40,7 +75,7 @@ const authSlice = createSlice({
state.loading = true;
state.error = null;
},
- loginSuccess(state, action: PayloadAction) {
+ loginSuccess(state, action: PayloadAction) {
state.user = action.payload;
state.isAuthenticated = true;
state.loading = false;
diff --git a/app/modules/Auth/screens/LoginScreen.tsx b/app/modules/Auth/screens/LoginScreen.tsx
index 4f47e47..7ed0651 100644
--- a/app/modules/Auth/screens/LoginScreen.tsx
+++ b/app/modules/Auth/screens/LoginScreen.tsx
@@ -1,50 +1,99 @@
/*
* File: LoginScreen.tsx
- * Description: Login screen for user authentication
+ * Description: Login screen with a loading indicator on the submit button.
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React, { useState } from 'react';
-import { View, Text, StyleSheet } from 'react-native';
-import { TextInput } from '../../../../shared/src/components/Input';
+import {
+ View,
+ Text,
+ StyleSheet,
+ TouchableWithoutFeedback,
+ Keyboard,
+ TouchableOpacity,
+ TextInput, // Using default TextInput for container styling
+} from 'react-native';
import { Button } from '../../../../shared/src/components/Button';
import { Colors, Spacing, Typography } from '../../../../shared/src/theme';
-import { useDispatch } from 'react-redux';
+import { useAppDispatch } from '../../../redux/hooks';
import { login } from '../redux/authActions';
+import { showError } from '../../../../shared/src/utils/helpers/Toast';
+import { validateEmail } from '../../../../shared/src/utils/validation/validators';
+import Icon from 'react-native-vector-icons/Feather';
-/**
- * LoginScreen - allows user to login with email and password
- */
const LoginScreen: React.FC = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
- const dispatch = useDispatch();
+ const [isPasswordVisible, setPasswordVisible] = useState(false);
+ const [loading, setLoading] = useState(false); // Add loading state
+ const dispatch = useAppDispatch();
const handleLogin = () => {
+ if (!email.trim() || !password.trim()) {
+ showError('Validation Error', 'Email and password are required.');
+ return;
+ }
+ if (!validateEmail(email)) {
+ showError('Validation Error', 'Please enter a valid email address.');
+ return;
+ }
+
+ setLoading(true); // Start loading
dispatch(login({ email, password }));
+
+ // Simulate a network request for 4 seconds
+ setTimeout(() => {
+ setLoading(false); // Stop loading after 4 seconds
+ }, 4000);
};
return (
-
- Radiologist Portal
-
-
-
-
+
+
+ Radiologist Portal
+
+ {/* Email Input */}
+
+
+
+
+
+ {/* Password Input */}
+
+
+
+ setPasswordVisible(!isPasswordVisible)}
+ style={styles.eyeIcon}>
+
+
+
+
+
+
+
);
};
@@ -62,8 +111,28 @@ const styles = StyleSheet.create({
marginBottom: Spacing.xl,
textAlign: 'center',
},
- input: {
+ inputContainer: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ backgroundColor: Colors.backgroundAlt,
+ borderWidth: 1,
+ borderColor: Colors.border,
+ borderRadius: 8,
marginBottom: Spacing.md,
+ paddingHorizontal: Spacing.md,
+ paddingVertical:2
+ },
+ inputIcon: {
+ marginRight: Spacing.sm,
+ },
+ inputField: {
+ flex: 1,
+ paddingVertical: Spacing.md,
+ fontSize: Typography.fontSize.md,
+ color: Colors.textPrimary,
+ },
+ eyeIcon: {
+ paddingLeft: Spacing.sm,
},
button: {
marginTop: Spacing.md,
diff --git a/app/modules/Auth/screens/SetupBiometricScreen.tsx b/app/modules/Auth/screens/SetupBiometricScreen.tsx
index 718adbd..83be4f4 100644
--- a/app/modules/Auth/screens/SetupBiometricScreen.tsx
+++ b/app/modules/Auth/screens/SetupBiometricScreen.tsx
@@ -1,66 +1,192 @@
/*
* File: SetupBiometricScreen.tsx
- * Description: Screen for setting up biometric authentication
+ * Description: Screen for setting up and authenticating with device biometrics or PIN.
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
-import React from 'react';
-import { View, Text, StyleSheet } from 'react-native';
-import { Button } from '../../../../shared/src/components/Button';
-import { Colors, Spacing, Typography } from '../../../../shared/src/theme';
-import { useBiometric } from '../hooks/useBiometric';
+import React, { useState, useEffect } from 'react';
+import {
+ View,
+ Text,
+ TouchableOpacity,
+ Alert,
+ StyleSheet,
+ SafeAreaView,
+} from 'react-native';
+import ReactNativeBiometrics, {
+ BiometryTypes,
+ BiometryType,
+} from 'react-native-biometrics';
+import AsyncStorage from '@react-native-async-storage/async-storage';
+import { Colors, Spacing, Typography, Shadows } from '../../../../shared/src/theme';
+import { BASE_URL } from '../../../constants/URLS';
+
+const PAYLOAD = 'neo-scan-secure-payload'; // A static payload for signature
/**
- * SetupBiometricScreen - allows user to setup biometric authentication
+ * SetupBiometricScreen - Manages user authentication using the device's
+ * built-in biometrics or PIN/password.
*/
const SetupBiometricScreen: React.FC = () => {
- const { authenticate } = useBiometric();
+ const [isAuthenticated, setIsAuthenticated] = useState(false);
+ const [biometryType, setBiometryType] = useState(null);
+ const [isAvailable, setIsAvailable] = useState(false);
+ const rnBiometrics = new ReactNativeBiometrics({ allowDeviceCredentials: true });
+ console.log('base url',BASE_URL)
+ useEffect(() => {
+ checkBiometricAvailability();
+ checkAuthStatus();
+ }, []);
- const handleSetup = async () => {
- await authenticate();
- // TODO: Handle result and navigate
+ const checkBiometricAvailability = async () => {
+ try {
+ const { available, biometryType: bioType } = await rnBiometrics.isSensorAvailable();
+ setIsAvailable(available);
+ setBiometryType(bioType || null);
+ } catch (error) {
+ console.error('Biometric check failed:', error);
+ Alert.alert('Error', 'Could not check for biometric features.');
+ }
+ };
+
+ const checkAuthStatus = async () => {
+ try {
+ const authStatus = await AsyncStorage.getItem('isAuthenticated');
+ setIsAuthenticated(authStatus === 'true');
+ } catch (error) {
+ console.error('Auth status check failed:', error);
+ }
+ };
+
+ const handleSuccessfulAuth = async () => {
+ setIsAuthenticated(true);
+ await AsyncStorage.setItem('isAuthenticated', 'true');
+ Alert.alert('Success', 'Authentication successful!');
+ };
+
+ const authenticateUser = async () => {
+ if (!isAvailable) {
+ Alert.alert('Not Available', 'Biometrics are not supported on this device.');
+ return;
+ }
+
+ try {
+ // Check if keys exist. If not, create them.
+ const { keysExist } = await rnBiometrics.biometricKeysExist();
+ if (!keysExist) {
+ await rnBiometrics.createKeys();
+ }
+
+ // Create a signature to authenticate. This will fallback to device PIN.
+ const { success, signature } = await rnBiometrics.createSignature({
+ promptMessage: 'Authenticate',
+ payload: PAYLOAD,
+ });
+
+ if (success && signature) {
+ // In a real app, you would verify the signature on your server.
+ // For this example, we'll assume it's valid.
+ await handleSuccessfulAuth();
+ } else {
+ Alert.alert('Authentication Failed', 'You could not be authenticated.');
+ }
+ } catch (error) {
+ console.error('Authentication failed:', error);
+ Alert.alert('Error', 'An unexpected error occurred during authentication.');
+ }
+ };
+
+ const getBiometricTypeText = () => {
+ switch (biometryType) {
+ case BiometryTypes.TouchID: return 'Touch ID';
+ case BiometryTypes.FaceID: return 'Face ID';
+ case BiometryTypes.Biometrics: return 'Fingerprint';
+ default: return 'Device Credentials';
+ }
+ };
+
+ const logout = async () => {
+ try {
+ setIsAuthenticated(false);
+ await AsyncStorage.setItem('isAuthenticated', 'false');
+ } catch (error) {
+ console.error('Logout failed:', error);
+ }
};
return (
-
- Setup Biometric Login
- Enable Face ID or Touch ID for quick and secure login.
-
-
+
+ {isAuthenticated ? (
+
+ Welcome!
+ You are successfully authenticated.
+
+ Logout
+
+
+ ) : (
+
+ Authentication Required
+ {isAvailable ? (
+
+ Use {getBiometricTypeText()} or your device PIN to continue.
+
+ ) : (
+
+ Biometrics not available. Please use your device PIN to continue.
+
+ )}
+
+ Authenticate
+
+
+ )}
+
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
- backgroundColor: Colors.background,
+ backgroundColor: Colors.backgroundAlt,
+ },
+ content: {
+ flex: 1,
justifyContent: 'center',
+ alignItems: 'center',
padding: Spacing.lg,
},
title: {
fontFamily: Typography.fontFamily.bold,
fontSize: Typography.fontSize.title,
- color: Colors.primary,
- marginBottom: Spacing.lg,
+ color: Colors.textPrimary,
+ marginBottom: Spacing.md,
textAlign: 'center',
},
- info: {
+ subtitle: {
fontFamily: Typography.fontFamily.regular,
fontSize: Typography.fontSize.md,
color: Colors.textSecondary,
- marginBottom: Spacing.xl,
textAlign: 'center',
+ marginBottom: Spacing.xl,
+ paddingHorizontal: Spacing.lg,
},
button: {
- marginTop: Spacing.md,
+ backgroundColor: Colors.primary,
+ paddingHorizontal: Spacing.xl,
+ paddingVertical: Spacing.md,
+ borderRadius: 8,
+ marginVertical: Spacing.sm,
+ ...Shadows.sm,
+ minWidth: '80%',
+ alignItems: 'center',
+ },
+ buttonText: {
+ color: Colors.background,
+ fontSize: Typography.fontSize.md,
+ fontFamily: Typography.fontFamily.bold,
},
});
-export default SetupBiometricScreen;
-
-/*
- * End of File: SetupBiometricScreen.tsx
- * Design & Developed by Tech4Biz Solutions
- * Copyright (c) Spurrin Innovations. All rights reserved.
- */
\ No newline at end of file
+export default SetupBiometricScreen;
\ No newline at end of file
diff --git a/app/modules/Auth/services/authAPI.ts b/app/modules/Auth/services/authAPI.ts
index 8536f5d..7afe6a8 100644
--- a/app/modules/Auth/services/authAPI.ts
+++ b/app/modules/Auth/services/authAPI.ts
@@ -6,17 +6,19 @@
*/
import { create } from 'apisauce';
+import { BASE_URL } from '../../../constants/URLS';
+import { buildHeaders } from '../../../../shared/src/utils/helpers/Api';
const api = create({
- baseURL: 'https://api.example.com', // TODO: Replace with actual endpoint
- timeout: 10000,
+ baseURL: BASE_URL, // TODO: Replace with actual endpoint
+ timeout: 3000,
});
/**
* login - authenticates user with email and password
*/
export const authAPI = {
- login: (email: string, password: string) => api.post('/login', { email, password }),
+ login: (email: string, password: string,platform:string) => api.post('/api/auth/auth/login', { email, password,platform },buildHeaders()),
// Add more endpoints as needed
};
diff --git a/app/modules/Auth/services/index.ts b/app/modules/Auth/services/index.ts
index cb3d52d..c4028b5 100644
--- a/app/modules/Auth/services/index.ts
+++ b/app/modules/Auth/services/index.ts
@@ -5,7 +5,7 @@
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
-export * from './authAPI';
+export * from './';
export * from './biometricService';
/*
diff --git a/app/modules/CaseReview/navigation/CaseReviewNavigator.tsx b/app/modules/CaseReview/navigation/CaseReviewNavigator.tsx
index 15f8e7c..96a9f67 100644
--- a/app/modules/CaseReview/navigation/CaseReviewNavigator.tsx
+++ b/app/modules/CaseReview/navigation/CaseReviewNavigator.tsx
@@ -26,14 +26,12 @@ const CaseReviewNavigator: React.FC = () => (
initialRouteName="CaseDetails"
screenOptions={{
headerStyle: { backgroundColor: Colors.primary },
- headerTintColor: Colors.background,
- headerTitleStyle: { fontWeight: 'bold' },
}}
>
state.caseReview.selectedCase;
export const selectCaseReviewLoading = (state: RootState) => state.caseReview.loading;
diff --git a/app/modules/CaseReview/screens/CaseDetailsScreen.tsx b/app/modules/CaseReview/screens/CaseDetailsScreen.tsx
index 00e2f1d..d8e92ac 100644
--- a/app/modules/CaseReview/screens/CaseDetailsScreen.tsx
+++ b/app/modules/CaseReview/screens/CaseDetailsScreen.tsx
@@ -18,22 +18,23 @@ import { useAIOverlay } from '../hooks/useAIOverlay';
* CaseDetailsScreen - shows patient info, AI findings, and navigation to DICOM viewer
*/
const CaseDetailsScreen: React.FC = ({ navigation }) => {
- const caseData = useSelector(selectSelectedCase);
- const { findings, confidence } = useAIOverlay();
+ // const caseData = useSelector(selectSelectedCase);
+ // const { findings, confidence } = useAIOverlay();
- if (!caseData) {
- return No case selected.;
- }
+ // if (!caseData) {
+ // return No case selected.;
+ // }
return (
-
+ {/*
{caseData.patientName}
AI Findings:
{findings}
Confidence: {confidence}%
+ */}
+ Coming Soon..
);
};
@@ -44,6 +45,7 @@ const styles = StyleSheet.create({
backgroundColor: Colors.background,
justifyContent: 'center',
padding: Spacing.lg,
+ alignItems:'center'
},
title: {
fontFamily: Typography.fontFamily.bold,
diff --git a/app/modules/Dashboard/navigation/DashboardNavigator.tsx b/app/modules/Dashboard/navigation/DashboardNavigator.tsx
index e9e2d56..d464839 100644
--- a/app/modules/Dashboard/navigation/DashboardNavigator.tsx
+++ b/app/modules/Dashboard/navigation/DashboardNavigator.tsx
@@ -25,14 +25,12 @@ const DashboardNavigator: React.FC = () => (
initialRouteName="Dashboard"
screenOptions={{
headerStyle: { backgroundColor: Colors.primary },
- headerTintColor: Colors.background,
- headerTitleStyle: { fontWeight: 'bold' },
}}
>
{/* Add more screens here */}
diff --git a/app/modules/Dashboard/redux/dashboardSelectors.ts b/app/modules/Dashboard/redux/dashboardSelectors.ts
index 3a9d462..5ce76a9 100644
--- a/app/modules/Dashboard/redux/dashboardSelectors.ts
+++ b/app/modules/Dashboard/redux/dashboardSelectors.ts
@@ -5,7 +5,7 @@
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
-import { RootState } from 'app/redux/rootReducer';
+import { RootState } from '../../../../app/redux/rootReducer';
export const selectCaseQueue = (state: RootState) => state.dashboard.caseQueue;
export const selectDashboardLoading = (state: RootState) => state.dashboard.loading;
diff --git a/app/modules/Dashboard/screens/DashBoardDetail.tsx b/app/modules/Dashboard/screens/DashBoardDetail.tsx
new file mode 100644
index 0000000..942a074
--- /dev/null
+++ b/app/modules/Dashboard/screens/DashBoardDetail.tsx
@@ -0,0 +1,44 @@
+/*
+ * File: DashboardScreen.tsx
+ * Description: Sample dashboard screen using Clinical Blue Interface and shared components
+ * Design & Developed by Tech4Biz Solutions
+ * Copyright (c) Spurrin Innovations. All rights reserved.
+ */
+
+import React, { useState } from 'react';
+import { View, Text, StyleSheet, ScrollView } from 'react-native';
+import { Colors, Spacing, Typography } from '../../../../shared/src/theme';
+
+/**
+ * DashboardScreen demonstrates usage of all shared, themed components.
+ * This is a reference implementation for other modules.
+ */
+const DashBoardDetail: React.FC = () => {
+
+
+ return (
+
+ Dashboard Detail screen
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: Colors.background,
+ },
+ content: {
+ padding: Spacing.lg,
+ flex:1
+ }
+});
+
+export default DashBoardDetail;
+
+/*
+ * End of File: DashboardScreen.tsx
+ * Design & Developed by Tech4Biz Solutions
+ * Copyright (c) Spurrin Innovations. All rights reserved.
+ */
\ No newline at end of file
diff --git a/app/modules/Dashboard/screens/DashboardScreen.tsx b/app/modules/Dashboard/screens/DashboardScreen.tsx
index 3acaa22..30d6301 100644
--- a/app/modules/Dashboard/screens/DashboardScreen.tsx
+++ b/app/modules/Dashboard/screens/DashboardScreen.tsx
@@ -1,25 +1,26 @@
/*
* File: DashboardScreen.tsx
- * Description: Sample dashboard screen using Clinical Blue Interface and shared components
+ * Description: Main dashboard screen with a custom header and themed components.
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React, { useState } from 'react';
import { View, Text, StyleSheet, ScrollView } from 'react-native';
-import { Button } from 'shared/src/components/Button';
-import { Card, InfoCard } from 'shared/src/components/Card';
-import { TextInput, SearchInput } from 'shared/src/components/Input';
-import { Modal, ConfirmModal, AlertModal } from 'shared/src/components/Modal';
-import { Spinner, LoadingOverlay } from 'shared/src/components/Loading';
-import { CustomIcon, IconButton } from 'shared/src/components/Icons';
-import { Colors, Spacing, Typography } from 'shared/src/theme';
+import { Button } from '../../../../shared/src/components/Button';
+import { Card, InfoCard } from '../../../../shared/src/components/Card';
+import { TextInput, SearchInput } from '../../../../shared/src/components/Input';
+import { Modal, ConfirmModal, AlertModal } from '../../../../shared/src/components/Modal';
+import { Spinner, LoadingOverlay } from '../../../../shared/src/components/Loading';
+import { CustomIcon, IconButton } from '../../../../shared/src/components/Icons';
+import { CustomHeader } from '../../../../shared/src/components/Header'; // Import CustomHeader
+import { Colors, Spacing, Typography } from '../../../../shared/src/theme';
/**
- * DashboardScreen demonstrates usage of all shared, themed components.
- * This is a reference implementation for other modules.
+ * DashboardScreen - The primary landing screen after login, featuring a custom header,
+ * case summaries, and navigation to other parts of the app.
*/
-const DashboardScreen: React.FC = () => {
+const DashboardScreen: React.FC<{ navigation: any }> = ({ navigation }) => {
const [modalVisible, setModalVisible] = useState(false);
const [confirmVisible, setConfirmVisible] = useState(false);
const [alertVisible, setAlertVisible] = useState(false);
@@ -28,66 +29,96 @@ const DashboardScreen: React.FC = () => {
const [input, setInput] = useState('');
return (
-
- Dashboard
-
+
-
- Welcome, Dr. Smith
- On-Call Status: ACTIVE
-
-
- Critical Cases
-
-
- Bed 3 - Hemorrhage (93% AI)
-
-
-
- Routine Cases
-
-
- Bed 12 - Headache (99% AI)
- setConfirmVisible(true)} />
-
-
-
-
+
+
+
+ Welcome, Dr. Smith
+
+ On-Call Status: ACTIVE
+
+
+
+ Critical Cases
+
+
+ Bed 3 - Hemorrhage (93% AI)
+ setModalVisible(true)}
+ style={styles.button}
+ />
+
+
+
+ Routine Cases
+
+
+ Bed 12 - Headache (99% AI)
+ setConfirmVisible(true)}
+ />
+
+
+
+ setAlertVisible(true)}
+ style={styles.button}
+ />
+ setLoading(true)}
+ style={styles.button}
+ />
+ {/* */}
+ {/* Modals */}
+ setModalVisible(false)}>
+ Critical Case Details
+ Patient: John Doe, 45M
+ setModalVisible(false)}
+ style={styles.button}
+ />
+
+ {
+ setConfirmVisible(false);
+ }}
+ onCancel={() => setConfirmVisible(false)}
+ />
+ setAlertVisible(false)}
+ />
+
+
+
);
};
@@ -99,13 +130,6 @@ const styles = StyleSheet.create({
content: {
padding: Spacing.lg,
},
- header: {
- fontFamily: Typography.fontFamily.bold,
- fontSize: Typography.fontSize.title,
- color: Colors.primary,
- marginBottom: Spacing.lg,
- textAlign: 'center',
- },
search: {
marginBottom: Spacing.md,
},
diff --git a/app/modules/Dashboard/services/caseAPI.ts b/app/modules/Dashboard/services/caseAPI.ts
index 4e71e5a..5ebc261 100644
--- a/app/modules/Dashboard/services/caseAPI.ts
+++ b/app/modules/Dashboard/services/caseAPI.ts
@@ -6,17 +6,21 @@
*/
import { create } from 'apisauce';
+import { BASE_URL } from '../../../constants/URLS';
+import { buildHeaders } from '../../../../shared/src/utils/helpers/Api';
+
const api = create({
- baseURL: 'https://api.example.com', // TODO: Replace with actual endpoint
- timeout: 10000,
+ baseURL: BASE_URL, // TODO: Replace with actual endpoint
+ timeout: 3000,
});
/**
* getCases - fetches the list of cases from the server
*/
+
export const caseAPI = {
- getCases: () => api.get('/cases'),
+ getCases: (token:string) => api.get('/api/dicom/medpacks-sync/get-synced-medpacks-data',{},buildHeaders({token})),
// Add more endpoints as needed
};
diff --git a/app/modules/Profile/components/PreferencesForm.tsx b/app/modules/Profile/components/PreferencesForm.tsx
index ada5d76..2920d00 100644
--- a/app/modules/Profile/components/PreferencesForm.tsx
+++ b/app/modules/Profile/components/PreferencesForm.tsx
@@ -7,7 +7,7 @@
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
-import { Colors, Spacing, Typography } from 'shared/src/theme';
+import { Colors, Spacing, Typography } from '../../../../shared/src/theme';
interface PreferencesFormProps {
preferences: any;
diff --git a/app/modules/Profile/components/ProfileHeader.tsx b/app/modules/Profile/components/ProfileHeader.tsx
index 092b471..9f260d7 100644
--- a/app/modules/Profile/components/ProfileHeader.tsx
+++ b/app/modules/Profile/components/ProfileHeader.tsx
@@ -7,7 +7,7 @@
import React from 'react';
import { View, Text, Image, StyleSheet } from 'react-native';
-import { Colors, Spacing, Typography } from 'shared/src/theme';
+import { Colors, Spacing, Typography } from '../../../../shared/src/theme';
interface ProfileHeaderProps {
user: { name: string; avatar?: string };
diff --git a/app/modules/Profile/components/SecuritySettings.tsx b/app/modules/Profile/components/SecuritySettings.tsx
index 12e97e3..7d1abd7 100644
--- a/app/modules/Profile/components/SecuritySettings.tsx
+++ b/app/modules/Profile/components/SecuritySettings.tsx
@@ -7,7 +7,7 @@
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
-import { Colors, Spacing, Typography } from 'shared/src/theme';
+import { Colors, Spacing, Typography } from '../../../../shared/src/theme';
interface SecuritySettingsProps {
security: any;
diff --git a/app/modules/Profile/components/SettingsPanel.tsx b/app/modules/Profile/components/SettingsPanel.tsx
index b2c65fb..63f0528 100644
--- a/app/modules/Profile/components/SettingsPanel.tsx
+++ b/app/modules/Profile/components/SettingsPanel.tsx
@@ -7,7 +7,7 @@
import React from 'react';
import { View, Text, StyleSheet, Switch } from 'react-native';
-import { Colors, Spacing, Typography } from 'shared/src/theme';
+import { Colors, Spacing, Typography } from '../../../../shared/src/theme';
interface SettingsPanelProps {
settings: { notificationsEnabled: boolean; darkMode: boolean; language: string };
diff --git a/app/modules/Profile/navigation/ProfileNavigator.tsx b/app/modules/Profile/navigation/ProfileNavigator.tsx
index 8dfce43..74326a7 100644
--- a/app/modules/Profile/navigation/ProfileNavigator.tsx
+++ b/app/modules/Profile/navigation/ProfileNavigator.tsx
@@ -8,7 +8,7 @@
import React from 'react';
import { createStackNavigator } from '@react-navigation/stack';
import { ProfileScreen, SettingsScreen, PreferencesScreen } from '../screens';
-import { Colors } from 'shared/src/theme';
+import { Colors } from '../../../../shared/src/theme';
export type ProfileStackParamList = {
Profile: undefined;
@@ -26,14 +26,12 @@ const ProfileNavigator: React.FC = () => (
initialRouteName="Profile"
screenOptions={{
headerStyle: { backgroundColor: Colors.primary },
- headerTintColor: Colors.background,
- headerTitleStyle: { fontWeight: 'bold' },
}}
>
state.profile.userProfile;
+export const selectUserProfile = (state: RootState) => state.auth.user;
export const selectProfileLoading = (state: RootState) => state.profile.loading;
export const selectProfileError = (state: RootState) => state.profile.error;
export const selectUserSettings = (state: RootState) => state.settings.settings;
diff --git a/app/modules/Profile/redux/profileSlice.ts b/app/modules/Profile/redux/profileSlice.ts
index ace93db..6b08abb 100644
--- a/app/modules/Profile/redux/profileSlice.ts
+++ b/app/modules/Profile/redux/profileSlice.ts
@@ -14,6 +14,7 @@ export interface UserProfile {
email: string;
avatar?: string;
role?: string;
+ location?: string;
}
// Profile state type
@@ -24,7 +25,14 @@ interface ProfileState {
}
const initialState: ProfileState = {
- userProfile: null,
+ userProfile: {
+ id:'1',
+ name:'laxman h',
+ email:'laxman.halaki@tech4biz.org',
+ avatar:'',
+ role:'doctor'
+
+ },
loading: false,
error: null,
};
diff --git a/app/modules/Profile/screens/PreferencesScreen.tsx b/app/modules/Profile/screens/PreferencesScreen.tsx
index 41cfe53..ae228ec 100644
--- a/app/modules/Profile/screens/PreferencesScreen.tsx
+++ b/app/modules/Profile/screens/PreferencesScreen.tsx
@@ -7,8 +7,8 @@
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
-import { Card } from 'shared/src/components/Card';
-import { Colors, Spacing, Typography } from 'shared/src/theme';
+import { Card } from '../../../../shared/src/components/Card';
+import { Colors, Spacing, Typography } from '../../../../shared/src/theme';
/**
* PreferencesScreen - shows stub content for preferences form
diff --git a/app/modules/Profile/screens/ProfileScreen.tsx b/app/modules/Profile/screens/ProfileScreen.tsx
index 70315a5..f431124 100644
--- a/app/modules/Profile/screens/ProfileScreen.tsx
+++ b/app/modules/Profile/screens/ProfileScreen.tsx
@@ -1,70 +1,494 @@
/*
* File: ProfileScreen.tsx
- * Description: Screen for displaying user profile
+ * Description: Screen for displaying and managing user profile information, with editable name fields.
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
-import React from 'react';
-import { View, Text, StyleSheet, Image } from 'react-native';
-import { Button } from 'shared/src/components/Button';
-import { Card } from 'shared/src/components/Card';
-import { Colors, Spacing, Typography } from 'shared/src/theme';
+import React, { useState } from 'react';
+import {
+ View,
+ Text,
+ StyleSheet,
+ Image,
+ ScrollView,
+ TouchableOpacity,
+ Alert,
+ Switch,
+ Modal, // Import Modal
+ TextInput, // Import TextInput
+} from 'react-native';
+import Icon from 'react-native-vector-icons/Feather';
import { useProfile } from '../hooks/useProfile';
+import {
+ Colors,
+ Spacing,
+ Typography,
+ Shadows,
+ BorderRadius,
+} from '../../../../shared/src/theme';
+import { Button } from '../../../../shared/src/components/Button';
+import { useDispatch } from 'react-redux';
+import { logout } from '../../Auth/redux/authSlice';
-/**
- * ProfileScreen - shows user info, avatar, and navigation to settings/preferences
- */
-const ProfileScreen: React.FC = ({ navigation }) => {
- const user = useProfile();
+interface InfoRowProps {
+ icon: string;
+ label?: string;
+ value?: string;
+ onEdit?: () => void;
+ isNav?: boolean;
+ isLogout?: boolean;
+}
- if (!user) {
- return No profile loaded.;
- }
+const InfoRow: React.FC = ({
+ icon,
+ label,
+ value,
+ onEdit,
+ isNav,
+ isLogout,
+}) => (
+
+
+
+ {value || label}
+
+ {onEdit && !isNav && !isLogout && (
+
+ )}
+ {isNav && (
+
+ )}
+
+);
+
+const SectionHeader: React.FC<{ title: string; icon?: string }> = ({ title, icon }) => (
+
+ {icon && }
+ {title}
+
+);
+
+// New component for individual notification toggles
+const NotificationToggle: React.FC<{
+ label: string;
+ isEnabled: boolean;
+ onToggle: (value: boolean) => void;
+}> = ({ label, isEnabled, onToggle }) => (
+
+ {label}
+
+
+);
+
+const ProfileScreen: React.FC<{ navigation: any }> = ({ navigation }) => {
+ const initialUser = useProfile() || {
+ user_id: '9fe5cd7f-e563-4c7f-93ec-6de64e12b83e',
+ email: 'radiologist@gmail.com',
+ first_name: 'Aman',
+ last_name: 'Kumar',
+ display_name: 'Aman Kumar',
+ dashboard_role: 'radiologist',
+ hospital_id: '0240c6d9-415f-49c9-ae24-7eba4927f939',
+ dashboard_settings: {
+ theme: 'light',
+ language: 'en',
+ timezone: 'UTC',
+ },
+ notification_preferences: {
+ critical_alerts: {
+ sms: true,
+ push: true,
+ email: true,
+ in_app: true,
+ },
+ system_notifications: {
+ push: true,
+ email: true,
+ in_app: true,
+ },
+ },
+ onboarding_completed: false,
+ onboarding_step: 0,
+ onboarding_message: 'You need to complete onboarding (change password and upload profile photo).',
+ onboarded: false,
+ profile_photo_url: null,
+ theme_color: null,
+ accent_color: null,
+ };
+
+ // State for user data, notifications, and the edit modal
+ const [user, setUser] = useState(initialUser);
+ const [notifications, setNotifications] = useState(user.notification_preferences);
+ const [isModalVisible, setModalVisible] = useState(false);
+ const [editingField, setEditingField] = useState('');
+ const [currentValue, setCurrentValue] = useState('');
+ const dispatch=useDispatch();
+
+ const handleLogout = () => {
+ Alert.alert('Log Out', 'Are you sure you want to log out?', [
+ { text: 'Cancel', style: 'cancel' },
+ { text: 'OK', onPress: () => dispatch(logout()) },
+ ]);
+ };
+
+ // Updated handleEdit to open the modal
+ const handleEdit = (field: string, value: string) => {
+ setEditingField(field);
+ setCurrentValue(value);
+ setModalVisible(true);
+ };
+
+ // New function to save changes from the modal
+ const handleSave = () => {
+ const updatedUser = { ...user };
+ if (editingField === 'First Name') {
+ updatedUser.first_name = currentValue;
+ } else if (editingField === 'Last Name') {
+ updatedUser.last_name = currentValue;
+ }
+ updatedUser.display_name = `${updatedUser.first_name} ${updatedUser.last_name}`;
+ setUser(updatedUser);
+ setModalVisible(false);
+ Alert.alert('Profile Updated', `${editingField} has been changed.`);
+ };
+
+ const handleNotificationToggle = (
+ category: 'critical_alerts' | 'system_notifications',
+ channel: string,
+ ) => {
+ const newPrefs = { ...notifications };
+ // Asserting the channel type to fix TypeScript indexing error
+ (newPrefs[category] as any)[channel] = !(newPrefs[category] as any)[channel];
+ setNotifications(newPrefs);
+ // In a real app, you would dispatch an action here to save the new preferences.
+ Alert.alert(
+ 'Preference Updated',
+ `${channel.toUpperCase()} for ${category.replace('_', ' ')} has been ${
+ (newPrefs[category] as any)[channel] ? 'enabled' : 'disabled'
+ }.`,
+ );
+ };
return (
-
-
- {user.avatar && }
- {user.name}
+
+ {/* Profile Section */}
+
+
+
+ handleEdit('Profile Photo', user.profile_photo_url || '')}>
+
+
+
+ {user.display_name}
+ {user.dashboard_role}
{user.email}
- navigation.navigate('Settings')} style={styles.button} />
- navigation.navigate('Preferences')} style={styles.button} />
-
-
+
+
+ {/* Onboarding Section */}
+ {!user.onboarding_completed && (
+
+
+ {user.onboarding_message}
+
+ )}
+
+ {/* Personal Information Section - New */}
+
+
+ handleEdit('First Name', user.first_name)}
+ />
+ handleEdit('Last Name', user.last_name)}
+ />
+
+
+ {/* Settings Section */}
+ {/*
+
+
+
+
+ */}
+
+ {/* Notification Preferences Section - Updated with Toggles */}
+
+
+
+ Critical Alerts
+ {Object.entries(notifications.critical_alerts).map(([channel, isEnabled]) => (
+ handleNotificationToggle('critical_alerts', channel)}
+ />
+ ))}
+
+
+ System Notifications
+ {Object.entries(notifications.system_notifications).map(
+ ([channel, isEnabled]) => (
+ handleNotificationToggle('system_notifications', channel)}
+ />
+ ),
+ )}
+
+
+
+ {/* Actions Section */}
+
+ navigation.navigate('Support')}
+ />
+
+
+
+ {/* Edit Modal */}
+ setModalVisible(false)}>
+
+
+ Edit {editingField}
+
+
+ setModalVisible(false)} style={styles.modalButton} />
+
+
+
+
+
+
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
- backgroundColor: Colors.background,
- justifyContent: 'center',
- padding: Spacing.lg,
+ backgroundColor: Colors.backgroundAlt,
+ },
+ contentContainer: {
+ paddingBottom: Spacing.xl,
+ },
+ header: {
+ alignItems: 'center',
+ paddingVertical: Spacing.xl,
+ paddingHorizontal: Spacing.lg,
+ },
+ avatarContainer: {
+ position: 'relative',
+ marginBottom: Spacing.md,
},
avatar: {
- width: 80,
- height: 80,
- borderRadius: 40,
- alignSelf: 'center',
- marginBottom: Spacing.md,
+ width: 100,
+ height: 100,
+ borderRadius: 50,
+ borderWidth: 3,
+ borderColor: Colors.primary,
+ },
+ editIconContainer: {
+ position: 'absolute',
+ bottom: 2,
+ right: 2,
+ backgroundColor: Colors.primary,
+ padding: Spacing.xs,
+ borderRadius: 15,
+ borderWidth: 2,
+ borderColor: Colors.background,
},
name: {
fontFamily: Typography.fontFamily.bold,
- fontSize: Typography.fontSize.lg,
- color: Colors.primary,
+ fontSize: Typography.fontSize.xl,
+ color: Colors.textPrimary,
+ textAlign: 'center',
+ },
+ role: {
+ fontFamily: Typography.fontFamily.regular,
+ fontSize: Typography.fontSize.md,
+ color: Colors.textSecondary,
textAlign: 'center',
marginBottom: Spacing.xs,
},
email: {
fontFamily: Typography.fontFamily.regular,
fontSize: Typography.fontSize.md,
- color: Colors.textSecondary,
+ color: Colors.textMuted,
textAlign: 'center',
marginBottom: Spacing.md,
},
- button: {
- marginTop: Spacing.sm,
+ onboardingSection: {
+ marginHorizontal: Spacing.lg,
+ marginBottom: Spacing.md,
+ backgroundColor: Colors.warning + '22',
+ borderRadius: BorderRadius.md,
+ padding: Spacing.md,
+ },
+ onboardingMessage: {
+ color: Colors.warning,
+ fontFamily: Typography.fontFamily.regular,
+ fontSize: Typography.fontSize.sm,
+ textAlign: 'center',
+ },
+ sectionHeader: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ marginBottom: Spacing.xs,
+ marginTop: Spacing.md,
+ },
+ sectionHeaderText: {
+ fontFamily: Typography.fontFamily.bold,
+ fontSize: Typography.fontSize.md,
+ color: Colors.primary,
+ },
+ infoSection: {
+ marginHorizontal: Spacing.lg,
+ },
+ actionsSection: {
+ marginHorizontal: Spacing.lg,
+ marginTop: Spacing.lg,
+ borderTopWidth: 1,
+ borderTopColor: Colors.border,
+ paddingTop: Spacing.lg,
+ },
+ infoCard: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ backgroundColor: Colors.cardBackground,
+ padding: Spacing.md,
+ borderRadius: BorderRadius.md,
+ marginBottom: Spacing.md,
+ ...Shadows.sm,
+ },
+ infoIcon: {
+ marginRight: Spacing.md,
+ },
+ infoLabel: {
+ flex: 1,
+ fontFamily: Typography.fontFamily.bold,
+ fontSize: Typography.fontSize.md,
+ color: Colors.textPrimary,
+ },
+ infoValue: {
+ flex: 1,
+ fontFamily: Typography.fontFamily.regular,
+ fontSize: Typography.fontSize.md,
+ color: Colors.textSecondary,
+ },
+ logoutCard: {
+ backgroundColor: 'transparent',
+ ...Shadows.none,
+ },
+ logoutText: {
+ color: Colors.error,
+ fontFamily: Typography.fontFamily.bold,
+ },
+ notificationGroup: {
+ backgroundColor: Colors.cardBackground,
+ borderRadius: BorderRadius.md,
+ padding: Spacing.md,
+ marginBottom: Spacing.md,
+ ...Shadows.sm,
+ },
+ notificationCategory: {
+ fontFamily: Typography.fontFamily.bold,
+ fontSize: Typography.fontSize.md,
+ color: Colors.textPrimary,
+ marginBottom: Spacing.sm,
+ },
+ notificationRow: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ paddingVertical: Spacing.sm,
+ borderBottomWidth: 1,
+ borderBottomColor: Colors.border,
+ },
+ notificationLabel: {
+ fontFamily: Typography.fontFamily.regular,
+ fontSize: Typography.fontSize.md,
+ color: Colors.textSecondary,
+ },
+ modalContainer: {
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center',
+ backgroundColor: 'rgba(0,0,0,0.5)',
+ },
+ modalContent: {
+ width: '80%',
+ backgroundColor: Colors.cardBackground,
+ borderRadius: BorderRadius.md,
+ padding: Spacing.lg,
+ ...Shadows.card,
+ },
+ modalTitle: {
+ fontFamily: Typography.fontFamily.bold,
+ fontSize: Typography.fontSize.lg,
+ color: Colors.textPrimary,
+ marginBottom: Spacing.md,
+ },
+ modalInput: {
+ borderWidth: 1,
+ borderColor: Colors.border,
+ borderRadius: BorderRadius.sm,
+ padding: Spacing.sm,
+ marginBottom: Spacing.lg,
+ fontFamily: Typography.fontFamily.regular,
+ fontSize: Typography.fontSize.md,
+ },
+ modalActions: {
+ flexDirection: 'row',
+ justifyContent: 'flex-end',
+ },
+ modalButton: {
+ marginLeft: Spacing.sm,
},
});
diff --git a/app/modules/Profile/screens/SettingsScreen.tsx b/app/modules/Profile/screens/SettingsScreen.tsx
index ee891bd..026687a 100644
--- a/app/modules/Profile/screens/SettingsScreen.tsx
+++ b/app/modules/Profile/screens/SettingsScreen.tsx
@@ -7,8 +7,8 @@
import React from 'react';
import { View, Text, StyleSheet, Switch } from 'react-native';
-import { Card } from 'shared/src/components/Card';
-import { Colors, Spacing, Typography } from 'shared/src/theme';
+import { Card } from '../../../../shared/src/components/Card';
+import { Colors, Spacing, Typography } from '../../../../shared/src/theme';
import { useSettings } from '../hooks/useSettings';
/**
diff --git a/app/navigation/AppNavigator.tsx b/app/navigation/AppNavigator.tsx
index ca5ec3b..4b3d721 100644
--- a/app/navigation/AppNavigator.tsx
+++ b/app/navigation/AppNavigator.tsx
@@ -11,6 +11,8 @@ import { useSelector } from 'react-redux';
import { selectIsAuthenticated } from '../modules/Auth/redux/authSelectors';
import AuthNavigator from './AuthNavigator';
import TabNavigator from './TabNavigator';
+import { LoginScreen, SetupBiometricScreen } from '../modules/Auth/screens';
+import { DashboardScreen } from '../modules/Dashboard/screens';
/**
* AppNavigator - root navigator for the app
@@ -19,7 +21,7 @@ import TabNavigator from './TabNavigator';
const AppNavigator: React.FC = () => {
const isAuthenticated = useSelector(selectIsAuthenticated);
return (
-
+
{isAuthenticated ? : }
);
diff --git a/app/navigation/TabNavigator.tsx b/app/navigation/TabNavigator.tsx
index 096255f..d2d8811 100644
--- a/app/navigation/TabNavigator.tsx
+++ b/app/navigation/TabNavigator.tsx
@@ -10,8 +10,8 @@ import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { DashboardNavigator } from '../modules/Dashboard/navigation';
import { CaseReviewNavigator } from '../modules/CaseReview/navigation';
import { ProfileNavigator } from '../modules/Profile/navigation';
-import { Colors } from 'shared/src/theme';
-import { CustomIcon } from 'shared/src/components/Icons';
+import { Colors } from '../../shared/src/theme';
+import { CustomIcon } from '../../shared/src/components/Icons';
export type TabParamList = {
Dashboard: undefined;
diff --git a/app/redux/hooks.ts b/app/redux/hooks.ts
new file mode 100644
index 0000000..32b53fc
--- /dev/null
+++ b/app/redux/hooks.ts
@@ -0,0 +1,15 @@
+/*
+ * File: hooks.ts
+ * Description: Typed hooks for Redux store interaction (useAppDispatch and useAppSelector).
+ * Design & Developed by Tech4Biz Solutions
+ * Copyright (c) Spurrin Innovations. All rights reserved.
+ */
+
+import { useDispatch, useSelector } from 'react-redux';
+import type { TypedUseSelectorHook } from 'react-redux';
+import type { RootState } from './rootReducer';
+import type { AppDispatch } from './store';
+
+// Use throughout your app instead of plain `useDispatch` and `useSelector`
+export const useAppDispatch: () => AppDispatch = useDispatch;
+export const useAppSelector: TypedUseSelectorHook = useSelector;
\ No newline at end of file
diff --git a/app/redux/rootReducer.ts b/app/redux/rootReducer.ts
new file mode 100644
index 0000000..081a795
--- /dev/null
+++ b/app/redux/rootReducer.ts
@@ -0,0 +1,34 @@
+/*
+ * File: rootReducer.ts
+ * Description: Root reducer combining all feature slices for Redux store
+ * Design & Developed by Tech4Biz Solutions
+ * Copyright (c) Spurrin Innovations. All rights reserved.
+ */
+
+import { combineReducers } from '@reduxjs/toolkit';
+// Import feature slices
+import profileReducer from '../modules/Profile/redux/profileSlice';
+import settingsReducer from '../modules/Profile/redux/settingsSlice';
+// Add other slices as needed, e.g.:
+import dashboardReducer from '../modules/Dashboard/redux/dashboardSlice';
+import authReducer from '../modules/Auth/redux/authSlice';
+
+// RootState type for use throughout the app
+export type RootState = ReturnType;
+
+// Combine all reducers into the rootReducer
+const rootReducer = combineReducers({
+ profile: profileReducer,
+ settings: settingsReducer,
+ dashboard:dashboardReducer,
+ auth:authReducer
+ // Add other reducers here
+});
+
+export default rootReducer;
+
+/*
+ * End of File: rootReducer.ts
+ * Design & Developed by Tech4Biz Solutions
+ * Copyright (c) Spurrin Innovations. All rights reserved.
+ */
\ No newline at end of file
diff --git a/app/redux/store.ts b/app/redux/store.ts
new file mode 100644
index 0000000..0296dcc
--- /dev/null
+++ b/app/redux/store.ts
@@ -0,0 +1,25 @@
+/*
+ * File: store.ts
+ * Description: Redux store configuration for the Radiologist App
+ * Design & Developed by Tech4Biz Solutions
+ * Copyright (c) Spurrin Innovations. All rights reserved.
+ */
+
+import { configureStore } from '@reduxjs/toolkit';
+import rootReducer from './rootReducer';
+
+// Configure the Redux store
+const store = configureStore({
+ reducer: rootReducer,
+ // You can add middleware, devTools, and other options here if needed
+});
+
+// Export store and types for use throughout the app
+export type AppDispatch = typeof store.dispatch;
+export default store;
+
+/*
+ * End of File: store.ts
+ * Design & Developed by Tech4Biz Solutions
+ * Copyright (c) Spurrin Innovations. All rights reserved.
+ */
\ No newline at end of file
diff --git a/babel.config.js b/babel.config.js
index f7b3da3..02c7d13 100644
--- a/babel.config.js
+++ b/babel.config.js
@@ -1,3 +1,4 @@
module.exports = {
presets: ['module:@react-native/babel-preset'],
+ plugins: ['react-native-reanimated/plugin'],
};
diff --git a/package-lock.json b/package-lock.json
index d13d87c..b969a6d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7,11 +7,11 @@
"": {
"name": "NeoScan_Radiologist",
"version": "0.0.1",
+ "hasInstallScript": true,
"dependencies": {
"@react-native-async-storage/async-storage": "^2.1.0",
"@react-native-clipboard/clipboard": "^1.16.1",
"@react-native-community/netinfo": "^11.4.1",
- "@react-native-voice/voice": "^3.2.4",
"@react-navigation/bottom-tabs": "^7.2.0",
"@react-navigation/native": "^7.0.14",
"@react-navigation/native-stack": "^7.2.0",
@@ -22,10 +22,12 @@
"lottie-react-native": "^7.2.2",
"react": "19.0.0",
"react-native": "0.79.0",
+ "react-native-biometrics": "^3.0.1",
"react-native-blob-util": "^0.22.2",
"react-native-config": "^1.5.5",
"react-native-gesture-handler": "^2.22.1",
"react-native-image-picker": "^7.2.3",
+ "react-native-keychain": "^10.0.0",
"react-native-linear-gradient": "^2.8.3",
"react-native-permissions": "^5.2.4",
"react-native-raw-bottom-sheet": "^3.0.0",
@@ -55,6 +57,7 @@
"@react-native/typescript-config": "0.79.0",
"@types/jest": "^29.5.13",
"@types/react": "^19.0.0",
+ "@types/react-native-vector-icons": "^6.4.18",
"@types/react-test-renderer": "^19.0.0",
"eslint": "^8.19.0",
"jest": "^29.6.3",
@@ -426,92 +429,6 @@
"node": ">=6.9.0"
}
},
- "node_modules/@babel/highlight": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.9.tgz",
- "integrity": "sha512-llL88JShoCsth8fF8R4SJnIn+WLvR6ccFxu1H3FlMhDontdcmZWf2HgIZ7AIqV3Xcck1idlohrN4EUBQz6klbw==",
- "license": "MIT",
- "dependencies": {
- "@babel/helper-validator-identifier": "^7.25.9",
- "chalk": "^2.4.2",
- "js-tokens": "^4.0.0",
- "picocolors": "^1.0.0"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/highlight/node_modules/ansi-styles": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
- "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
- "license": "MIT",
- "dependencies": {
- "color-convert": "^1.9.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/@babel/highlight/node_modules/chalk": {
- "version": "2.4.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
- "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^3.2.1",
- "escape-string-regexp": "^1.0.5",
- "supports-color": "^5.3.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/@babel/highlight/node_modules/color-convert": {
- "version": "1.9.3",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
- "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
- "license": "MIT",
- "dependencies": {
- "color-name": "1.1.3"
- }
- },
- "node_modules/@babel/highlight/node_modules/color-name": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
- "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
- "license": "MIT"
- },
- "node_modules/@babel/highlight/node_modules/escape-string-regexp": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
- "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
- "license": "MIT",
- "engines": {
- "node": ">=0.8.0"
- }
- },
- "node_modules/@babel/highlight/node_modules/has-flag": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
- "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/@babel/highlight/node_modules/supports-color": {
- "version": "5.5.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
- "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
- "license": "MIT",
- "dependencies": {
- "has-flag": "^3.0.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/@babel/parser": {
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz",
@@ -2223,229 +2140,6 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
- "node_modules/@expo/config-plugins": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-2.0.4.tgz",
- "integrity": "sha512-JGt/X2tFr7H8KBQrKfbGo9hmCubQraMxq5sj3bqDdKmDOLcE1a/EDCP9g0U4GHsa425J8VDIkQUHYz3h3ndEXQ==",
- "license": "MIT",
- "dependencies": {
- "@expo/config-types": "^41.0.0",
- "@expo/json-file": "8.2.30",
- "@expo/plist": "0.0.13",
- "debug": "^4.3.1",
- "find-up": "~5.0.0",
- "fs-extra": "9.0.0",
- "getenv": "^1.0.0",
- "glob": "7.1.6",
- "resolve-from": "^5.0.0",
- "slash": "^3.0.0",
- "xcode": "^3.0.1",
- "xml2js": "^0.4.23"
- }
- },
- "node_modules/@expo/config-plugins/node_modules/brace-expansion": {
- "version": "1.1.12",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
- "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "node_modules/@expo/config-plugins/node_modules/fs-extra": {
- "version": "9.0.0",
- "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.0.tgz",
- "integrity": "sha512-pmEYSk3vYsG/bF651KPUXZ+hvjpgWYw/Gc7W9NFUe3ZVLczKKWIij3IKpOrQcdw4TILtibFslZ0UmR8Vvzig4g==",
- "license": "MIT",
- "dependencies": {
- "at-least-node": "^1.0.0",
- "graceful-fs": "^4.2.0",
- "jsonfile": "^6.0.1",
- "universalify": "^1.0.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/@expo/config-plugins/node_modules/glob": {
- "version": "7.1.6",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
- "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
- "deprecated": "Glob versions prior to v9 are no longer supported",
- "license": "ISC",
- "dependencies": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^3.0.4",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
- },
- "engines": {
- "node": "*"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/@expo/config-plugins/node_modules/jsonfile": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
- "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
- "license": "MIT",
- "dependencies": {
- "universalify": "^2.0.0"
- },
- "optionalDependencies": {
- "graceful-fs": "^4.1.6"
- }
- },
- "node_modules/@expo/config-plugins/node_modules/jsonfile/node_modules/universalify": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
- "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
- "license": "MIT",
- "engines": {
- "node": ">= 10.0.0"
- }
- },
- "node_modules/@expo/config-plugins/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
- "engines": {
- "node": "*"
- }
- },
- "node_modules/@expo/config-plugins/node_modules/resolve-from": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
- "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/@expo/config-plugins/node_modules/universalify": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz",
- "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==",
- "license": "MIT",
- "engines": {
- "node": ">= 10.0.0"
- }
- },
- "node_modules/@expo/config-types": {
- "version": "41.0.0",
- "resolved": "https://registry.npmjs.org/@expo/config-types/-/config-types-41.0.0.tgz",
- "integrity": "sha512-Ax0pHuY5OQaSrzplOkT9DdpdmNzaVDnq9VySb4Ujq7UJ4U4jriLy8u93W98zunOXpcu0iiKubPsqD6lCiq0pig==",
- "license": "MIT"
- },
- "node_modules/@expo/json-file": {
- "version": "8.2.30",
- "resolved": "https://registry.npmjs.org/@expo/json-file/-/json-file-8.2.30.tgz",
- "integrity": "sha512-vrgGyPEXBoFI5NY70IegusCSoSVIFV3T3ry4tjJg1MFQKTUlR7E0r+8g8XR6qC705rc2PawaZQjqXMAVtV6s2A==",
- "license": "MIT",
- "dependencies": {
- "@babel/code-frame": "~7.10.4",
- "fs-extra": "9.0.0",
- "json5": "^1.0.1",
- "write-file-atomic": "^2.3.0"
- }
- },
- "node_modules/@expo/json-file/node_modules/@babel/code-frame": {
- "version": "7.10.4",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz",
- "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==",
- "license": "MIT",
- "dependencies": {
- "@babel/highlight": "^7.10.4"
- }
- },
- "node_modules/@expo/json-file/node_modules/fs-extra": {
- "version": "9.0.0",
- "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.0.tgz",
- "integrity": "sha512-pmEYSk3vYsG/bF651KPUXZ+hvjpgWYw/Gc7W9NFUe3ZVLczKKWIij3IKpOrQcdw4TILtibFslZ0UmR8Vvzig4g==",
- "license": "MIT",
- "dependencies": {
- "at-least-node": "^1.0.0",
- "graceful-fs": "^4.2.0",
- "jsonfile": "^6.0.1",
- "universalify": "^1.0.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/@expo/json-file/node_modules/json5": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
- "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
- "license": "MIT",
- "dependencies": {
- "minimist": "^1.2.0"
- },
- "bin": {
- "json5": "lib/cli.js"
- }
- },
- "node_modules/@expo/json-file/node_modules/jsonfile": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
- "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
- "license": "MIT",
- "dependencies": {
- "universalify": "^2.0.0"
- },
- "optionalDependencies": {
- "graceful-fs": "^4.1.6"
- }
- },
- "node_modules/@expo/json-file/node_modules/jsonfile/node_modules/universalify": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
- "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
- "license": "MIT",
- "engines": {
- "node": ">= 10.0.0"
- }
- },
- "node_modules/@expo/json-file/node_modules/universalify": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz",
- "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==",
- "license": "MIT",
- "engines": {
- "node": ">= 10.0.0"
- }
- },
- "node_modules/@expo/json-file/node_modules/write-file-atomic": {
- "version": "2.4.3",
- "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz",
- "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==",
- "license": "ISC",
- "dependencies": {
- "graceful-fs": "^4.1.11",
- "imurmurhash": "^0.1.4",
- "signal-exit": "^3.0.2"
- }
- },
- "node_modules/@expo/plist": {
- "version": "0.0.13",
- "resolved": "https://registry.npmjs.org/@expo/plist/-/plist-0.0.13.tgz",
- "integrity": "sha512-zGPSq9OrCn7lWvwLLHLpHUUq2E40KptUFXn53xyZXPViI0k9lbApcR9KlonQZ95C+ELsf0BQ3gRficwK92Ivcw==",
- "license": "MIT",
- "dependencies": {
- "base64-js": "^1.2.3",
- "xmlbuilder": "^14.0.0",
- "xmldom": "~0.5.0"
- }
- },
"node_modules/@hapi/hoek": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz",
@@ -3503,19 +3197,6 @@
"react-native": ">=0.59"
}
},
- "node_modules/@react-native-voice/voice": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/@react-native-voice/voice/-/voice-3.2.4.tgz",
- "integrity": "sha512-4i3IpB/W5VxCI7BQZO5Nr2VB0ecx0SLvkln2Gy29cAQKqgBl+1ZsCwUBChwHlPbmja6vA3tp/+2ADQGwB1OhHg==",
- "license": "MIT",
- "dependencies": {
- "@expo/config-plugins": "^2.0.0",
- "invariant": "^2.2.4"
- },
- "peerDependencies": {
- "react-native": ">= 0.60.2"
- }
- },
"node_modules/@react-native/assets-registry": {
"version": "0.79.0",
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.79.0.tgz",
@@ -4304,6 +3985,27 @@
"@types/react": "*"
}
},
+ "node_modules/@types/react-native-vector-icons": {
+ "version": "6.4.18",
+ "resolved": "https://registry.npmjs.org/@types/react-native-vector-icons/-/react-native-vector-icons-6.4.18.tgz",
+ "integrity": "sha512-YGlNWb+k5laTBHd7+uZowB9DpIK3SXUneZqAiKQaj1jnJCZM0x71GDim5JCTMi4IFkhc9m8H/Gm28T5BjyivUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/react": "*",
+ "@types/react-native": "^0.70"
+ }
+ },
+ "node_modules/@types/react-native-vector-icons/node_modules/@types/react-native": {
+ "version": "0.70.19",
+ "resolved": "https://registry.npmjs.org/@types/react-native/-/react-native-0.70.19.tgz",
+ "integrity": "sha512-c6WbyCgWTBgKKMESj/8b4w+zWcZSsCforson7UdXtXMecG3MxCinYi6ihhrHVPyUrVzORsvEzK8zg32z4pK6Sg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/react": "*"
+ }
+ },
"node_modules/@types/react-test-renderer": {
"version": "19.1.0",
"resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-19.1.0.tgz",
@@ -4587,15 +4289,6 @@
"devOptional": true,
"license": "MIT"
},
- "node_modules/@xmldom/xmldom": {
- "version": "0.8.10",
- "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz",
- "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==",
- "license": "MIT",
- "engines": {
- "node": ">=10.0.0"
- }
- },
"node_modules/abort-controller": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
@@ -4994,15 +4687,6 @@
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
},
- "node_modules/at-least-node": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
- "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
- "license": "ISC",
- "engines": {
- "node": ">= 4.0.0"
- }
- },
"node_modules/available-typed-arrays": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
@@ -5232,15 +4916,6 @@
],
"license": "MIT"
},
- "node_modules/big-integer": {
- "version": "1.6.52",
- "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz",
- "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==",
- "license": "Unlicense",
- "engines": {
- "node": ">=0.6"
- }
- },
"node_modules/bl": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
@@ -5301,27 +4976,6 @@
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
"license": "ISC"
},
- "node_modules/bplist-creator": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.1.0.tgz",
- "integrity": "sha512-sXaHZicyEEmY86WyueLTQesbeoH/mquvarJaQNbjuOQO+7gbFcDEWqKmcWA4cOTLzFlfgvkiVxolk1k5bBIpmg==",
- "license": "MIT",
- "dependencies": {
- "stream-buffers": "2.2.x"
- }
- },
- "node_modules/bplist-parser": {
- "version": "0.3.1",
- "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.3.1.tgz",
- "integrity": "sha512-PyJxiNtA5T2PlLIeBot4lbp7rj4OadzjnMZD/G5zuBNt8ei/yCU7+wW0h2bag9vr8c+/WuRWmSxbqAl9hL1rBA==",
- "license": "MIT",
- "dependencies": {
- "big-integer": "1.6.x"
- },
- "engines": {
- "node": ">= 5.10.0"
- }
- },
"node_modules/brace-expansion": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
@@ -7572,6 +7226,7 @@
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
"integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "devOptional": true,
"license": "MIT",
"dependencies": {
"locate-path": "^6.0.0",
@@ -7871,15 +7526,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/getenv": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/getenv/-/getenv-1.0.0.tgz",
- "integrity": "sha512-7yetJWqbS9sbn0vIfliPsFgoXMKn/YMF+Wuiog97x+urnSRRRZ7xB+uVkwGKzRgq9CDFfMQnE9ruL5DHv9c6Xg==",
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
@@ -10119,6 +9765,7 @@
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
"integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "devOptional": true,
"license": "MIT",
"dependencies": {
"p-locate": "^5.0.0"
@@ -10976,15 +10623,6 @@
"url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/minimist": {
- "version": "1.2.8",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
- "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/minipass": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
@@ -11366,6 +11004,7 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "devOptional": true,
"license": "MIT",
"dependencies": {
"yocto-queue": "^0.1.0"
@@ -11381,6 +11020,7 @@
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
"integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "devOptional": true,
"license": "MIT",
"dependencies": {
"p-limit": "^3.0.2"
@@ -11610,29 +11250,6 @@
"node": ">=8"
}
},
- "node_modules/plist": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz",
- "integrity": "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==",
- "license": "MIT",
- "dependencies": {
- "@xmldom/xmldom": "^0.8.8",
- "base64-js": "^1.5.1",
- "xmlbuilder": "^15.1.1"
- },
- "engines": {
- "node": ">=10.4.0"
- }
- },
- "node_modules/plist/node_modules/xmlbuilder": {
- "version": "15.1.1",
- "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz",
- "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==",
- "license": "MIT",
- "engines": {
- "node": ">=8.0"
- }
- },
"node_modules/possible-typed-array-names": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
@@ -12004,6 +11621,15 @@
}
}
},
+ "node_modules/react-native-biometrics": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/react-native-biometrics/-/react-native-biometrics-3.0.1.tgz",
+ "integrity": "sha512-Ru80gXRa9KG04sl5AB9HyjLjVbduhqZVjA+AiOSGqr+fNqCDmCu9y5WEksnjbnniNLmq1yGcw+qcLXmR1ddLDQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react-native": ">=0.60.0"
+ }
+ },
"node_modules/react-native-blob-util": {
"version": "0.22.2",
"resolved": "https://registry.npmjs.org/react-native-blob-util/-/react-native-blob-util-0.22.2.tgz",
@@ -12087,6 +11713,19 @@
"react-native": "*"
}
},
+ "node_modules/react-native-keychain": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/react-native-keychain/-/react-native-keychain-10.0.0.tgz",
+ "integrity": "sha512-YzPKSAnSzGEJ12IK6CctNLU79T1W15WDrElRQ+1/FsOazGX9ucFPTQwgYe8Dy8jiSEDJKM4wkVa3g4lD2Z+Pnw==",
+ "license": "MIT",
+ "workspaces": [
+ "KeychainExample",
+ "website"
+ ],
+ "engines": {
+ "node": ">=16"
+ }
+ },
"node_modules/react-native-linear-gradient": {
"version": "2.8.3",
"resolved": "https://registry.npmjs.org/react-native-linear-gradient/-/react-native-linear-gradient-2.8.3.tgz",
@@ -12831,12 +12470,6 @@
"devOptional": true,
"license": "MIT"
},
- "node_modules/sax": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
- "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
- "license": "ISC"
- },
"node_modules/scheduler": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz",
@@ -13122,17 +12755,6 @@
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
"license": "ISC"
},
- "node_modules/simple-plist": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/simple-plist/-/simple-plist-1.3.1.tgz",
- "integrity": "sha512-iMSw5i0XseMnrhtIzRb7XpQEXepa9xhWxGUojHBL43SIpQuDQkh3Wpy67ZbDzZVr6EKxvwVChnVpdl8hEVLDiw==",
- "license": "MIT",
- "dependencies": {
- "bplist-creator": "0.1.0",
- "bplist-parser": "0.3.1",
- "plist": "^3.0.5"
- }
- },
"node_modules/simple-swizzle": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
@@ -13315,15 +12937,6 @@
"node": ">= 0.4"
}
},
- "node_modules/stream-buffers": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-2.2.0.tgz",
- "integrity": "sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg==",
- "license": "Unlicense",
- "engines": {
- "node": ">= 0.10.0"
- }
- },
"node_modules/strict-uri-encode": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
@@ -14089,15 +13702,6 @@
"node": ">= 0.4.0"
}
},
- "node_modules/uuid": {
- "version": "7.0.3",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz",
- "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==",
- "license": "MIT",
- "bin": {
- "uuid": "dist/bin/uuid"
- }
- },
"node_modules/v8-to-istanbul": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
@@ -14344,59 +13948,6 @@
"async-limiter": "~1.0.0"
}
},
- "node_modules/xcode": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/xcode/-/xcode-3.0.1.tgz",
- "integrity": "sha512-kCz5k7J7XbJtjABOvkc5lJmkiDh8VhjVCGNiqdKCscmVpdVUpEAyXv1xmCLkQJ5dsHqx3IPO4XW+NTDhU/fatA==",
- "license": "Apache-2.0",
- "dependencies": {
- "simple-plist": "^1.1.0",
- "uuid": "^7.0.3"
- },
- "engines": {
- "node": ">=10.0.0"
- }
- },
- "node_modules/xml2js": {
- "version": "0.4.23",
- "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
- "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==",
- "license": "MIT",
- "dependencies": {
- "sax": ">=0.6.0",
- "xmlbuilder": "~11.0.0"
- },
- "engines": {
- "node": ">=4.0.0"
- }
- },
- "node_modules/xml2js/node_modules/xmlbuilder": {
- "version": "11.0.1",
- "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
- "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
- "license": "MIT",
- "engines": {
- "node": ">=4.0"
- }
- },
- "node_modules/xmlbuilder": {
- "version": "14.0.0",
- "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-14.0.0.tgz",
- "integrity": "sha512-ts+B2rSe4fIckR6iquDjsKbQFK2NlUk6iG5nf14mDEyldgoc2nEKZ3jZWMPTxGQwVgToSjt6VGIho1H8/fNFTg==",
- "license": "MIT",
- "engines": {
- "node": ">=8.0"
- }
- },
- "node_modules/xmldom": {
- "version": "0.5.0",
- "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.5.0.tgz",
- "integrity": "sha512-Foaj5FXVzgn7xFzsKeNIde9g6aFBxTPi37iwsno8QvApmtg7KYrr+OPyRHcJF7dud2a5nGRBXK3n0dL62Gf7PA==",
- "license": "MIT",
- "engines": {
- "node": ">=10.0.0"
- }
- },
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
@@ -14465,6 +14016,7 @@
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "devOptional": true,
"license": "MIT",
"engines": {
"node": ">=10"
diff --git a/package.json b/package.json
index 4b76e52..5c3d723 100644
--- a/package.json
+++ b/package.json
@@ -7,13 +7,13 @@
"ios": "react-native run-ios",
"lint": "eslint .",
"start": "react-native start",
- "test": "jest"
+ "test": "jest",
+ "postinstall": "patch-package"
},
"dependencies": {
"@react-native-async-storage/async-storage": "^2.1.0",
"@react-native-clipboard/clipboard": "^1.16.1",
"@react-native-community/netinfo": "^11.4.1",
- "@react-native-voice/voice": "^3.2.4",
"@react-navigation/bottom-tabs": "^7.2.0",
"@react-navigation/native": "^7.0.14",
"@react-navigation/native-stack": "^7.2.0",
@@ -24,10 +24,12 @@
"lottie-react-native": "^7.2.2",
"react": "19.0.0",
"react-native": "0.79.0",
+ "react-native-biometrics": "^3.0.1",
"react-native-blob-util": "^0.22.2",
"react-native-config": "^1.5.5",
"react-native-gesture-handler": "^2.22.1",
"react-native-image-picker": "^7.2.3",
+ "react-native-keychain": "^10.0.0",
"react-native-linear-gradient": "^2.8.3",
"react-native-permissions": "^5.2.4",
"react-native-raw-bottom-sheet": "^3.0.0",
@@ -57,6 +59,7 @@
"@react-native/typescript-config": "0.79.0",
"@types/jest": "^29.5.13",
"@types/react": "^19.0.0",
+ "@types/react-native-vector-icons": "^6.4.18",
"@types/react-test-renderer": "^19.0.0",
"eslint": "^8.19.0",
"jest": "^29.6.3",
diff --git a/patches/@react-native-voice+voice+3.2.4.patch b/patches/@react-native-voice+voice+3.2.4.patch
new file mode 100644
index 0000000..1012b50
--- /dev/null
+++ b/patches/@react-native-voice+voice+3.2.4.patch
@@ -0,0 +1,11 @@
+diff --git a/node_modules/@react-native-voice/voice/android/build.gradle b/node_modules/@react-native-voice/voice/android/build.gradle
+index 8fce879..bda4ee1 100644
+--- a/node_modules/@react-native-voice/voice/android/build.gradle
++++ b/node_modules/@react-native-voice/voice/android/build.gradle
+@@ -62,6 +62,5 @@ def supportVersion = rootProject.hasProperty('supportLibVersion') ? rootProject.
+ dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+ testImplementation 'junit:junit:4.12'
+- implementation "com.android.support:appcompat-v7:${supportVersion}"
+ implementation 'com.facebook.react:react-native:+'
+ }
diff --git a/shared/src/components/Button/Button.tsx b/shared/src/components/Button/Button.tsx
index 6da16c1..0fe086f 100644
--- a/shared/src/components/Button/Button.tsx
+++ b/shared/src/components/Button/Button.tsx
@@ -1,75 +1,62 @@
/*
* File: Button.tsx
- * Description: Themed button component using Clinical Blue Interface design system
+ * Description: A reusable button component with optional loading state.
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
-import { TouchableOpacity, Text, StyleSheet, ViewStyle, TextStyle, GestureResponderEvent } from 'react-native';
-import { Colors, Typography, Spacing, BorderRadius, Shadows } from '../../theme';
+import { TouchableOpacity, Text, StyleSheet, ActivityIndicator } from 'react-native'; // Import ActivityIndicator
+import { Colors, Spacing, Typography, BorderRadius } from '../../theme';
-// Button props for reusability and type safety
interface ButtonProps {
title: string;
- onPress: (event: GestureResponderEvent) => void;
- style?: ViewStyle;
- textStyle?: TextStyle;
+ onPress: () => void;
+ style?: object;
+ textStyle?: object;
disabled?: boolean;
+ loading?: boolean; // Add loading prop
}
-/**
- * Themed Button component
- * - Uses Clinical Blue Interface theme
- * - Supports custom styles and disabled state
- */
-const Button: React.FC = ({ title, onPress, style, textStyle, disabled }) => {
+const Button: React.FC = ({
+ title,
+ onPress,
+ style,
+ textStyle,
+ disabled,
+ loading, // Destructure loading prop
+}) => {
return (
- {title}
+ disabled={disabled || loading}>
+ {loading ? (
+
+ ) : (
+ {title}
+ )}
);
};
-// Styles use the theme for consistency
const styles = StyleSheet.create({
button: {
backgroundColor: Colors.primary,
paddingVertical: Spacing.md,
- paddingHorizontal: Spacing.xl,
+ paddingHorizontal: Spacing.lg,
borderRadius: BorderRadius.md,
alignItems: 'center',
justifyContent: 'center',
- ...Shadows.card,
- },
- buttonDisabled: {
- backgroundColor: Colors.inactiveState,
},
text: {
color: Colors.background,
- fontFamily: Typography.fontFamily.bold,
fontSize: Typography.fontSize.md,
- fontWeight: Typography.fontWeight.bold as any,
- letterSpacing: 0.5,
+ fontFamily: Typography.fontFamily.bold,
},
- textDisabled: {
- color: Colors.textMuted,
+ disabled: {
+ backgroundColor: Colors.inactiveState,
},
});
-export default Button;
-
-/*
- * End of File: Button.tsx
- * Design & Developed by Tech4Biz Solutions
- * Copyright (c) Spurrin Innovations. All rights reserved.
- */
\ No newline at end of file
+export default Button;
\ No newline at end of file
diff --git a/shared/src/components/Card/Card.tsx b/shared/src/components/Card/Card.tsx
index 0519ecb..971f936 100644
--- a/shared/src/components/Card/Card.tsx
+++ b/shared/src/components/Card/Card.tsx
@@ -1 +1,25 @@
-
\ No newline at end of file
+ /*
+ * File: Card.tsx
+ * Description: Themed info card using Clinical Blue Interface design system
+ * Design & Developed by Tech4Biz Solutions
+ * Copyright (c) Spurrin Innovations. All rights reserved.
+ */
+
+import React, { ReactNode } from 'react';
+import { View, StyleSheet, ViewStyle } from 'react-native';
+import { cardStyles } from './Card.styles';
+
+interface CardProps {
+ children: ReactNode;
+ style?: ViewStyle;
+}
+
+/**
+ * Card component
+ * - Simple card for displaying information
+ */
+const Card: React.FC = ({ children, style }) => (
+ {children}
+);
+
+export default Card;
\ No newline at end of file
diff --git a/shared/src/components/Card/index.ts b/shared/src/components/Card/index.ts
index c394be1..ce562fc 100644
--- a/shared/src/components/Card/index.ts
+++ b/shared/src/components/Card/index.ts
@@ -6,6 +6,7 @@
*/
export { default as Card } from './Card';
+export { default as InfoCard } from './InfoCard';
/*
* End of File: index.ts
diff --git a/shared/src/components/Header/Header.tsx b/shared/src/components/Header/Header.tsx
new file mode 100644
index 0000000..113c0da
--- /dev/null
+++ b/shared/src/components/Header/Header.tsx
@@ -0,0 +1,73 @@
+/*
+ * File: Header.tsx
+ * Description: A reusable header component with optional back button, title, and right-side icon.
+ * Design & Developed by Tech4Biz Solutions
+ * Copyright (c) Spurrin Innovations. All rights reserved.
+ */
+
+import React from 'react';
+import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
+import Icon from 'react-native-vector-icons/Feather';
+import { useNavigation } from '@react-navigation/native';
+import { Colors, Spacing, Typography } from '../../theme';
+
+interface CustomHeaderProps {
+ title: string;
+ showBackButton?: boolean;
+ rightIcon?: {
+ name: string;
+ onPress: () => void;
+ };
+}
+
+const CustomHeader: React.FC = ({
+ title,
+ showBackButton = true,
+ rightIcon,
+}) => {
+ const navigation = useNavigation();
+
+ return (
+
+ {showBackButton ? (
+ navigation.goBack()} style={styles.iconButton}>
+
+
+ ) : }
+ {title}
+ {rightIcon ? (
+
+
+
+ ) : (
+
+ )}
+
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ paddingHorizontal: Spacing.lg,
+ paddingVertical: Spacing.md,
+ backgroundColor: Colors.background,
+ borderBottomWidth: 1,
+ borderBottomColor: Colors.border,
+ },
+ title: {
+ fontFamily: Typography.fontFamily.bold,
+ fontSize: Typography.fontSize.lg,
+ color: Colors.textPrimary,
+ },
+ iconButton: {
+ padding: Spacing.xs,
+ },
+ placeholder: {
+ width: 24 + Spacing.xs * 2, // Match icon button size
+ },
+});
+
+export default CustomHeader;
\ No newline at end of file
diff --git a/shared/src/components/Header/index.ts b/shared/src/components/Header/index.ts
new file mode 100644
index 0000000..12787f5
--- /dev/null
+++ b/shared/src/components/Header/index.ts
@@ -0,0 +1,8 @@
+/*
+ * File: index.ts
+ * Description: Barrel file for exporting the CustomHeader component.
+ * Design & Developed by Tech4Biz Solutions
+ * Copyright (c) Spurrin Innovations. All rights reserved.
+ */
+
+export { default as CustomHeader } from './Header';
\ No newline at end of file
diff --git a/shared/src/components/Input/index.ts b/shared/src/components/Input/index.ts
index d9e8334..32aa914 100644
--- a/shared/src/components/Input/index.ts
+++ b/shared/src/components/Input/index.ts
@@ -6,6 +6,7 @@
*/
export { default as TextInput } from './TextInput';
+export { default as SearchInput } from './SearchInput';
// Add other input components here as you implement them
/*
diff --git a/shared/src/theme/shadows.ts b/shared/src/theme/shadows.ts
index e72ccef..93270b5 100644
--- a/shared/src/theme/shadows.ts
+++ b/shared/src/theme/shadows.ts
@@ -14,6 +14,20 @@ export const Shadows = {
shadowRadius: 8,
elevation: 4,
},
+ sm: {
+ shadowColor: '#5B7CE6',
+ shadowOffset: { width: 0, height: 1 },
+ shadowOpacity: 0.05,
+ shadowRadius: 4,
+ elevation: 2,
+ },
+ none: {
+ shadowColor: 'transparent',
+ shadowOffset: { width: 0, height: 0 },
+ shadowOpacity: 0,
+ shadowRadius: 0,
+ elevation: 0,
+ },
};
// Usage: import { Shadows } from './shadows';
diff --git a/shared/src/utils/helpers/Api.ts b/shared/src/utils/helpers/Api.ts
new file mode 100644
index 0000000..29ec5d5
--- /dev/null
+++ b/shared/src/utils/helpers/Api.ts
@@ -0,0 +1,13 @@
+interface BuildHeadersParams {
+ token?: string;
+ contentType?: string;
+}
+
+export const buildHeaders = ({ token, contentType }: BuildHeadersParams = {}) => {
+ const headers: Record = {};
+
+ if (token) headers['Authorization'] = `Bearer ${token}`;
+ if (contentType) headers['Content-Type'] = contentType;
+
+ return { headers };
+};
diff --git a/shared/src/utils/helpers/Toast.ts b/shared/src/utils/helpers/Toast.ts
new file mode 100644
index 0000000..06b94c9
--- /dev/null
+++ b/shared/src/utils/helpers/Toast.ts
@@ -0,0 +1,48 @@
+/*
+ * File: Toast.js
+ * Description: Utility for displaying toast notifications
+ * Design & Developed by Tech4Biz Solutions
+ * Copyright (c) Spurrin Innovations. All rights reserved.
+ */
+
+import Toast from 'react-native-toast-message';
+//shows success toast
+const showSuccess=(text1='Successfull',text2='')=>{
+
+ Toast.show({
+ type: 'success',
+ text1,
+ text2
+ });
+}
+//shows error text
+const showError=(text1='something went wrong',text2='')=>{
+
+ Toast.show({
+ type: 'error',
+ text1,
+ text2
+ });
+}
+//shows warning text
+const showWarning=(text1='you have some warning',text2='')=>{
+
+ Toast.show({
+ type: 'info',
+ text1,
+ text2
+ });
+}
+
+const handleError=()=>{
+
+
+}
+
+export {showSuccess,showError,showWarning,handleError};
+
+/*
+ * End of File: Toast.js
+ * Design & Developed by Tech4Biz Solutions
+ * Copyright (c) Spurrin Innovations. All rights reserved.
+ */
diff --git a/shared/src/utils/validation/validators.ts b/shared/src/utils/validation/validators.ts
new file mode 100644
index 0000000..fb675be
--- /dev/null
+++ b/shared/src/utils/validation/validators.ts
@@ -0,0 +1,22 @@
+/*
+ * File: validators.ts
+ * Description: Common input validation functions.
+ * Design & Developed by Tech4Biz Solutions
+ * Copyright (c) Spurrin Innovations. All rights reserved.
+ */
+
+/**
+ * Validates an email address against a standard regex pattern.
+ * @param email The email address string to validate.
+ * @returns `true` if the email is valid, `false` otherwise.
+ */
+export const validateEmail = (email: string): boolean => {
+ const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
+ return regex.test(email);
+};
+
+/*
+ * End of File: validators.ts
+ * Design & Developed by Tech4Biz Solutions
+ * Copyright (c) Spurrin Innovations. All rights reserved.
+ */
\ No newline at end of file