change password registerd mail id validation api integrated

This commit is contained in:
yashwin-foxy 2025-07-25 19:33:45 +05:30
parent 29940278e6
commit 5186e39e23
24 changed files with 1391 additions and 97 deletions

View File

@ -13,6 +13,7 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:allowBackup="false"
android:theme="@style/AppTheme"
android:networkSecurityConfig="@xml/network_security_config"
android:supportsRtl="true">
<activity
android:name=".MainActivity"

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</base-config>
</network-security-config>

View File

@ -33,7 +33,18 @@ import { Colors, Spacing, Typography } from "../../../../shared/src/theme"
import { showError, showSuccess } from "../../../../shared/src/utils/helpers/Toast"
import Icon from "react-native-vector-icons/Feather"
import OnboardingContainer from "./OnboardingContainer"
import IconContainer from "./IconContainer"
import { HospitalUploadSVG } from "../../../../shared/src/components/Icons/SvgIcons"
interface SignUpData {
email: string
password: string
first_name: string
last_name: string
username:string
id_photo_url: {}
hospital_id: string
}
interface DocumentUploadScreenProps {
onContinue: (imageData: {
uri: string
@ -42,6 +53,7 @@ interface DocumentUploadScreenProps {
size?: number
}) => void
onBack: () => void
Data:SignUpData
}
interface ImageData {
@ -51,8 +63,8 @@ interface ImageData {
size?: number
}
const DocumentUploadScreen: React.FC<DocumentUploadScreenProps> = ({ onContinue, onBack }) => {
const [selectedImage, setSelectedImage] = useState<ImageData | null>(null)
const DocumentUploadScreen: React.FC<DocumentUploadScreenProps> = ({ onContinue, onBack ,Data }) => {
const [selectedImage, setSelectedImage] = useState<ImageData | null >(Data.id_photo_url)
const [loading, setLoading] = useState(false)
// Request camera permission for Android
@ -206,11 +218,7 @@ const DocumentUploadScreen: React.FC<DocumentUploadScreenProps> = ({ onContinue,
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
<View style={styles.container}>
<View style={styles.content}>
<View style={styles.iconContainer}>
<View style={styles.iconWrapper}>
<Icon name="upload" size={32} color={Colors.primary} />
</View>
</View>
<IconContainer Icon={()=><HospitalUploadSVG/>} />
<Text style={styles.title}>Upload Image</Text>
<Text style={styles.subtitle}>Please upload your profile picture or identification image.</Text>

View File

@ -0,0 +1,148 @@
/*
* File: EmailAlreadyRegisteredModal.tsx
* Description: Modal popup for when email is already registered
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react'
import {
Modal,
View,
Text,
TouchableOpacity,
StyleSheet,
Dimensions,
} from 'react-native'
import { Colors } from '../../../../shared/src/theme'
interface EmailAlreadyRegisteredModalProps {
visible: boolean
onClose: () => void
onGoToLogin: () => void
}
const { width } = Dimensions.get('window')
const EmailAlreadyRegisteredModal: React.FC<EmailAlreadyRegisteredModalProps> = ({
visible,
onClose,
onGoToLogin,
}) => {
return (
<Modal
animationType="fade"
transparent={true}
visible={visible}
onRequestClose={onClose}
>
<View style={styles.overlay}>
<View style={styles.modalContainer}>
<View style={styles.content}>
<Text style={styles.title}>Email Already Registered</Text>
<Text style={styles.message}>
Your email is already registered. The login credentials have been sent to your emailplease check your inbox to proceed.
</Text>
<View style={styles.buttonContainer}>
<TouchableOpacity
style={[styles.button, styles.secondaryButton]}
onPress={onClose}
activeOpacity={0.8}
>
<Text style={styles.secondaryButtonText}>Cancel</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.button, styles.primaryButton]}
onPress={onGoToLogin}
activeOpacity={0.8}
>
<Text style={styles.primaryButtonText}>Go to Login</Text>
</TouchableOpacity>
</View>
</View>
</View>
</View>
</Modal>
)
}
const styles = StyleSheet.create({
overlay: {
flex: 1,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
justifyContent: 'center',
alignItems: 'center',
},
modalContainer: {
width: width * 0.85,
backgroundColor: Colors.background || '#FFFFFF',
borderRadius: 12,
elevation: 5,
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 0.25,
shadowRadius: 3.84,
},
content: {
padding: 24,
},
title: {
fontSize: 20,
fontWeight: 'bold',
color: '#333333',
textAlign: 'center',
marginBottom: 16,
},
message: {
fontSize: 16,
color: Colors.textSecondary || '#666666',
textAlign: 'center',
lineHeight: 24,
marginBottom: 24,
},
buttonContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
gap: 12,
},
button: {
flex: 1,
paddingVertical: 12,
paddingHorizontal: 16,
borderRadius: 8,
alignItems: 'center',
justifyContent: 'center',
minHeight: 48,
},
primaryButton: {
backgroundColor: Colors.primary || '#007AFF',
},
secondaryButton: {
backgroundColor: 'transparent',
borderWidth: 1,
borderColor: Colors.border || '#E0E0E0',
},
primaryButtonText: {
color: '#FFFFFF',
fontSize: 16,
fontWeight: '600',
},
secondaryButtonText: {
color: '#333333',
fontSize: 16,
fontWeight: '600',
},
})
export default EmailAlreadyRegisteredModal
/*
* End of File: EmailAlreadyRegisteredModal.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -17,14 +17,27 @@ import { Colors, Spacing, Typography } from "../../../../shared/src/theme"
import { showError } from "../../../../shared/src/utils/helpers/Toast"
import { validateEmail } from "../../../../shared/src/utils/validation/validators"
import Icon from "react-native-vector-icons/Feather"
import { EmailIconSVG } from "../../../../shared/src/components/Icons/SvgIcons"
interface SignUpDataInterface {
email: string
password: string
first_name: string
last_name: string
username:string
id_photo_url: {}
hospital_id: string
}
interface EmailScreenProps {
onContinue: (email: string) => void
onBack: () => void
Data:SignUpDataInterface
}
const EmailScreen: React.FC<EmailScreenProps> = ({ onContinue, onBack }) => {
const [email, setEmail] = useState("")
const EmailScreen: React.FC<EmailScreenProps> = ({ onContinue, onBack, Data }) => {
console.log('signupData',Data)
const [email, setEmail] = useState(Data.email)
const [loading, setLoading] = useState(false)
const handleContinue = () => {
@ -40,13 +53,14 @@ const EmailScreen: React.FC<EmailScreenProps> = ({ onContinue, onBack }) => {
setLoading(true)
setTimeout(() => {
setLoading(false)
}, 2000)
onContinue(email)
}, 1000)
}
return (
<OnboardingContainer onBack={onBack}>
<IconContainer iconName="mail" />
<IconContainer Icon={()=><EmailIconSVG/>} />
<Text style={styles.title}>What's your email?</Text>
<Text style={styles.subtitle}>Please enter your email address.</Text>
@ -88,20 +102,20 @@ const styles = StyleSheet.create({
flexDirection: "row",
alignItems: "center",
backgroundColor: Colors.inputBackground,
borderWidth: 1,
borderColor: Colors.border,
borderWidth:1,
borderRadius: 12,
marginBottom: Spacing.xl,
paddingHorizontal: Spacing.md,
paddingVertical: 2,
shadowColor: Colors.backButtonShadow,
shadowOffset: {
width: 0,
height: 1,
},
shadowOpacity: 0.05,
shadowRadius: 2,
elevation: 1,
// shadowOffset: {
// width: 0,
// height: 1,
// },
// shadowOpacity: 0.05,
// shadowRadius: 2,
// elevation: 1,
},
inputIcon: {
marginRight: Spacing.sm,

View File

@ -18,10 +18,21 @@ import Icon from "react-native-vector-icons/Feather"
import OnboardingContainer from "./OnboardingContainer"
import { selectHospitals } from "../redux"
import { useSelector } from "react-redux"
import IconContainer from "./IconContainer"
import { HospitalSVG } from "../../../../shared/src/components/Icons/SvgIcons"
interface SignUpData {
email: string
password: string
first_name: string
last_name: string
username:string
id_photo_url: {}|null
hospital_id: string
}
interface HospitalSelectionScreenProps {
onContinue: (hospitalId: string) => void
onBack: () => void
Data: SignUpData
}
const hospitalData = [
@ -35,8 +46,8 @@ const hospitalData = [
{ label: "Veterans Medical Center", value: "veterans" },
]
const HospitalSelectionScreen: React.FC<HospitalSelectionScreenProps> = ({ onContinue, onBack }) => {
const [selectedHospital, setSelectedHospital] = useState<string | null>(null)
const HospitalSelectionScreen: React.FC<HospitalSelectionScreenProps> = ({ onContinue, onBack ,Data}) => {
const [selectedHospital, setSelectedHospital] = useState<string | null>(Data.hospital_id)
const [loading, setLoading] = useState(false)
const [isFocus, setIsFocus] = useState(false)
const hospitals :any=useSelector(selectHospitals)
@ -51,7 +62,7 @@ const HospitalSelectionScreen: React.FC<HospitalSelectionScreenProps> = ({ onCon
setTimeout(() => {
setLoading(false)
onContinue(selectedHospital)
}, 1000)
}, 6000)
}
const renderLabel = () => {
@ -70,11 +81,7 @@ const HospitalSelectionScreen: React.FC<HospitalSelectionScreenProps> = ({ onCon
</View> */}
<View style={styles.content}>
<View style={styles.iconContainer}>
<View style={styles.iconWrapper}>
<Icon name="map-pin" size={32} color={Colors.primary} />
</View>
</View>
<IconContainer Icon={()=><HospitalSVG/>} />
<Text style={styles.title}>Select Hospital</Text>
<Text style={styles.subtitle}>Choose the hospital you're affiliated with.</Text>

View File

@ -13,7 +13,7 @@ import Icon from "react-native-vector-icons/Feather"
import { Colors, Spacing } from "../../../../shared/src/theme"
interface IconContainerProps {
iconName: string
Icon: any;
iconSize?: number
containerSize?: number
backgroundColor?: string
@ -21,7 +21,7 @@ interface IconContainerProps {
}
const IconContainer: React.FC<IconContainerProps> = ({
iconName,
Icon,
iconSize = 32,
containerSize = 80,
backgroundColor = Colors.inputBackground,
@ -29,7 +29,7 @@ const IconContainer: React.FC<IconContainerProps> = ({
}) => {
return (
<View style={styles.container}>
<View
{/* <View
style={[
styles.iconWrapper,
{
@ -39,9 +39,9 @@ const IconContainer: React.FC<IconContainerProps> = ({
backgroundColor,
},
]}
>
<Icon name={iconName} size={iconSize} color={iconColor} />
</View>
> */}
{<Icon/>}
{/* </View> */}
</View>
)
}

View File

@ -16,16 +16,27 @@ import IconContainer from "./IconContainer"
import { Colors, Spacing, Typography } from "../../../../shared/src/theme"
import { showError } from "../../../../shared/src/utils/helpers/Toast"
import Icon from "react-native-vector-icons/Feather"
import { UsernameIconSVG } from "../../../../shared/src/components/Icons/SvgIcons"
interface SignUpData {
email: string
password: string
first_name: string
last_name: string
username:string
id_photo_url: {}
hospital_id: string
}
interface NameScreenProps {
onContinue: (firstName: string, lastName: string, username: string) => void
onBack: () => void
Data:SignUpData
}
const NameScreen: React.FC<NameScreenProps> = ({ onContinue, onBack }) => {
const [firstName, setFirstName] = useState("")
const [lastName, setLastName] = useState("")
const [username, setUsername] = useState("")
const NameScreen: React.FC<NameScreenProps> = ({ onContinue, onBack,Data }) => {
const [firstName, setFirstName] = useState(Data.first_name)
const [lastName, setLastName] = useState(Data.last_name)
const [username, setUsername] = useState(Data.username)
const [loading, setLoading] = useState(false)
const handleContinue = () => {
@ -60,7 +71,7 @@ const NameScreen: React.FC<NameScreenProps> = ({ onContinue, onBack }) => {
return (
<OnboardingContainer onBack={onBack}>
<IconContainer iconName="user" />
<IconContainer Icon={()=><UsernameIconSVG/>} />
<Text style={styles.title}>What's your name?</Text>
<Text style={styles.subtitle}>Please enter your details to get started.</Text>
@ -132,14 +143,14 @@ const styles = StyleSheet.create({
marginBottom: Spacing.md,
paddingHorizontal: Spacing.md,
paddingVertical: 2,
shadowColor: Colors.backButtonShadow,
shadowOffset: {
width: 0,
height: 1,
},
shadowOpacity: 0.05,
shadowRadius: 2,
elevation: 1,
// shadowColor: Colors.backButtonShadow,
// shadowOffset: {
// width: 0,
// height: 1,
// },
// shadowOpacity: 0.05,
// shadowRadius: 2,
// elevation: 1,
},
inputIcon: {
marginRight: Spacing.sm,

View File

@ -8,10 +8,11 @@
*/
import type React from "react"
import { View, StyleSheet, TouchableWithoutFeedback, Keyboard } from "react-native"
import { View, StyleSheet, TouchableWithoutFeedback, Keyboard, ScrollView } from "react-native"
import LinearGradient from "react-native-linear-gradient"
import { Colors, Spacing } from "../../../../shared/src/theme"
import BackButton from "./BackButton"
import AngledGradient from "../../../../shared/src/components/Gradient/AngledGradient"
interface OnboardingContainerProps {
children: React.ReactNode
@ -21,20 +22,17 @@ interface OnboardingContainerProps {
const OnboardingContainer: React.FC<OnboardingContainerProps> = ({ children, onBack, showBackButton = true }) => {
return (
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
<LinearGradient
colors={Colors.backgroundGradient}
style={styles.container}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }}
>
<TouchableWithoutFeedback style={{flex:1}} onPress={Keyboard.dismiss}>
<AngledGradient>
{showBackButton && (
<View style={styles.header}>
<BackButton onPress={onBack} />
</View>
)}
<ScrollView>
<View style={styles.content}>{children}</View>
</LinearGradient>
</ScrollView>
</AngledGradient>
</TouchableWithoutFeedback>
)
}
@ -51,7 +49,7 @@ const styles = StyleSheet.create({
content: {
flex: 1,
paddingHorizontal: Spacing.lg,
justifyContent: "center",
justifyContent: 'center',
},
})

View File

@ -16,14 +16,25 @@ import IconContainer from "./IconContainer"
import { Colors, Spacing, Typography } from "../../../../shared/src/theme"
import { showError } from "../../../../shared/src/utils/helpers/Toast"
import Icon from "react-native-vector-icons/Feather"
import { PasswordIconSVG } from "../../../../shared/src/components/Icons/SvgIcons"
interface SignUpData {
email: string
password: string
first_name: string
last_name: string
username:string
id_photo_url: {}
hospital_id: string
}
interface PasswordScreenProps {
onContinue: (password: string) => void
onBack: () => void
Data:SignUpData
}
const PasswordScreen: React.FC<PasswordScreenProps> = ({ onContinue, onBack }) => {
const [password, setPassword] = useState("")
const PasswordScreen: React.FC<PasswordScreenProps> = ({ onContinue, onBack ,Data}) => {
const [password, setPassword] = useState(Data.password)
const [confirmPassword, setConfirmPassword] = useState("")
const [isPasswordVisible, setPasswordVisible] = useState(false)
const [isConfirmPasswordVisible, setConfirmPasswordVisible] = useState(false)
@ -58,7 +69,7 @@ const PasswordScreen: React.FC<PasswordScreenProps> = ({ onContinue, onBack }) =
return (
<OnboardingContainer onBack={onBack}>
<IconContainer iconName="lock" />
<IconContainer Icon={()=><PasswordIconSVG/>} />
<Text style={styles.title}>Create a password</Text>
<Text style={styles.subtitle}>Password must be at least 8 characters</Text>
@ -123,14 +134,14 @@ const styles = StyleSheet.create({
marginBottom: Spacing.md,
paddingHorizontal: Spacing.md,
paddingVertical: 2,
shadowColor: Colors.backButtonShadow,
shadowOffset: {
width: 0,
height: 1,
},
shadowOpacity: 0.05,
shadowRadius: 2,
elevation: 1,
// shadowColor: Colors.backButtonShadow,
// shadowOffset: {
// width: 0,
// height: 1,
// },
// shadowOpacity: 0.05,
// shadowRadius: 2,
// elevation: 1,
},
inputIcon: {
marginRight: Spacing.sm,
@ -142,7 +153,7 @@ const styles = StyleSheet.create({
color: Colors.textPrimary,
},
eyeIcon: {
paddingLeft: Spacing.sm,
padding: Spacing.sm,
},
button: {
marginTop: Spacing.xl,

View File

@ -10,11 +10,13 @@ import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { LoginScreen, SetupBiometricScreen } from '../screens';
import { Colors } from '../../../../shared/src/theme';
import SignUpScreen from '../screens/SignUpScreen';
import ResetPasswordScreen from '../screens/ResetPasswordScreen';
export type AuthStackParamList = {
Login: undefined;
SetupBiometric: undefined;
SignUpScreen:undefined;
ResetPassword:undefined;
};
const Stack = createNativeStackNavigator<AuthStackParamList>();
@ -45,6 +47,11 @@ const AuthNavigator: React.FC = () => (
name="SignUpScreen"
component={SignUpScreen}
options={{ title: 'Setup Biometric' ,headerShown:false}}
/>
<Stack.Screen
name="ResetPassword"
component={ResetPasswordScreen}
options={{ title: 'Setup Biometric' ,headerShown:false}}
/>
</Stack.Navigator>
);

View File

@ -89,6 +89,9 @@ const authSlice = createSlice({
state.user = null;
state.isAuthenticated = false;
},
updateOnboarded(state,action) {
state.user.onboarded = action.payload;
}
},
});
@ -97,6 +100,7 @@ export const {
loginSuccess,
loginFailure,
logout,
updateOnboarded
} = authSlice.actions;
export default authSlice.reducer;

View File

@ -22,7 +22,7 @@ interface HospitalState {
}
const initialState: HospitalState = {
hospitals: null,
hospitals: [],
loading: false,
error: null,
};

View File

@ -13,7 +13,8 @@ import {
TouchableWithoutFeedback,
Keyboard,
TouchableOpacity,
TextInput, // Using default TextInput for container styling
TextInput,
ScrollView, // Using default TextInput for container styling
} from 'react-native';
import { Button } from '../../../../shared/src/components/Button';
import { Colors, Spacing, Typography } from '../../../../shared/src/theme';
@ -23,6 +24,8 @@ import { showError } from '../../../../shared/src/utils/helpers/Toast';
import { validateEmail } from '../../../../shared/src/utils/validation/validators';
import Icon from 'react-native-vector-icons/Feather';
import { useNavigation } from '@react-navigation/native';
import AngledGradient from '../../../../shared/src/components/Gradient/AngledGradient';
import { LoginSVG, SecureLoginSVG } from '../../../../shared/src/components/Icons/SvgIcons';
const LoginScreen: React.FC = () => {
const [email, setEmail] = useState('');
@ -48,12 +51,17 @@ const LoginScreen: React.FC = () => {
// Simulate a network request for 4 seconds
setTimeout(() => {
setLoading(false); // Stop loading after 4 seconds
}, 4000);
}, 6000);
};
return (
<AngledGradient>
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
<ScrollView>
<View style={styles.container}>
<View style={{alignItems:'center'}}>
<SecureLoginSVG/>
</View>
<Text style={styles.title}>Radiologist</Text>
{/* Email Input */}
@ -103,14 +111,16 @@ const LoginScreen: React.FC = () => {
/>
</View>
</ScrollView>
</TouchableWithoutFeedback>
</AngledGradient>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: Colors.background,
// backgroundColor: Colors.background,
justifyContent: 'center',
padding: Spacing.lg,
},

View File

@ -0,0 +1,336 @@
"use client"
/*
* File: ResetPasswordScreen.tsx
* Description: Password reset screen with gradient background and shared components.
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import type React from "react"
import { useState, useEffect } from "react"
import { View, Text, StyleSheet, TextInput, TouchableOpacity } from "react-native"
import { Button } from "../../../../shared/src/components/Button"
import OnboardingContainer from "../components/OnboardingContainer"
import IconContainer from "../components/IconContainer"
import { Colors, Spacing, Typography } from "../../../../shared/src/theme"
import { showError, showSuccess } from "../../../../shared/src/utils/helpers/Toast"
import Icon from "react-native-vector-icons/Feather"
import { PasswordIconSVG } from "../../../../shared/src/components/Icons/SvgIcons"
import { authAPI } from "../services/authAPI"
import { useDispatch, useSelector } from "react-redux"
import { selectUser } from "../redux"
import { updateOnboarded } from "../redux/authSlice"
interface PasswordScreenProps {
onContinue: (password: string) => void
onBack: () => void
}
interface PasswordRule {
id: string
label: string
validator: (password: string) => boolean
isValid: boolean
}
const ResetPasswordScreen: React.FC = () => {
const [password, setPassword] = useState("")
const [confirmPassword, setConfirmPassword] = useState("")
const [isPasswordVisible, setPasswordVisible] = useState(false)
const [isConfirmPasswordVisible, setConfirmPasswordVisible] = useState(false)
const [loading, setLoading] = useState(false);
const user=useSelector(selectUser);
const dispatch=useDispatch()
const [passwordRules, setPasswordRules] = useState<PasswordRule[]>([
{
id: 'length',
label: 'At least 8 characters',
validator: (pwd: string) => pwd.length >= 8,
isValid: false
},
{
id: 'uppercase',
label: 'One uppercase letter',
validator: (pwd: string) => /[A-Z]/.test(pwd),
isValid: false
},
{
id: 'lowercase',
label: 'One lowercase letter',
validator: (pwd: string) => /[a-z]/.test(pwd),
isValid: false
},
{
id: 'number',
label: 'One number',
validator: (pwd: string) => /\d/.test(pwd),
isValid: false
},
{
id: 'special',
label: 'One special character',
validator: (pwd: string) => /[!@#$%^&*(),.?":{}|<>]/.test(pwd),
isValid: false
},
{
id: 'match',
label: 'Passwords match',
validator: (pwd:string) => {if(pwd.length > 0 && confirmPassword.length > 0 && pwd === confirmPassword){
return true
}else{
return false
}
}, // This will be handled separately
isValid: false
}
])
console.log('password rules',passwordRules)
const validatePassword = (pwd: string): boolean => {
return passwordRules.every(rule => rule.isValid)
}
const updatePasswordRules = (pwd: string) => {
setPasswordRules(prevRules =>
prevRules.map(rule => {
if (rule.id === 'match') {
return {
...rule,
isValid: pwd.length > 0 && confirmPassword.length > 0 && pwd === confirmPassword
}
}
return {
...rule,
isValid: rule.validator(pwd)
}
})
)
}
const updatePasswordMatchRule = () => {
setPasswordRules(prevRules =>
prevRules.map(rule => {
// if (rule.id === 'match') {
// return {
// ...rule,
// isValid: password.length > 0 && confirmPassword.length > 0 && password === confirmPassword
// }
// }
return rule
})
)
}
useEffect(() => {
updatePasswordRules(password)
}, [password,confirmPassword])
// useEffect(() => {
// updatePasswordMatchRule()
// }, [password, confirmPassword])
const handlePasswordChange = (pwd: string) => {
setPassword(pwd)
}
const handleConfirmPasswordChange = (pwd: string) => {
setConfirmPassword(pwd)
}
const handleContinue = () => {
if (!password.trim() || !confirmPassword.trim()) {
showError("Validation Error", "Both password fields are required.")
return
}
console.log('Validation',validatePassword(password))
if (!validatePassword(password)) {
showError("Validation Error", "Please meet all password requirements.")
return
}
if (password !== confirmPassword) {
showError("Validation Error", "Passwords do not match.")
return
}
setLoading(true)
setTimeout(() => {
setLoading(false)
onReset(password)
}, 1000)
}
const onBack=()=>{}
const onReset=async (password:string)=>{
const response :any=await authAPI.changepassword({password,token:user?.access_token});
console.log('reset response',response);
if(response.data&&response.data.message){
if(response.data.success){
showSuccess(response.data.message);
dispatch(updateOnboarded(true))
}else{
showError(response.data.message)
}
}else{
showError('eeror while changing password')
}
}
const renderPasswordRule = (rule: PasswordRule) => (
<View key={rule.id} style={styles.ruleContainer}>
<View style={[styles.checkbox, rule.isValid && styles.checkboxChecked]}>
{rule.isValid && (
<Icon name="check" size={12} color={"#FFFFFF"} />
)}
</View>
<Text style={[styles.ruleText, rule.isValid && styles.ruleTextValid]}>
{rule.label}
</Text>
</View>
)
return (
<OnboardingContainer onBack={onBack} showBackButton={false}>
<IconContainer Icon={()=><PasswordIconSVG/>} />
<Text style={styles.title}>Reset your password</Text>
<Text style={styles.subtitle}>Create a strong password with the following requirements</Text>
<View style={styles.inputContainer}>
<Icon name="lock" size={20} color={Colors.textSecondary} style={styles.inputIcon} />
<TextInput
placeholder="Password"
value={password}
onChangeText={handlePasswordChange}
style={styles.inputField}
secureTextEntry={!isPasswordVisible}
placeholderTextColor={Colors.textMuted}
/>
<TouchableOpacity onPress={() => setPasswordVisible(!isPasswordVisible)} style={styles.eyeIcon}>
<Icon name={isPasswordVisible ? "eye-off" : "eye"} size={22} color={Colors.textSecondary} />
</TouchableOpacity>
</View>
<View style={styles.inputContainer}>
<Icon name="lock" size={20} color={Colors.textSecondary} style={styles.inputIcon} />
<TextInput
placeholder="Confirm Password"
value={confirmPassword}
onChangeText={handleConfirmPasswordChange}
style={styles.inputField}
secureTextEntry={!isConfirmPasswordVisible}
placeholderTextColor={Colors.textMuted}
/>
<TouchableOpacity onPress={() => setConfirmPasswordVisible(!isConfirmPasswordVisible)} style={styles.eyeIcon}>
<Icon name={isConfirmPasswordVisible ? "eye-off" : "eye"} size={22} color={Colors.textSecondary} />
</TouchableOpacity>
</View>
{/* Password Rules Section */}
<View style={styles.rulesContainer}>
<Text style={styles.rulesTitle}>Password Requirements:</Text>
<View style={styles.rulesGrid}>
{passwordRules.map(renderPasswordRule)}
</View>
</View>
<Button title="Reset" onPress={handleContinue} style={styles.button} loading={loading} />
</OnboardingContainer>
)
}
const styles = StyleSheet.create({
title: {
fontFamily: Typography.fontFamily.bold,
fontSize: Typography.fontSize.title,
color: Colors.textPrimary,
textAlign: "center",
marginBottom: Spacing.sm,
},
subtitle: {
fontFamily: Typography.fontFamily.regular,
fontSize: Typography.fontSize.md,
color: Colors.textSecondary,
textAlign: "center",
marginBottom: Spacing.xl,
},
inputContainer: {
flexDirection: "row",
alignItems: "center",
backgroundColor: Colors.inputBackground,
borderWidth: 1,
borderColor: Colors.border,
borderRadius: 12,
marginBottom: Spacing.md,
paddingHorizontal: Spacing.md,
paddingVertical: 2,
},
inputIcon: {
marginRight: Spacing.sm,
},
inputField: {
flex: 1,
paddingVertical: Spacing.md,
fontSize: Typography.fontSize.md,
color: Colors.textPrimary,
},
eyeIcon: {
paddingLeft: Spacing.sm,
},
rulesContainer: {
marginTop: Spacing.sm,
marginBottom: Spacing.lg,
paddingHorizontal: Spacing.sm,
},
rulesTitle: {
fontFamily: Typography.fontFamily.medium,
fontSize: Typography.fontSize.sm,
color: Colors.textPrimary,
marginBottom: Spacing.sm,
},
rulesGrid: {
gap: Spacing.xs,
},
ruleContainer: {
flexDirection: "row",
alignItems: "center",
marginBottom: Spacing.xs,
},
checkbox: {
width: 18,
height: 18,
borderRadius: 4,
borderWidth: 2,
borderColor: Colors.border || "#E5E5E5",
backgroundColor: Colors.inputBackground || "#F8F9FA",
marginRight: Spacing.sm,
alignItems: "center",
justifyContent: "center",
},
checkboxChecked: {
backgroundColor: Colors.primary || "#007AFF",
borderColor: Colors.primary || "#007AFF",
},
ruleText: {
fontFamily: Typography.fontFamily.regular,
fontSize: Typography.fontSize.sm,
color: Colors.textSecondary,
flex: 1,
},
ruleTextValid: {
color: Colors.success || Colors.primary || "#28A745",
},
button: {
marginTop: Spacing.xl,
},
})
export default ResetPasswordScreen
/*
* End of File: ResetPasswordScreen.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -9,7 +9,7 @@
import type React from "react"
import { useEffect, useState } from "react"
import { View, StyleSheet, StatusBar, Platform } from "react-native"
import { View, StyleSheet, StatusBar, Platform, Keyboard} from "react-native"
import { Colors } from "../../../../shared/src/theme"
import { useAppDispatch } from "../../../redux/hooks"
import { showError, showSuccess } from "../../../../shared/src/utils/helpers/Toast"
@ -20,16 +20,17 @@ import PasswordScreen from "../components/PasswordScreen"
import NameScreen from "../components/NameScreen"
import DocumentUploadScreen from "../components/DocumentUploadScreen"
import HospitalSelectionScreen from "../components/HospitalSelectionScreen"
import EmailAlreadyRegisteredModal from "../components/EmailAlreadyRegisteredModal"
import { authAPI } from "../services/authAPI"
import { setHospitals } from "../redux/hospitalSlice"
interface SignUpData {
interface SignUpDataInterface {
email: string
password: string
first_name: string
last_name: string
username:string
id_photo_url: {}
id_photo_url: {}|null
hospital_id: string
}
@ -39,15 +40,46 @@ interface SignUpScreenProps {
const SignUpScreen: React.FC<SignUpScreenProps> = ({ navigation }) => {
const [currentStep, setCurrentStep] = useState(0)
const [signUpData, setSignUpData] = useState<Partial<SignUpData>>({})
const [showEmailRegisteredModal, setShowEmailRegisteredModal] = useState(false)
const [signUpData, setSignUpData] = useState<Partial<SignUpDataInterface>>({
email: '',
password: '',
first_name: '',
last_name: '',
username:'',
id_photo_url: null,
hospital_id: ''
})
const dispatch = useAppDispatch()
const steps = ["email", "password", "name", "document", "hospital"]
const handleEmailContinue = (email: string) => {
const handleEmailContinue = async (email: string) => {
setSignUpData((prev) => ({ ...prev, email }))
setCurrentStep(1)
try {
const response:any=await authAPI.validatemail({email});
console.log('response',response)
if(response.status==409&&response.data.message){
// Show modal instead of toast for already registered email
setShowEmailRegisteredModal(true)
}
if(response.status==200&&response.data.message){
setCurrentStep(1);
}
} catch (error) {
showError('error while validating your email')
}
}
const handleCloseModal = () => {
setShowEmailRegisteredModal(false)
}
const handleGoToLogin = () => {
setShowEmailRegisteredModal(false)
navigation.navigate('Login')
}
useEffect(()=>{
fetchHospitals();
},[])
@ -100,7 +132,12 @@ const SignUpScreen: React.FC<SignUpScreenProps> = ({ navigation }) => {
navigation.navigate('Login');
// dispatch(setHospitals(response.data.data))
} else {
showError('error while signup')
showError('error while signup');
if( response.data && response.data.message ) {
//@ts-ignore
showError(response.data.message)
}
}
} catch (error: any) {
console.log('error',error)
@ -114,9 +151,21 @@ const SignUpScreen: React.FC<SignUpScreenProps> = ({ navigation }) => {
setCurrentStep(2)
}
const handleNameContinue = (firstName: string, lastName: string,userName:string) => {
const handleNameContinue = async (firstName: string, lastName: string,userName:string) => {
setSignUpData((prev) => ({ ...prev, first_name : firstName, last_name : lastName ,username: userName}))
setCurrentStep(3)
try {
const response:any=await authAPI.validateusername(userName);
console.log('response',response)
if(response.status==409&&response.data.message){
showError(response.data.message);
}
if(response.status==200&&response.data.message){
setCurrentStep(3);
}
} catch (error) {
showError('error while validating your email')
}
}
const handleDocumentContinue = (documentUri: string) => {
@ -152,19 +201,20 @@ const SignUpScreen: React.FC<SignUpScreenProps> = ({ navigation }) => {
}
const renderCurrentStep = () => {
console.log('signupdate',signUpData)
switch (currentStep) {
case 0:
return <EmailScreen onContinue={handleEmailContinue} onBack={handleBack} />
return <EmailScreen onContinue={handleEmailContinue} onBack={handleBack} Data={signUpData} />
case 1:
return <PasswordScreen onContinue={handlePasswordContinue} onBack={handleBack} />
return <PasswordScreen onContinue={handlePasswordContinue} onBack={handleBack} Data={signUpData} />
case 2:
return <NameScreen onContinue={handleNameContinue} onBack={handleBack} />
return <NameScreen onContinue={handleNameContinue} onBack={handleBack} Data={signUpData} />
case 3:
return <DocumentUploadScreen onContinue={handleDocumentContinue} onBack={handleBack} />
return <DocumentUploadScreen onContinue={handleDocumentContinue} onBack={handleBack} Data={signUpData} />
case 4:
return <HospitalSelectionScreen onContinue={handleHospitalContinue} onBack={handleBack} />
return <HospitalSelectionScreen onContinue={handleHospitalContinue} onBack={handleBack} Data={signUpData} />
default:
return <EmailScreen onContinue={handleEmailContinue} onBack={handleBack} />
return <EmailScreen onContinue={handleEmailContinue} onBack={handleBack} Data={signUpData}/>
}
}
@ -172,6 +222,12 @@ const SignUpScreen: React.FC<SignUpScreenProps> = ({ navigation }) => {
<View style={styles.container}>
<StatusBar barStyle="dark-content" backgroundColor={Colors.background} />
{renderCurrentStep()}
<EmailAlreadyRegisteredModal
visible={showEmailRegisteredModal}
onClose={handleCloseModal}
onGoToLogin={handleGoToLogin}
/>
</View>
)
}

View File

@ -22,6 +22,12 @@ export const authAPI = {
gethospitals: () => api.get('/api/hospitals/hospitals/app_user/hospitals', {},buildHeaders()),
//user signup
signup: (formData:any) => api.post('/api/auth/auth/admin/create-user-fromapp', formData,buildHeaders({ contentType: 'multipart/form-data' })),
//validate email
validatemail: (payload:{email:string}) => api.post('/api/auth/auth/check-email', payload,buildHeaders()),
//change password
changepassword: (payload:{password:string,token:string | undefined}) => api.post('/api/auth/onboarding/change-password', {password:payload.password},buildHeaders({token:payload.token})),
//validate username
validateusername: (username:string|undefined) => api.post('/api/auth/auth/check-username', {username},buildHeaders())
// Add more endpoints as needed
};

View File

@ -72,12 +72,12 @@ const DashboardScreen: React.FC<{ navigation: any }> = ({ navigation }) => {
const [selectedCase, setSelectedCase] = useState<MedicalCase | null>(null);
const dispatch = useDispatch();
const { access_token, display_name } = useSelector((state: any) => state.auth.user);
const user = useSelector((state: any) => state.auth.user);
const patientData: MedicalCase[] = useSelector(selectPatients) || [];
useEffect(() => {
//@ts-ignore
dispatch(fetchPatients(access_token));
dispatch(fetchPatients(user?.access_token));
}, []);
// Filter cases based on search
@ -202,7 +202,7 @@ const DashboardScreen: React.FC<{ navigation: any }> = ({ navigation }) => {
/> */}
<InfoCard>
<Text style={styles.infoTitle}>Welcome, Dr. {display_name}</Text>
<Text style={styles.infoTitle}>Welcome, Dr. {user?.display_name}</Text>
<Text style={styles.infoText}>
On-Call Status: <Text style={styles.active}>ACTIVE</Text>
</Text>

View File

@ -215,7 +215,7 @@ const ProfileScreen: React.FC<{ navigation: any }> = ({ navigation }) => {
</View>
{/* Onboarding Section */}
{!user.onboarding_completed && (
{!user.onboarded && (
<View style={styles.onboardingSection}>
<SectionHeader title="Onboarding" icon="alert-circle" />
<Text style={styles.onboardingMessage}>{user.onboarding_message}</Text>

View File

@ -8,11 +8,12 @@
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { useSelector } from 'react-redux';
import { selectIsAuthenticated } from '../modules/Auth/redux/authSelectors';
import { selectIsAuthenticated, selectUser } 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';
import ResetPasswordScreen from '../modules/Auth/screens/ResetPasswordScreen';
/**
* AppNavigator - root navigator for the app
@ -20,9 +21,12 @@ import { DashboardScreen } from '../modules/Dashboard/screens';
*/
const AppNavigator: React.FC = () => {
const isAuthenticated = useSelector(selectIsAuthenticated);
const user=useSelector(selectUser);
return (
<NavigationContainer >
{isAuthenticated ? <TabNavigator /> : <AuthNavigator />}
{isAuthenticated ? user?.onboarded?<TabNavigator />: <ResetPasswordScreen/> : <AuthNavigator />}
{/* <ResetPasswordScreen/> */}
</NavigationContainer>
);
};

View File

@ -55,7 +55,7 @@ const styles = StyleSheet.create({
fontFamily: Typography.fontFamily.bold,
},
disabled: {
backgroundColor: Colors.inactiveState,
backgroundColor: Colors.primary,
},
});

View File

@ -0,0 +1,98 @@
/*
* File: AngledGradient.tsx
* Description: Provides a visually appealing single color gradient background for screens.
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React, { ReactNode } from 'react';
import { StatusBar, StyleSheet, View } from 'react-native';
import Svg, { Defs, LinearGradient, Stop, Rect } from 'react-native-svg';
import { useSelector } from 'react-redux';
import { getAdjacentColors, hexWithOpacity } from '../../../src/utils/helpers/gradient';
import {Colors} from '../../../src/theme';
/**
* AngledGradient Component
* Renders a background with a single color linear gradient that fades from top to bottom.
* Wraps children with this background.
*
* @param {ReactNode} children - The content to render on top of the gradient.
*/
interface AngledGradientProps {
children?: ReactNode;
}
const AngledGradient: React.FC<AngledGradientProps> = ({ children }) => {
return (
<View style={styles.container}>
{/* Transparent status bar for immersive effect */}
<StatusBar
animated={true}
backgroundColor={hexWithOpacity(Colors.primary,0.3)}
barStyle="dark-content"
/>
{/* Single color gradient background */}
<Svg height="100%" width="100%" style={styles.gradientContainer}>
<Defs>
<LinearGradient id="mainGradient" x1="0%" y1="0%" x2="0%" y2="100%">
<Stop offset="0%" stopColor={Colors.primary} stopOpacity="0.3" />
<Stop offset="60%" stopColor={Colors.primary} stopOpacity="0.1" />
<Stop offset="100%" stopColor={Colors.primary} stopOpacity="0" />
</LinearGradient>
</Defs>
<Rect
x="0"
y="0"
width="100%"
height="100%"
fill="url(#mainGradient)"
/>
</Svg>
{/* Render children on top of the gradient background */}
{children}
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: Colors.background,
},
gradientContainer: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
},
gradientOverlay: {
position: "absolute",
width: "150%",
height: "150%",
borderRadius: 400,
opacity: 0.5,
},
image: {
position: 'absolute',
width: 400,
height: 400,
},
blurContainer: {
position: 'absolute',
width: '100%',
height: '100%',
},
});
export default AngledGradient;
/*
* End of File: AngledGradient.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,477 @@
import React from 'react';
import Svg, { Path, Rect, Circle, Ellipse, Defs, LinearGradient, Stop, Polygon } from 'react-native-svg';
import { Colors } from '../../theme';
// Screen 1: Email Icon with Document/Folder
export const EmailIconSVG = ({ width = 180, height = 180 }) => (
<Svg width={width} height={height} viewBox="0 0 120 120">
<Defs>
<LinearGradient id="folderGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<Stop offset="0%" stopColor="#FF8A65" />
<Stop offset="100%" stopColor="#FF7043" />
</LinearGradient>
<LinearGradient id="documentGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<Stop offset="0%" stopColor="#FFFFFF" />
<Stop offset="100%" stopColor="#F5F5F5" />
</LinearGradient>
</Defs>
{/* Purple base/stand */}
<Rect x="25" y="75" width="70" height="8" rx="4" fill={Colors.primary}/>
<Rect x="30" y="70" width="60" height="12" rx="6" fill={Colors.primary} />
{/* Orange folder/document container */}
<Rect x="35" y="35" width="50" height="40" rx="6" fill="url(#folderGradient)" />
{/* Document pages */}
<Rect x="40" y="40" width="40" height="30" rx="3" fill="url(#documentGradient)" stroke={Colors.primary}strokeWidth="1" />
<Rect x="42" y="42" width="36" height="26" rx="2" fill="#FFFFFF" />
{/* Email icon overlay */}
<Circle cx="55" cy="45" r="8" fill="#FF5722" />
<Path d="M50 42 L55 46 L60 42" stroke="#FFFFFF" strokeWidth="2" fill="none" strokeLinecap="round" strokeLinejoin="round" />
{/* Document lines */}
<Rect x="45" y="52" width="20" height="2" rx="1" fill={Colors.primary}/>
<Rect x="45" y="57" width="25" height="2" rx="1" fill={Colors.primary}/>
<Rect x="45" y="62" width="18" height="2" rx="1" fill={Colors.primary}/>
</Svg>
);
// Screen 2: Password/Security Icon with Lock
export const PasswordIconSVG = ({ width = 180, height = 180 }) => (
<Svg width={width} height={height} viewBox="0 0 120 120">
<Defs>
<LinearGradient id="lockGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<Stop offset="0%" stopColor="#4CAF50" />
<Stop offset="100%" stopColor="#388E3C" />
</LinearGradient>
<LinearGradient id="shieldGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<Stop offset="0%" stopColor="#81C784" />
<Stop offset="100%" stopColor="#66BB6A" />
</LinearGradient>
</Defs>
{/* Purple base/stand */}
<Rect x="25" y="75" width="70" height="8" rx="4" fill={Colors.primary} />
<Rect x="30" y="70" width="60" height="12" rx="6" fill={Colors.primary}/>
{/* Green document/shield container */}
<Rect x="35" y="35" width="50" height="40" rx="6" fill="url(#shieldGradient)" />
{/* Document base */}
<Rect x="40" y="40" width="40" height="30" rx="3" fill="#FFFFFF" stroke="#E0E0E0" strokeWidth="1" />
{/* Lock icon */}
<Circle cx="60" cy="52" r="12" fill="url(#lockGradient)" />
{/* Lock shackle */}
<Path
d="M56 48 C56 45.8 57.8 44 60 44 C62.2 44 64 45.8 64 48 L64 52"
stroke="#FFFFFF"
strokeWidth="2.5"
fill="none"
strokeLinecap="round"
/>
{/* Lock body */}
<Rect x="57" y="50" width="6" height="6" rx="1" fill="#FFFFFF" />
<Circle cx="60" cy="53" r="1" fill="url(#lockGradient)" />
{/* Document lines */}
<Rect x="45" y="60" width="15" height="1.5" rx="0.75" fill="#E0E0E0" />
<Rect x="65" y="60" width="10" height="1.5" rx="0.75" fill="#E0E0E0" />
<Rect x="45" y="64" width="25" height="1.5" rx="0.75" fill="#E0E0E0" />
</Svg>
);
// Screen 3: Username/Profile Icon
export const UsernameIconSVG = ({ width = 180, height = 180 }) => (
<Svg width={width} height={height} viewBox="0 0 120 120">
<Defs>
<LinearGradient id="profileGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<Stop offset="0%" stopColor={Colors.primary} />
<Stop offset="100%" stopColor="#7B1FA2" />
</LinearGradient>
<LinearGradient id="badgeGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<Stop offset="0%" stopColor="#FF9800" />
<Stop offset="100%" stopColor="#F57C00" />
</LinearGradient>
</Defs>
{/* Purple base/stand */}
<Rect x="25" y="75" width="70" height="8" rx="4" fill={Colors.primary}/>
<Rect x="30" y="70" width="60" height="12" rx="6" fill={Colors.primary}/>
{/* Document container */}
<Rect x="35" y="35" width="50" height="40" rx="6" fill="orange" />
{/* Document base */}
<Rect x="40" y="40" width="40" height="30" rx="3" fill="#FFFFFF" stroke="#E0E0E0" strokeWidth="1" />
{/* Profile circle background */}
<Circle cx="60" cy="50" r="10" fill="url(#profileGradient)" />
{/* Profile icon - person */}
<Circle cx="60" cy="47" r="3" fill="#FFFFFF" />
<Path
d="M54 56 C54 53 56.7 51 60 51 C63.3 51 66 53 66 56"
stroke="#FFFFFF"
strokeWidth="2"
fill="none"
strokeLinecap="round"
/>
{/* Orange badge/notification */}
<Circle cx="68" cy="42" r="6" fill="url(#badgeGradient)" />
<Path d="M65 42 L67 44 L71 39" stroke="#FFFFFF" strokeWidth="1.5" fill="none" strokeLinecap="round" strokeLinejoin="round" />
{/* Document lines */}
<Rect x="45" y="62" width="20" height="1.5" rx="0.75" fill="#E0E0E0" />
<Rect x="45" y="65" width="25" height="1.5" rx="0.75" fill="#E0E0E0" />
</Svg>
);
export const HospitalSVG = ({ width = 160, height = 160 }) => (
<Svg width={width} height={height} viewBox="0 0 120 120">
<Defs>
<LinearGradient id="buildingGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<Stop offset="0%" stopColor="#E3F2FD" />
<Stop offset="100%" stopColor="#BBDEFB" />
</LinearGradient>
<LinearGradient id="roofGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<Stop offset="0%" stopColor="#42A5F5" />
<Stop offset="100%" stopColor="#1E88E5" />
</LinearGradient>
<LinearGradient id="crossGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<Stop offset="0%" stopColor="#FF5252" />
<Stop offset="100%" stopColor="#F44336" />
</LinearGradient>
<LinearGradient id="doorGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<Stop offset="0%" stopColor="#8D6E63" />
<Stop offset="100%" stopColor="#5D4037" />
</LinearGradient>
</Defs>
{/* Ground/Base */}
<Rect x="10" y="100" width="100" height="10" rx="2" fill="#A5D6A7" />
{/* Main Hospital Building */}
<Rect x="25" y="45" width="70" height="55" rx="3" fill="url(#buildingGradient)" stroke="#90CAF9" strokeWidth="1" />
{/* Hospital Roof */}
<Polygon points="20,45 60,25 100,45" fill="url(#roofGradient)" />
{/* Entrance Door */}
<Rect x="50" y="75" width="20" height="25" rx="2" fill="url(#doorGradient)" />
<Circle cx="65" cy="87" r="1.5" fill="#FFAB91" />
{/* Windows - First Floor */}
<Rect x="32" y="55" width="12" height="12" rx="1" fill="#FFFFFF" stroke="#E1F5FE" strokeWidth="1" />
<Rect x="76" y="55" width="12" height="12" rx="1" fill="#FFFFFF" stroke="#E1F5FE" strokeWidth="1" />
{/* Windows - Second Floor */}
<Rect x="32" y="75" width="12" height="12" rx="1" fill="#FFFFFF" stroke="#E1F5FE" strokeWidth="1" />
<Rect x="76" y="75" width="12" height="12" rx="1" fill="#FFFFFF" stroke="#E1F5FE" strokeWidth="1" />
{/* Window Cross Dividers */}
<Path d="M38 55 L38 67" stroke="#E1F5FE" strokeWidth="1" />
<Path d="M32 61 L44 61" stroke="#E1F5FE" strokeWidth="1" />
<Path d="M82 55 L82 67" stroke="#E1F5FE" strokeWidth="1" />
<Path d="M76 61 L88 61" stroke="#E1F5FE" strokeWidth="1" />
<Path d="M38 75 L38 87" stroke="#E1F5FE" strokeWidth="1" />
<Path d="M32 81 L44 81" stroke="#E1F5FE" strokeWidth="1" />
<Path d="M82 75 L82 87" stroke="#E1F5FE" strokeWidth="1" />
<Path d="M76 81 L88 81" stroke="#E1F5FE" strokeWidth="1" />
{/* Medical Cross on Building */}
<Circle cx="60" cy="60" r="12" fill="#FFFFFF" stroke="#E0E0E0" strokeWidth="1" />
<Rect x="57" y="52" width="6" height="16" rx="1" fill="url(#crossGradient)" />
<Rect x="52" y="57" width="16" height="6" rx="1" fill="url(#crossGradient)" />
{/* Hospital Sign */}
<Rect x="45" y="35" width="30" height="8" rx="2" fill="#FFFFFF" stroke="#E0E0E0" strokeWidth="1" />
<Rect x="47" y="37" width="26" height="4" rx="1" fill="#1976D2" />
{/* Ambulance Area Markings */}
<Rect x="15" y="95" width="25" height="3" rx="1" fill="#FFEB3B" />
<Rect x="80" y="95" width="25" height="3" rx="1" fill="#FFEB3B" />
{/* Small Trees/Landscaping */}
<Circle cx="15" cy="90" r="5" fill="#4CAF50" />
<Rect x="13" y="90" width="4" height="8" fill="#8D6E63" />
<Circle cx="105" cy="90" r="5" fill="#4CAF50" />
<Rect x="103" y="90" width="4" height="8" fill="#8D6E63" />
{/* Small Medical Cross on Roof */}
<Rect x="58" y="28" width="2" height="6" fill="#FF5252" />
<Rect x="56" y="30" width="6" height="2" fill="#FF5252" />
</Svg>
);
// Upload/Cloud Icon for Hospital Data Upload
export const HospitalUploadSVG = ({ width = 160, height = 160 }) => (
<Svg width={width} height={height} viewBox="0 0 120 120">
<Defs>
<LinearGradient id="cloudGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<Stop offset="0%" stopColor="#E3F2FD" />
<Stop offset="100%" stopColor="#BBDEFB" />
</LinearGradient>
<LinearGradient id="hospitalIconGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<Stop offset="0%" stopColor="#FF5252" />
<Stop offset="100%" stopColor="#F44336" />
</LinearGradient>
</Defs>
{/* Cloud Base */}
<Ellipse cx="60" cy="65" rx="35" ry="20" fill="url(#cloudGradient)" />
<Circle cx="35" cy="65" r="15" fill="url(#cloudGradient)" />
<Circle cx="85" cy="65" r="15" fill="url(#cloudGradient)" />
<Circle cx="60" cy="50" r="18" fill="url(#cloudGradient)" />
{/* Upload Arrow */}
<Path d="M60 35 L60 70" stroke="#1976D2" strokeWidth="3" strokeLinecap="round" />
<Path d="M52 43 L60 35 L68 43" stroke="#1976D2" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" fill="none" />
{/* Hospital Cross in Cloud */}
<Circle cx="60" cy="65" r="8" fill="#FFFFFF" />
<Rect x="58" y="59" width="4" height="12" rx="1" fill="url(#hospitalIconGradient)" />
<Rect x="55" y="62" width="10" height="4" rx="1" fill="url(#hospitalIconGradient)" />
{/* Data Points/Dots */}
<Circle cx="45" cy="55" r="2" fill="#4CAF50" />
<Circle cx="75" cy="55" r="2" fill="#4CAF50" />
<Circle cx="50" cy="75" r="2" fill="#4CAF50" />
<Circle cx="70" cy="75" r="2" fill="#4CAF50" />
{/* Upload Progress Indicator */}
<Rect x="40" y="85" width="40" height="4" rx="2" fill="#E0E0E0" />
<Rect x="40" y="85" width="25" height="4" rx="2" fill="#4CAF50" />
</Svg>
);
export const LoginSVG = ({ width = 120, height = 120 }) => (
<Svg width={width} height={height} viewBox="0 0 120 120">
<Defs>
<LinearGradient id="userGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<Stop offset="0%" stopColor="#42A5F5" />
<Stop offset="100%" stopColor="#1E88E5" />
</LinearGradient>
<LinearGradient id="keyGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<Stop offset="0%" stopColor="#FFB74D" />
<Stop offset="100%" stopColor="#FF9800" />
</LinearGradient>
<LinearGradient id="shieldGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<Stop offset="0%" stopColor="#66BB6A" />
<Stop offset="100%" stopColor="#4CAF50" />
</LinearGradient>
</Defs>
{/* Purple base/stand */}
<Rect x="25" y="75" width="70" height="8" rx="4" fill="#8E24AA" />
<Rect x="30" y="70" width="60" height="12" rx="6" fill="#9C27B0" />
{/* Main container */}
<Rect x="35" y="35" width="50" height="40" rx="6" fill="#E8EAF6" />
{/* Document/Screen base */}
<Rect x="40" y="40" width="40" height="30" rx="3" fill="#FFFFFF" stroke="#E0E0E0" strokeWidth="1" />
{/* User Profile Circle */}
<Circle cx="55" cy="50" r="8" fill="url(#userGradient)" />
{/* User Icon - Head */}
<Circle cx="55" cy="47" r="3" fill="#FFFFFF" />
{/* User Icon - Body */}
<Path
d="M49 56 C49 53 51.7 51 55 51 C58.3 51 61 53 61 56"
stroke="#FFFFFF"
strokeWidth="2"
fill="none"
strokeLinecap="round"
/>
{/* Key Icon */}
<Circle cx="68" cy="52" r="6" fill="url(#keyGradient)" />
{/* Key shaft */}
<Rect x="72" y="50.5" width="8" height="3" rx="1.5" fill="url(#keyGradient)" />
{/* Key teeth */}
<Rect x="78" y="48.5" width="2" height="2" fill="url(#keyGradient)" />
<Rect x="78" y="53.5" width="2" height="2" fill="url(#keyGradient)" />
{/* Key hole in user circle */}
<Circle cx="55" cy="54" r="1.5" fill="#1565C0" />
<Rect x="54.2" y="54" width="1.6" height="2" fill="#1565C0" />
{/* Login text representation */}
<Rect x="45" y="60" width="15" height="2" rx="1" fill="#E0E0E0" />
<Rect x="45" y="64" width="20" height="2" rx="1" fill="#E0E0E0" />
</Svg>
);
// Secure Login Icon - Lock with Shield
export const SecureLoginSVG = ({ width = 250, height = 250 }) => (
<Svg width={width} height={height} viewBox="0 0 120 120">
<Defs>
<LinearGradient id="lockBodyGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<Stop offset="0%" stopColor="#4CAF50" />
<Stop offset="100%" stopColor="#388E3C" />
</LinearGradient>
<LinearGradient id="shackleGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<Stop offset="0%" stopColor="#81C784" />
<Stop offset="100%" stopColor="#66BB6A" />
</LinearGradient>
</Defs>
{/* Purple base/stand */}
<Rect x="25" y="75" width="70" height="8" rx="4" fill={Colors.primary} />
<Rect x="30" y="70" width="60" height="12" rx="6" fill={Colors.primary} />
{/* Main container */}
<Rect x="35" y="35" width="50" height="40" rx="6" fill="#E8F5E8" />
{/* Document/Screen base */}
<Rect x="40" y="40" width="40" height="30" rx="3" fill="#FFFFFF" stroke="#E0E0E0" strokeWidth="1" />
{/* Shield Background */}
<Path
d="M60 42 L52 46 L52 58 C52 62 56 65 60 65 C64 65 68 62 68 58 L68 46 Z"
fill="url(#shackleGradient)"
/>
{/* Lock Body */}
<Rect x="56" y="52" width="8" height="10" rx="2" fill="url(#lockBodyGradient)" />
{/* Lock Shackle */}
<Path
d="M58 50 C58 47.8 58.9 46 60 46 C61.1 46 62 47.8 62 50 L62 54"
stroke="#FFFFFF"
strokeWidth="2"
fill="none"
strokeLinecap="round"
/>
{/* Keyhole */}
<Circle cx="60" cy="56" r="1.5" fill="#FFFFFF" />
<Rect x="59.2" y="56" width="1.6" height="3" rx="0.8" fill="#FFFFFF" />
{/* Security dots/indicators */}
<Circle cx="48" cy="48" r="1" fill="#4CAF50" />
<Circle cx="72" cy="48" r="1" fill="#4CAF50" />
<Circle cx="48" cy="60" r="1" fill="#4CAF50" />
<Circle cx="72" cy="60" r="1" fill="#4CAF50" />
{/* Login text representation */}
<Rect x="45" y="67" width="12" height="1.5" rx="0.75" fill="#E0E0E0" />
<Rect x="63" y="67" width="12" height="1.5" rx="0.75" fill="#E0E0E0" />
</Svg>
);
// Mobile Login Icon - Phone with User
export const MobileLoginSVG = ({ width = 120, height = 120 }) => (
<Svg width={width} height={height} viewBox="0 0 120 120">
<Defs>
<LinearGradient id="phoneGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<Stop offset="0%" stopColor="#37474F" />
<Stop offset="100%" stopColor="#263238" />
</LinearGradient>
<LinearGradient id="screenGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<Stop offset="0%" stopColor="#E3F2FD" />
<Stop offset="100%" stopColor="#BBDEFB" />
</LinearGradient>
<LinearGradient id="userIconGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<Stop offset="0%" stopColor="#2196F3" />
<Stop offset="100%" stopColor="#1976D2" />
</LinearGradient>
</Defs>
{/* Purple base/stand */}
<Rect x="25" y="75" width="70" height="8" rx="4" fill="#8E24AA" />
<Rect x="30" y="70" width="60" height="12" rx="6" fill="#9C27B0" />
{/* Phone Body */}
<Rect x="45" y="25" width="30" height="50" rx="6" fill="url(#phoneGradient)" />
{/* Phone Screen */}
<Rect x="48" y="30" width="24" height="35" rx="2" fill="url(#screenGradient)" />
{/* User Profile on Screen */}
<Circle cx="60" cy="42" r="6" fill="url(#userIconGradient)" />
{/* User Head */}
<Circle cx="60" cy="40" r="2.5" fill="#FFFFFF" />
{/* User Body */}
<Path
d="M55.5 46 C55.5 44 57.3 42.5 60 42.5 C62.7 42.5 64.5 44 64.5 46"
stroke="#FFFFFF"
strokeWidth="1.5"
fill="none"
strokeLinecap="round"
/>
{/* Login Form Elements */}
<Rect x="50" y="50" width="20" height="2" rx="1" fill="#FFFFFF" />
<Rect x="50" y="54" width="20" height="2" rx="1" fill="#FFFFFF" />
{/* Login Button */}
<Rect x="52" y="58" width="16" height="4" rx="2" fill="#4CAF50" />
{/* Phone Speaker */}
<Rect x="57" y="27" width="6" height="1.5" rx="0.75" fill="#90A4AE" />
{/* Home Button */}
<Circle cx="60" cy="68" r="2" fill="#90A4AE" />
{/* Signal/WiFi indicators */}
<Rect x="52" y="32" width="1" height="1" fill="#4CAF50" />
<Rect x="54" y="32" width="1" height="1" fill="#4CAF50" />
<Rect x="56" y="32" width="1" height="1" fill="#4CAF50" />
</Svg>
);
// Fingerprint Login Icon
export const FingerprintLoginSVG = ({ width = 120, height = 120 }) => (
<Svg width={width} height={height} viewBox="0 0 120 120">
<Defs>
<LinearGradient id="fingerprintGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<Stop offset="0%" stopColor="#FF7043" />
<Stop offset="100%" stopColor="#D84315" />
</LinearGradient>
</Defs>
{/* Purple base/stand */}
<Rect x="25" y="75" width="70" height="8" rx="4" fill="#8E24AA" />
<Rect x="30" y="70" width="60" height="12" rx="6" fill="#9C27B0" />
{/* Main container */}
<Rect x="35" y="35" width="50" height="40" rx="6" fill="#FFF3E0" />
{/* Document/Screen base */}
<Rect x="40" y="40" width="40" height="30" rx="3" fill="#FFFFFF" stroke="#E0E0E0" strokeWidth="1" />
{/* Fingerprint Circles */}
<Circle cx="60" cy="55" r="12" fill="none" stroke="url(#fingerprintGradient)" strokeWidth="1.5" />
<Circle cx="60" cy="55" r="9" fill="none" stroke="url(#fingerprintGradient)" strokeWidth="1.2" />
<Circle cx="60" cy="55" r="6" fill="none" stroke="url(#fingerprintGradient)" strokeWidth="1" />
<Circle cx="60" cy="55" r="3" fill="none" stroke="url(#fingerprintGradient)" strokeWidth="0.8" />
{/* Fingerprint Lines */}
<Path d="M48 55 Q54 45 60 55" fill="none" stroke="url(#fingerprintGradient)" strokeWidth="1" />
<Path d="M72 55 Q66 45 60 55" fill="none" stroke="url(#fingerprintGradient)" strokeWidth="1" />
<Path d="M48 58 Q54 65 60 55" fill="none" stroke="url(#fingerprintGradient)" strokeWidth="1" />
<Path d="M72 58 Q66 65 60 55" fill="none" stroke="url(#fingerprintGradient)" strokeWidth="1" />
{/* Center dot */}
<Circle cx="60" cy="55" r="1" fill="url(#fingerprintGradient)" />
{/* Scan line effect */}
<Rect x="45" y="54" width="30" height="1" fill="#4CAF50" opacity="0.7" />
</Svg>
);

View File

@ -0,0 +1,89 @@
function hexWithOpacity(hex:string, opacity:number) {
hex = hex.replace('#', '');
// Convert hex to RGB values
const r = parseInt(hex.slice(0, 2), 16);
const g = parseInt(hex.slice(2, 4), 16);
const b = parseInt(hex.slice(4, 6), 16);
// Blend the color with white (255, 255, 255) based on opacity
const rAdjusted = Math.round((1 - opacity) * 255 + opacity * r);
const gAdjusted = Math.round((1 - opacity) * 255 + opacity * g);
const bAdjusted = Math.round((1 - opacity) * 255 + opacity * b);
// Convert adjusted RGB values back to hex and ensure two digits
const rHex = rAdjusted.toString(16).padStart(2, '0');
const gHex = gAdjusted.toString(16).padStart(2, '0');
const bHex = bAdjusted.toString(16).padStart(2, '0');
// Return the new hex color with opacity simulated
return `#${rHex}${gHex}${bHex}`;
}
const hexToHSL = (hex:string) => {
hex = hex.replace(/^#/, '');
let r = parseInt(hex.substring(0, 2), 16) / 255;
let g = parseInt(hex.substring(2, 4), 16) / 255;
let b = parseInt(hex.substring(4, 6), 16) / 255;
let max = Math.max(r, g, b);
let min = Math.min(r, g, b);
let h, s, l :number = (max + min) / 2;
if (max === min) {
h = s = 0;
} else {
let d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return { h: h * 360, s: s * 100, l: l * 100 };
};
// Convert HSL to hex
const HSLToHex = (h:number, s:number, l:number) => {
s /= 100;
l /= 100;
const k = (n:number) => (n + h / 30) % 12;
const a = s * Math.min(l, 1 - l);
const f = (n:number) =>
l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)));
return '#' +
[255 * f(0), 255 * f(8), 255 * f(4)]
.map(x => Math.round(x).toString(16).padStart(2, '0'))
.join('')
.toUpperCase();
};
// Convert RGB to HSL
function getAdjacentColors(color:string) {
const hsl = hexToHSL(color);
const angle = 30;
// get adjacent color to the primary color from color wheel
const color1 = HSLToHex(
(hsl.h - angle + 360) % 360,
hsl.s,
hsl.l
);
const color2 = HSLToHex(
(hsl.h + angle) % 360,
hsl.s,
hsl.l
);
// Adjacent color (negative direction)
return {
adjacentColor1: color1,
adjacentColor2: color2
};
}
export {hexWithOpacity,getAdjacentColors}