login and logout implemented for radiologist
This commit is contained in:
parent
c3d92f11ea
commit
1e2e4eba47
2
.env
Normal file
2
.env
Normal file
@ -0,0 +1,2 @@
|
||||
BASE_URL='https://neoscan-backend.tech4bizsolutions.com'
|
||||
# BASE_URL='http://192.168.1.87:3000'
|
||||
22
App.tsx
22
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 (
|
||||
<View style={backgroundStyle}>
|
||||
<Provider store={store}>
|
||||
<View style={styles.container}>
|
||||
<StatusBar
|
||||
barStyle={isDarkMode ? 'light-content' : 'dark-content'}
|
||||
backgroundColor={backgroundStyle.backgroundColor}
|
||||
/>
|
||||
<TabNavigator/>
|
||||
<AppNavigator />
|
||||
</View>
|
||||
<Toast />
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
sectionContainer: {
|
||||
marginTop: 32,
|
||||
paddingHorizontal: 24,
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||
|
||||
<application
|
||||
android:name=".MainApplication"
|
||||
|
||||
3
app/constants/URLS.ts
Normal file
3
app/constants/URLS.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import Config from 'react-native-config';
|
||||
|
||||
export const BASE_URL=Config.BASE_URL;
|
||||
@ -1,69 +0,0 @@
|
||||
/*
|
||||
* File: PinInput.tsx
|
||||
* Description: Component for PIN entry
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { View, StyleSheet, TextInput as RNTextInput } from 'react-native';
|
||||
import { Colors, Spacing, BorderRadius, Typography } from 'shared/src/theme';
|
||||
|
||||
interface PinInputProps {
|
||||
value: string;
|
||||
onChange: (val: string) => void;
|
||||
length?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* PinInput - input for entering PIN code
|
||||
*/
|
||||
const PinInput: React.FC<PinInputProps> = ({ value, onChange, length = 4 }) => {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{Array.from({ length }).map((_, idx) => (
|
||||
<RNTextInput
|
||||
key={idx}
|
||||
style={styles.input}
|
||||
value={value[idx] || ''}
|
||||
onChangeText={text => {
|
||||
const newValue = value.substring(0, idx) + text + value.substring(idx + 1);
|
||||
onChange(newValue);
|
||||
}}
|
||||
maxLength={1}
|
||||
keyboardType="number-pad"
|
||||
secureTextEntry
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
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.
|
||||
*/
|
||||
@ -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<AuthStackParamList>();
|
||||
const Stack = createNativeStackNavigator<AuthStackParamList>();
|
||||
|
||||
/**
|
||||
* AuthNavigator sets up stack navigation for Auth module
|
||||
@ -32,7 +32,7 @@ const AuthNavigator: React.FC = () => (
|
||||
<Stack.Screen
|
||||
name="Login"
|
||||
component={LoginScreen}
|
||||
options={{ title: 'Login' }}
|
||||
options={{ title: 'Login',headerShown:false }}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="SetupBiometric"
|
||||
|
||||
@ -17,9 +17,11 @@ export const login = createAsyncThunk(
|
||||
async (credentials: { email: string; password: string }, { dispatch, rejectWithValue }) => {
|
||||
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');
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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<User>) {
|
||||
loginSuccess(state, action: PayloadAction<UserProfile>) {
|
||||
state.user = action.payload;
|
||||
state.isAuthenticated = true;
|
||||
state.loading = false;
|
||||
|
||||
@ -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 (
|
||||
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.title}>Radiologist Portal</Text>
|
||||
|
||||
{/* Email Input */}
|
||||
<View style={styles.inputContainer}>
|
||||
<Icon name="mail" size={20} color={Colors.textSecondary} style={styles.inputIcon} />
|
||||
<TextInput
|
||||
placeholder="Email"
|
||||
value={email}
|
||||
onChangeText={setEmail}
|
||||
style={styles.input}
|
||||
style={styles.inputField}
|
||||
autoCapitalize="none"
|
||||
keyboardType="email-address"
|
||||
placeholderTextColor={Colors.textMuted}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Password Input */}
|
||||
<View style={styles.inputContainer}>
|
||||
<Icon name="lock" size={20} color={Colors.textSecondary} style={styles.inputIcon} />
|
||||
<TextInput
|
||||
placeholder="Password"
|
||||
value={password}
|
||||
onChangeText={setPassword}
|
||||
style={styles.input}
|
||||
secureTextEntry
|
||||
style={styles.inputField}
|
||||
secureTextEntry={!isPasswordVisible}
|
||||
placeholderTextColor={Colors.textMuted}
|
||||
/>
|
||||
<Button title="Login" onPress={handleLogin} style={styles.button} />
|
||||
<TouchableOpacity
|
||||
onPress={() => setPasswordVisible(!isPasswordVisible)}
|
||||
style={styles.eyeIcon}>
|
||||
<Icon name={isPasswordVisible ? 'eye-off' : 'eye'} size={22} color={Colors.textSecondary} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
<Button
|
||||
title="Login"
|
||||
onPress={handleLogin}
|
||||
style={styles.button}
|
||||
loading={loading} // Pass loading state to button
|
||||
/>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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<BiometryType | null>(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 (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.title}>Setup Biometric Login</Text>
|
||||
<Text style={styles.info}>Enable Face ID or Touch ID for quick and secure login.</Text>
|
||||
<Button title="Enable Biometric" onPress={handleSetup} style={styles.button} />
|
||||
<SafeAreaView style={styles.container}>
|
||||
{isAuthenticated ? (
|
||||
<View style={styles.content}>
|
||||
<Text style={styles.title}>Welcome!</Text>
|
||||
<Text style={styles.subtitle}>You are successfully authenticated.</Text>
|
||||
<TouchableOpacity style={styles.button} onPress={logout}>
|
||||
<Text style={styles.buttonText}>Logout</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
) : (
|
||||
<View style={styles.content}>
|
||||
<Text style={styles.title}>Authentication Required</Text>
|
||||
{isAvailable ? (
|
||||
<Text style={styles.subtitle}>
|
||||
Use {getBiometricTypeText()} or your device PIN to continue.
|
||||
</Text>
|
||||
) : (
|
||||
<Text style={styles.subtitle}>
|
||||
Biometrics not available. Please use your device PIN to continue.
|
||||
</Text>
|
||||
)}
|
||||
<TouchableOpacity style={styles.button} onPress={authenticateUser}>
|
||||
<Text style={styles.buttonText}>Authenticate</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)}
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
|
||||
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.
|
||||
*/
|
||||
@ -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
|
||||
};
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
export * from './authAPI';
|
||||
export * from './';
|
||||
export * from './biometricService';
|
||||
|
||||
/*
|
||||
|
||||
@ -26,14 +26,12 @@ const CaseReviewNavigator: React.FC = () => (
|
||||
initialRouteName="CaseDetails"
|
||||
screenOptions={{
|
||||
headerStyle: { backgroundColor: Colors.primary },
|
||||
headerTintColor: Colors.background,
|
||||
headerTitleStyle: { fontWeight: 'bold' },
|
||||
}}
|
||||
>
|
||||
<Stack.Screen
|
||||
name="CaseDetails"
|
||||
component={CaseDetailsScreen}
|
||||
options={{ title: 'Case Details' }}
|
||||
options={{ title: 'Case Details',headerShown:false }}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="DICOMViewer"
|
||||
|
||||
@ -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 selectSelectedCase = (state: RootState) => state.caseReview.selectedCase;
|
||||
export const selectCaseReviewLoading = (state: RootState) => state.caseReview.loading;
|
||||
|
||||
@ -18,22 +18,23 @@ import { useAIOverlay } from '../hooks/useAIOverlay';
|
||||
* CaseDetailsScreen - shows patient info, AI findings, and navigation to DICOM viewer
|
||||
*/
|
||||
const CaseDetailsScreen: React.FC<any> = ({ navigation }) => {
|
||||
const caseData = useSelector(selectSelectedCase);
|
||||
const { findings, confidence } = useAIOverlay();
|
||||
// const caseData = useSelector(selectSelectedCase);
|
||||
// const { findings, confidence } = useAIOverlay();
|
||||
|
||||
if (!caseData) {
|
||||
return <View style={styles.container}><Text>No case selected.</Text></View>;
|
||||
}
|
||||
// if (!caseData) {
|
||||
// return <View style={styles.container}><Text>No case selected.</Text></View>;
|
||||
// }
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Card>
|
||||
{/* <Card>
|
||||
<Text style={styles.title}>{caseData.patientName}</Text>
|
||||
<Text style={styles.label}>AI Findings:</Text>
|
||||
<Text style={styles.findings}>{findings}</Text>
|
||||
<Text style={styles.confidence}>Confidence: {confidence}%</Text>
|
||||
<Button title="View DICOM Images" onPress={() => navigation.navigate('DICOMViewer')} style={styles.button} />
|
||||
</Card>
|
||||
</Card> */}
|
||||
<Text style={{color:'#000',fontWeight:'600'}} >Coming Soon..</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@ -44,6 +45,7 @@ const styles = StyleSheet.create({
|
||||
backgroundColor: Colors.background,
|
||||
justifyContent: 'center',
|
||||
padding: Spacing.lg,
|
||||
alignItems:'center'
|
||||
},
|
||||
title: {
|
||||
fontFamily: Typography.fontFamily.bold,
|
||||
|
||||
@ -25,14 +25,12 @@ const DashboardNavigator: React.FC = () => (
|
||||
initialRouteName="Dashboard"
|
||||
screenOptions={{
|
||||
headerStyle: { backgroundColor: Colors.primary },
|
||||
headerTintColor: Colors.background,
|
||||
headerTitleStyle: { fontWeight: 'bold' },
|
||||
}}
|
||||
>
|
||||
<Stack.Screen
|
||||
name="Dashboard"
|
||||
component={DashboardScreen}
|
||||
options={{ title: 'Dashboard' }}
|
||||
options={{ title: 'Dashboard' ,headerShown:false}}
|
||||
/>
|
||||
{/* Add more screens here */}
|
||||
</Stack.Navigator>
|
||||
|
||||
@ -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;
|
||||
|
||||
44
app/modules/Dashboard/screens/DashBoardDetail.tsx
Normal file
44
app/modules/Dashboard/screens/DashBoardDetail.tsx
Normal file
@ -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 (
|
||||
<ScrollView style={styles.container} contentContainerStyle={styles.content}>
|
||||
<Text style={{color:'#FFFFFF'}} >Dashboard Detail screen</Text>
|
||||
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
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.
|
||||
*/
|
||||
@ -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,8 +29,12 @@ const DashboardScreen: React.FC = () => {
|
||||
const [input, setInput] = useState('');
|
||||
|
||||
return (
|
||||
<ScrollView style={styles.container} contentContainerStyle={styles.content}>
|
||||
<Text style={styles.header}>Dashboard</Text>
|
||||
<View style={styles.container}>
|
||||
<CustomHeader
|
||||
title="Dashboard"
|
||||
showBackButton={false}
|
||||
/>
|
||||
<ScrollView contentContainerStyle={styles.content}>
|
||||
<SearchInput
|
||||
placeholder="Search cases..."
|
||||
value={search}
|
||||
@ -38,14 +43,20 @@ const DashboardScreen: React.FC = () => {
|
||||
/>
|
||||
<InfoCard>
|
||||
<Text style={styles.infoTitle}>Welcome, Dr. Smith</Text>
|
||||
<Text style={styles.infoText}>On-Call Status: <Text style={styles.active}>ACTIVE</Text></Text>
|
||||
<Text style={styles.infoText}>
|
||||
On-Call Status: <Text style={styles.active}>ACTIVE</Text>
|
||||
</Text>
|
||||
</InfoCard>
|
||||
<Card>
|
||||
<Text style={styles.cardTitle}>Critical Cases</Text>
|
||||
<View style={styles.row}>
|
||||
<CustomIcon name="alert" color={Colors.error} size={24} />
|
||||
<Text style={styles.criticalText}>Bed 3 - Hemorrhage (93% AI)</Text>
|
||||
<Button title="Review Now" onPress={() => setModalVisible(true)} style={styles.button} />
|
||||
<Button
|
||||
title="Review Now"
|
||||
onPress={() => setModalVisible(true)}
|
||||
style={styles.button}
|
||||
/>
|
||||
</View>
|
||||
</Card>
|
||||
<Card>
|
||||
@ -53,7 +64,10 @@ const DashboardScreen: React.FC = () => {
|
||||
<View style={styles.row}>
|
||||
<CustomIcon name="check-circle" color={Colors.success} size={24} />
|
||||
<Text style={styles.routineText}>Bed 12 - Headache (99% AI)</Text>
|
||||
<IconButton name="chevron-right" onPress={() => setConfirmVisible(true)} />
|
||||
<IconButton
|
||||
name="chevron-right"
|
||||
onPress={() => setConfirmVisible(true)}
|
||||
/>
|
||||
</View>
|
||||
</Card>
|
||||
<TextInput
|
||||
@ -62,20 +76,36 @@ const DashboardScreen: React.FC = () => {
|
||||
onChangeText={setInput}
|
||||
style={styles.input}
|
||||
/>
|
||||
<Button title="Show Alert" onPress={() => setAlertVisible(true)} style={styles.button} />
|
||||
<Button title={loading ? "Loading..." : "Show Loading Overlay"} onPress={() => setLoading(true)} style={styles.button} />
|
||||
<Spinner style={styles.spinner} />
|
||||
<Button
|
||||
title="Show Alert"
|
||||
onPress={() => setAlertVisible(true)}
|
||||
style={styles.button}
|
||||
/>
|
||||
<Button
|
||||
title={loading ? 'Loading...' : 'Show Loading Overlay'}
|
||||
onPress={() => setLoading(true)}
|
||||
style={styles.button}
|
||||
/>
|
||||
{/* <Spinner style={styles.spinner} /> */}
|
||||
{/* Modals */}
|
||||
<Modal visible={modalVisible} onRequestClose={() => setModalVisible(false)}>
|
||||
<Modal
|
||||
visible={modalVisible}
|
||||
onRequestClose={() => setModalVisible(false)}>
|
||||
<Text style={styles.modalTitle}>Critical Case Details</Text>
|
||||
<Text>Patient: John Doe, 45M</Text>
|
||||
<Button title="Close" onPress={() => setModalVisible(false)} style={styles.button} />
|
||||
<Button
|
||||
title="Close"
|
||||
onPress={() => setModalVisible(false)}
|
||||
style={styles.button}
|
||||
/>
|
||||
</Modal>
|
||||
<ConfirmModal
|
||||
visible={confirmVisible}
|
||||
title="Confirm Action"
|
||||
message="Are you sure you want to proceed?"
|
||||
onConfirm={() => { setConfirmVisible(false); }}
|
||||
onConfirm={() => {
|
||||
setConfirmVisible(false);
|
||||
}}
|
||||
onCancel={() => setConfirmVisible(false)}
|
||||
/>
|
||||
<AlertModal
|
||||
@ -88,6 +118,7 @@ const DashboardScreen: React.FC = () => {
|
||||
/>
|
||||
<LoadingOverlay visible={loading} />
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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,
|
||||
},
|
||||
|
||||
@ -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
|
||||
};
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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 };
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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 };
|
||||
|
||||
@ -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' },
|
||||
}}
|
||||
>
|
||||
<Stack.Screen
|
||||
name="Profile"
|
||||
component={ProfileScreen}
|
||||
options={{ title: 'Profile' }}
|
||||
options={{ title: 'Profile',headerShown:false }}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="Settings"
|
||||
|
||||
@ -5,9 +5,9 @@
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import { RootState } from 'app/redux/rootReducer';
|
||||
import { RootState } from '../../../../app/redux/rootReducer';
|
||||
|
||||
export const selectUserProfile = (state: RootState) => 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;
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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<any> = ({ navigation }) => {
|
||||
const user = useProfile();
|
||||
|
||||
if (!user) {
|
||||
return <View style={styles.container}><Text>No profile loaded.</Text></View>;
|
||||
interface InfoRowProps {
|
||||
icon: string;
|
||||
label?: string;
|
||||
value?: string;
|
||||
onEdit?: () => void;
|
||||
isNav?: boolean;
|
||||
isLogout?: boolean;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Card>
|
||||
{user.avatar && <Image source={{ uri: user.avatar }} style={styles.avatar} />}
|
||||
<Text style={styles.name}>{user.name}</Text>
|
||||
<Text style={styles.email}>{user.email}</Text>
|
||||
<Button title="Settings" onPress={() => navigation.navigate('Settings')} style={styles.button} />
|
||||
<Button title="Preferences" onPress={() => navigation.navigate('Preferences')} style={styles.button} />
|
||||
</Card>
|
||||
const InfoRow: React.FC<InfoRowProps> = ({
|
||||
icon,
|
||||
label,
|
||||
value,
|
||||
onEdit,
|
||||
isNav,
|
||||
isLogout,
|
||||
}) => (
|
||||
<TouchableOpacity
|
||||
style={[styles.infoCard, isLogout && styles.logoutCard]}
|
||||
onPress={onEdit}
|
||||
disabled={!onEdit}>
|
||||
<Icon
|
||||
name={icon}
|
||||
size={22}
|
||||
color={isLogout ? Colors.error : Colors.textSecondary}
|
||||
style={styles.infoIcon}
|
||||
/>
|
||||
<Text
|
||||
style={[
|
||||
styles.infoValue,
|
||||
!value && styles.infoLabel,
|
||||
isLogout && styles.logoutText,
|
||||
]}>
|
||||
{value || label}
|
||||
</Text>
|
||||
{onEdit && !isNav && !isLogout && (
|
||||
<Icon name="edit-2" size={20} color={Colors.textMuted} />
|
||||
)}
|
||||
{isNav && (
|
||||
<Icon name="chevron-right" size={22} color={Colors.textMuted} />
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
);
|
||||
|
||||
const SectionHeader: React.FC<{ title: string; icon?: string }> = ({ title, icon }) => (
|
||||
<View style={styles.sectionHeader}>
|
||||
{icon && <Icon name={icon} size={18} color={Colors.primary} style={{ marginRight: 8 }} />}
|
||||
<Text style={styles.sectionHeaderText}>{title}</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
// New component for individual notification toggles
|
||||
const NotificationToggle: React.FC<{
|
||||
label: string;
|
||||
isEnabled: boolean;
|
||||
onToggle: (value: boolean) => void;
|
||||
}> = ({ label, isEnabled, onToggle }) => (
|
||||
<View style={styles.notificationRow}>
|
||||
<Text style={styles.notificationLabel}>{label}</Text>
|
||||
<Switch
|
||||
value={isEnabled}
|
||||
onValueChange={onToggle}
|
||||
trackColor={{ false: Colors.inactiveState, true: Colors.success }}
|
||||
thumbColor={Colors.background}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
||||
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 (
|
||||
<ScrollView
|
||||
style={styles.container}
|
||||
contentContainerStyle={styles.contentContainer}
|
||||
showsVerticalScrollIndicator={false}>
|
||||
{/* Profile Section */}
|
||||
<View style={styles.header}>
|
||||
<View style={styles.avatarContainer}>
|
||||
<Image
|
||||
source={{ uri: user.profile_photo_url || 'https://ui-avatars.com/api/?name=' + encodeURIComponent(user.display_name) }}
|
||||
style={styles.avatar}
|
||||
/>
|
||||
<TouchableOpacity
|
||||
style={styles.editIconContainer}
|
||||
onPress={() => handleEdit('Profile Photo', user.profile_photo_url || '')}>
|
||||
<Icon name="edit-2" size={16} color={Colors.background} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<Text style={styles.name}>{user.display_name}</Text>
|
||||
<Text style={styles.role}>{user.dashboard_role}</Text>
|
||||
<Text style={styles.email}>{user.email}</Text>
|
||||
</View>
|
||||
|
||||
{/* Onboarding Section */}
|
||||
{!user.onboarding_completed && (
|
||||
<View style={styles.onboardingSection}>
|
||||
<SectionHeader title="Onboarding" icon="alert-circle" />
|
||||
<Text style={styles.onboardingMessage}>{user.onboarding_message}</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Personal Information Section - New */}
|
||||
<View style={styles.infoSection}>
|
||||
<SectionHeader title="Personal Information" icon="user" />
|
||||
<InfoRow
|
||||
icon="user"
|
||||
label="First Name"
|
||||
value={user.first_name}
|
||||
onEdit={() => handleEdit('First Name', user.first_name)}
|
||||
/>
|
||||
<InfoRow
|
||||
icon="user"
|
||||
label="Last Name"
|
||||
value={user.last_name}
|
||||
onEdit={() => handleEdit('Last Name', user.last_name)}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Settings Section */}
|
||||
{/* <View style={styles.infoSection}>
|
||||
<SectionHeader title="Settings" icon="settings" />
|
||||
<InfoRow icon="sun" label="Theme" value={user.dashboard_settings?.theme || '-'} />
|
||||
<InfoRow icon="globe" label="Language" value={user.dashboard_settings?.language || '-'} />
|
||||
<InfoRow icon="clock" label="Timezone" value={user.dashboard_settings?.timezone || '-'} />
|
||||
</View> */}
|
||||
|
||||
{/* Notification Preferences Section - Updated with Toggles */}
|
||||
<View style={styles.infoSection}>
|
||||
<SectionHeader title="Notification Preferences" icon="bell" />
|
||||
<View style={styles.notificationGroup}>
|
||||
<Text style={styles.notificationCategory}>Critical Alerts</Text>
|
||||
{Object.entries(notifications.critical_alerts).map(([channel, isEnabled]) => (
|
||||
<NotificationToggle
|
||||
key={`critical-${channel}`}
|
||||
label={channel.toUpperCase()}
|
||||
isEnabled={isEnabled}
|
||||
onToggle={() => handleNotificationToggle('critical_alerts', channel)}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
<View style={styles.notificationGroup}>
|
||||
<Text style={styles.notificationCategory}>System Notifications</Text>
|
||||
{Object.entries(notifications.system_notifications).map(
|
||||
([channel, isEnabled]) => (
|
||||
<NotificationToggle
|
||||
key={`system-${channel}`}
|
||||
label={channel.toUpperCase()}
|
||||
isEnabled={isEnabled}
|
||||
onToggle={() => handleNotificationToggle('system_notifications', channel)}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Actions Section */}
|
||||
<View style={styles.actionsSection}>
|
||||
<InfoRow
|
||||
icon="help-circle"
|
||||
label="Support"
|
||||
isNav
|
||||
onEdit={() => navigation.navigate('Support')}
|
||||
/>
|
||||
<InfoRow icon="log-out" label="Log Out" isLogout onEdit={handleLogout} />
|
||||
</View>
|
||||
|
||||
{/* Edit Modal */}
|
||||
<Modal
|
||||
transparent={true}
|
||||
visible={isModalVisible}
|
||||
onRequestClose={() => setModalVisible(false)}>
|
||||
<View style={styles.modalContainer}>
|
||||
<View style={styles.modalContent}>
|
||||
<Text style={styles.modalTitle}>Edit {editingField}</Text>
|
||||
<TextInput
|
||||
style={styles.modalInput}
|
||||
value={currentValue}
|
||||
onChangeText={setCurrentValue}
|
||||
autoFocus
|
||||
/>
|
||||
<View style={styles.modalActions}>
|
||||
<Button title="Cancel" onPress={() => setModalVisible(false)} style={styles.modalButton} />
|
||||
<Button title="Save" onPress={handleSave} style={styles.modalButton} />
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
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,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -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';
|
||||
|
||||
/**
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
15
app/redux/hooks.ts
Normal file
15
app/redux/hooks.ts
Normal file
@ -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<RootState> = useSelector;
|
||||
34
app/redux/rootReducer.ts
Normal file
34
app/redux/rootReducer.ts
Normal file
@ -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<typeof rootReducer>;
|
||||
|
||||
// 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.
|
||||
*/
|
||||
25
app/redux/store.ts
Normal file
25
app/redux/store.ts
Normal file
@ -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.
|
||||
*/
|
||||
@ -1,3 +1,4 @@
|
||||
module.exports = {
|
||||
presets: ['module:@react-native/babel-preset'],
|
||||
plugins: ['react-native-reanimated/plugin'],
|
||||
};
|
||||
|
||||
552
package-lock.json
generated
552
package-lock.json
generated
@ -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"
|
||||
|
||||
@ -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",
|
||||
|
||||
11
patches/@react-native-voice+voice+3.2.4.patch
Normal file
11
patches/@react-native-voice+voice+3.2.4.patch
Normal file
@ -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:+'
|
||||
}
|
||||
@ -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<ButtonProps> = ({ title, onPress, style, textStyle, disabled }) => {
|
||||
const Button: React.FC<ButtonProps> = ({
|
||||
title,
|
||||
onPress,
|
||||
style,
|
||||
textStyle,
|
||||
disabled,
|
||||
loading, // Destructure loading prop
|
||||
}) => {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.button,
|
||||
disabled ? styles.buttonDisabled : {},
|
||||
style,
|
||||
]}
|
||||
style={[styles.button, style, (disabled || loading) && styles.disabled]}
|
||||
onPress={onPress}
|
||||
activeOpacity={0.8}
|
||||
disabled={disabled}
|
||||
>
|
||||
<Text style={[styles.text, textStyle, disabled ? styles.textDisabled : {}]}>{title}</Text>
|
||||
disabled={disabled || loading}>
|
||||
{loading ? (
|
||||
<ActivityIndicator color={Colors.background} />
|
||||
) : (
|
||||
<Text style={[styles.text, textStyle]}>{title}</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
// 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.
|
||||
*/
|
||||
@ -1 +1,25 @@
|
||||
/*
|
||||
* 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<CardProps> = ({ children, style }) => (
|
||||
<View style={[cardStyles.card, style]}>{children}</View>
|
||||
);
|
||||
|
||||
export default Card;
|
||||
@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
export { default as Card } from './Card';
|
||||
export { default as InfoCard } from './InfoCard';
|
||||
|
||||
/*
|
||||
* End of File: index.ts
|
||||
|
||||
73
shared/src/components/Header/Header.tsx
Normal file
73
shared/src/components/Header/Header.tsx
Normal file
@ -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<CustomHeaderProps> = ({
|
||||
title,
|
||||
showBackButton = true,
|
||||
rightIcon,
|
||||
}) => {
|
||||
const navigation = useNavigation();
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{showBackButton ? (
|
||||
<TouchableOpacity onPress={() => navigation.goBack()} style={styles.iconButton}>
|
||||
<Icon name="arrow-left" size={24} color={Colors.textPrimary} />
|
||||
</TouchableOpacity>
|
||||
) : <View style={styles.placeholder} /> }
|
||||
<Text style={styles.title}>{title}</Text>
|
||||
{rightIcon ? (
|
||||
<TouchableOpacity onPress={rightIcon.onPress} style={styles.iconButton}>
|
||||
<Icon name={rightIcon.name} size={24} color={Colors.textPrimary} />
|
||||
</TouchableOpacity>
|
||||
) : (
|
||||
<View style={styles.placeholder} />
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
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;
|
||||
8
shared/src/components/Header/index.ts
Normal file
8
shared/src/components/Header/index.ts
Normal file
@ -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';
|
||||
@ -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
|
||||
|
||||
/*
|
||||
|
||||
@ -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';
|
||||
|
||||
13
shared/src/utils/helpers/Api.ts
Normal file
13
shared/src/utils/helpers/Api.ts
Normal file
@ -0,0 +1,13 @@
|
||||
interface BuildHeadersParams {
|
||||
token?: string;
|
||||
contentType?: string;
|
||||
}
|
||||
|
||||
export const buildHeaders = ({ token, contentType }: BuildHeadersParams = {}) => {
|
||||
const headers: Record<string, string> = {};
|
||||
|
||||
if (token) headers['Authorization'] = `Bearer ${token}`;
|
||||
if (contentType) headers['Content-Type'] = contentType;
|
||||
|
||||
return { headers };
|
||||
};
|
||||
48
shared/src/utils/helpers/Toast.ts
Normal file
48
shared/src/utils/helpers/Toast.ts
Normal file
@ -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.
|
||||
*/
|
||||
22
shared/src/utils/validation/validators.ts
Normal file
22
shared/src/utils/validation/validators.ts
Normal file
@ -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.
|
||||
*/
|
||||
Loading…
Reference in New Issue
Block a user