NeoScan_Radiologist/app/modules/Auth/screens/ResetPasswordScreen.tsx

1251 lines
33 KiB
TypeScript

/*
* File: ResetPasswordScreen.tsx
* Description: Password reset screen for onboarding flow with document upload support
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React, { useState, useEffect } from 'react';
import {
View,
Text,
StyleSheet,
TextInput,
TouchableOpacity,
ScrollView,
KeyboardAvoidingView,
Platform,
Alert,
Image,
PermissionsAndroid,
} from 'react-native';
import {
launchImageLibrary,
launchCamera,
ImagePickerResponse,
MediaType,
} from 'react-native-image-picker';
import { useAppDispatch, useAppSelector } from '../../../store/hooks';
import { updateOnboarded, logout } from '../redux/authSlice';
import { authAPI } from '../services/authAPI';
import { showError, showSuccess } from '../../../shared/utils/toast';
import { validateFileType, validateFileSize, prepareFileForUpload } from '../../../shared/utils/fileUpload';
import { theme } from '../../../theme/theme';
import Icon from 'react-native-vector-icons/Feather';
import { AuthNavigationProp } from '../navigation/navigationTypes';
/**
* ResetPasswordScreenProps Interface
*
* Purpose: Defines the props required by the ResetPasswordScreen component
*
* Props:
* - navigation: React Navigation object for screen navigation (optional when used in root stack)
*/
interface ResetPasswordScreenProps {
navigation?: AuthNavigationProp;
}
/**
* PasswordRule Interface
*
* Purpose: Defines the structure for password validation rules
*/
interface PasswordRule {
id: string;
label: string;
validator: (password: string) => boolean;
isValid: boolean;
}
/**
* ImageData Interface
*
* Purpose: Defines the structure for image data
*/
interface ImageData {
uri: string;
name: string;
type: string;
size?: number;
}
/**
* OnboardingStep Type
*
* Purpose: Defines the different onboarding steps
*/
type OnboardingStep = 'document' | 'password';
/**
* ResetPasswordScreen Component
*
* Purpose: Password reset screen for users who need to set their initial password
*
* Features:
* 1. Two flows based on platform:
* - platform = 'web': Document upload first, then password reset
* - platform = 'app': Password reset only
* 2. Document upload with camera/gallery selection
* 3. Password and confirm password input fields
* 4. Real-time password validation with visual feedback
* 5. Password visibility toggles
* 6. Password strength requirements display
* 7. Integration with Redux for onboarding status
* 8. API integration for document upload and password change
*
* Password Requirements:
* - At least 8 characters
* - One uppercase letter
* - One lowercase letter
* - One number
* - One special character
* - Passwords must match
*/
export const ResetPasswordScreen: React.FC<ResetPasswordScreenProps> = ({
navigation,
}) => {
// ============================================================================
// STATE MANAGEMENT
// ============================================================================
// Form input states
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [isPasswordVisible, setPasswordVisible] = useState(false);
const [isConfirmPasswordVisible, setConfirmPasswordVisible] = useState(false);
const [loading, setLoading] = useState(false);
// Document upload states
const [selectedImage, setSelectedImage] = useState<ImageData | null>(null);
const [documentUploaded, setDocumentUploaded] = useState(false);
// Current step state
const [currentStep, setCurrentStep] = useState<OnboardingStep>('document');
// Redux state
const dispatch = useAppDispatch();
const user = useAppSelector((state) => state.auth.user);
// Password validation rules
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) => pwd.length > 0 && confirmPassword.length > 0 && pwd === confirmPassword,
isValid: false,
},
]);
// ============================================================================
// EFFECTS
// ============================================================================
/**
* useEffect for password validation
*
* Purpose: Update password rules when password or confirm password changes
*/
useEffect(() => {
updatePasswordRules(password);
}, [password, confirmPassword]);
/**
* useEffect for determining initial step
*
* Purpose: Set the initial step based on user signup platform
*/
useEffect(() => {
if (user?.platform === 'app') {
setCurrentStep('password');
} else {
setCurrentStep('document');
}
}, [user?.platform]);
// ============================================================================
// HELPER FUNCTIONS
// ============================================================================
/**
* validatePassword Function
*
* Purpose: Check if all password requirements are met
*
* @param pwd - Password to validate
* @returns boolean indicating if password meets all requirements
*/
const validatePassword = (pwd: string): boolean => {
return passwordRules.every(rule => rule.isValid);
};
/**
* updatePasswordRules Function
*
* Purpose: Update password validation rules based on current password
*
* @param pwd - Current password value
*/
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),
};
})
);
};
/**
* Request Camera Permission
*
* Purpose: Request camera permission for Android devices
*
* @returns Promise<boolean> - Whether permission was granted
*/
const requestCameraPermission = async (): Promise<boolean> => {
if (Platform.OS === 'android') {
try {
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.CAMERA,
{
title: 'Camera Permission',
message: 'This app needs camera permission to capture images.',
buttonNeutral: 'Ask Me Later',
buttonNegative: 'Cancel',
buttonPositive: 'OK',
}
);
return granted === PermissionsAndroid.RESULTS.GRANTED;
} catch (err) {
console.warn('Camera permission error:', err);
return false;
}
}
return true;
};
/**
* Format File Size
*
* Purpose: Convert bytes to human readable format
*
* @param bytes - File size in bytes
* @returns Formatted file size string
*/
const formatFileSize = (bytes?: number): string => {
if (!bytes) return '';
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
if (bytes === 0) return '0 Bytes';
const i = Math.floor(Math.log(bytes) / Math.log(1024));
return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
};
/**
* Get File Type Display
*
* Purpose: Get display name for file type
*
* @param type - MIME type
* @returns Display name for file type
*/
const getFileTypeDisplay = (type: string): string => {
if (type.includes('jpeg') || type.includes('jpg')) return 'JPEG';
if (type.includes('png')) return 'PNG';
if (type.includes('gif')) return 'GIF';
if (type.includes('webp')) return 'WebP';
return 'Image';
};
// ============================================================================
// DOCUMENT UPLOAD HANDLERS
// ============================================================================
/**
* Handle Image Picker Selection
*
* Purpose: Show options for camera or gallery selection
*/
const handleImagePicker = () => {
Alert.alert(
'Select Image',
'Choose how you want to upload your document',
[
{
text: 'Camera',
onPress: () => handleCameraCapture(),
},
{
text: 'Gallery',
onPress: () => handleGalleryPicker(),
},
{
text: 'Cancel',
style: 'cancel',
},
]
);
};
/**
* Handle Camera Capture
*
* Purpose: Launch camera to capture image
*/
const handleCameraCapture = async () => {
const hasPermission = await requestCameraPermission();
if (!hasPermission) {
showError('Permission Error', 'Camera permission is required to capture images.');
return;
}
const options = {
mediaType: 'photo' as MediaType,
maxWidth: 2000,
maxHeight: 2000,
includeBase64: false,
};
launchCamera(options, (response: ImagePickerResponse) => {
if (response.didCancel) {
return;
}
if (response.errorMessage) {
showError('Camera Error', response.errorMessage);
return;
}
if (response.assets && response.assets[0]) {
const asset = response.assets[0];
const imageData: ImageData = {
uri: asset.uri!,
name: asset.fileName || `document_${Date.now()}.jpg`,
type: asset.type || 'image/jpeg',
size: asset.fileSize,
};
// Validate file type and size
if (!validateFileType(imageData)) {
showError('Invalid File Type', 'Please select a JPEG, JPG, or PNG image.');
return;
}
if (!validateFileSize(imageData, 10)) {
showError('File Too Large', 'Please select an image under 10MB.');
return;
}
setSelectedImage(imageData);
showSuccess('Success', 'Document captured successfully!');
}
});
};
/**
* Handle Gallery Picker
*
* Purpose: Launch image library to select image
*/
const handleGalleryPicker = () => {
const options = {
mediaType: 'photo' as MediaType,
maxWidth: 2000,
maxHeight: 2000,
includeBase64: false,
};
launchImageLibrary(options, (response: ImagePickerResponse) => {
if (response.didCancel) {
return;
}
if (response.errorMessage) {
showError('Gallery Error', response.errorMessage);
return;
}
if (response.assets && response.assets[0]) {
const asset = response.assets[0];
const imageData: ImageData = {
uri: asset.uri!,
name: asset.fileName || `document_${Date.now()}.jpg`,
type: asset.type || 'image/jpeg',
size: asset.fileSize,
};
// Validate file type and size
if (!validateFileType(imageData)) {
showError('Invalid File Type', 'Please select a JPEG, JPG, or PNG image.');
return;
}
if (!validateFileSize(imageData, 10)) {
showError('File Too Large', 'Please select an image under 10MB.');
return;
}
setSelectedImage(imageData);
showSuccess('Success', 'Document selected from gallery!');
}
});
};
/**
* Handle Remove Image
*
* Purpose: Remove selected image
*/
const handleRemoveImage = () => {
setSelectedImage(null);
setDocumentUploaded(false);
};
/**
* Handle Document Upload
*
* Purpose: Upload document to server
*/
const handleDocumentUpload = async () => {
if (!selectedImage) {
showError('Validation Error', 'Please upload a document to continue.');
return;
}
setLoading(true);
try {
const formData = new FormData();
// Prepare file with proper structure using utility function
const preparedFile = prepareFileForUpload(selectedImage, 'id_photo');
formData.append('id_photo', preparedFile as any);
const response: any = await authAPI.uploadDocument(formData, user?.access_token);
if (response.data && response.data.message) {
if (response.data.success) {
showSuccess(response.data.message);
setDocumentUploaded(true);
// Move to password step after successful upload
setCurrentStep('password');
} else {
showError(response.data.message);
}
} else {
showError('Error while uploading document');
}
} catch (error: any) {
console.error('Document upload error:', error);
showError('Failed to upload document. Please try again.');
} finally {
setLoading(false);
}
};
// ============================================================================
// EVENT HANDLERS
// ============================================================================
/**
* handlePasswordChange Function
*
* Purpose: Handle password input changes
*
* @param pwd - New password value
*/
const handlePasswordChange = (pwd: string) => {
setPassword(pwd);
};
/**
* handleConfirmPasswordChange Function
*
* Purpose: Handle confirm password input changes
*
* @param pwd - New confirm password value
*/
const handleConfirmPasswordChange = (pwd: string) => {
setConfirmPassword(pwd);
};
/**
* handleReset Function
*
* Purpose: Handle password reset submission
*
* Flow:
* 1. Validate required fields
* 2. Validate password requirements
* 3. Check password match
* 4. Call API to change password
* 5. Update onboarding status on success
*/
const handleReset = async () => {
// Validate required fields
if (!password.trim() || !confirmPassword.trim()) {
Alert.alert('Validation Error', 'Both password fields are required.');
return;
}
// Validate password requirements
if (!validatePassword(password)) {
Alert.alert('Validation Error', 'Please meet all password requirements.');
return;
}
// Check password match
if (password !== confirmPassword) {
Alert.alert('Validation Error', 'Passwords do not match.');
return;
}
setLoading(true);
try {
// Call API to change password
const response: any = await authAPI.changepassword({
password,
token: user?.access_token,
});
if (response.data && response.data.message) {
if (response.data.success) {
showSuccess(response.data.message);
// Update onboarding status
dispatch(updateOnboarded(true));
// Navigate to main app
// The app will automatically navigate to MainTabNavigator due to Redux state change
} else {
showError(response.data.message);
}
} else {
showError('Error while changing password');
}
} catch (error: any) {
console.error('Password reset error:', error);
showError('Failed to reset password. Please try again.');
} finally {
setLoading(false);
}
};
/**
* handleBack Function
*
* Purpose: Handle back navigation - logs out user since they can't go back to login
*/
const handleBack = () => {
Alert.alert(
'Sign Out',
'Are you sure you want to sign out? You will need to log in again.',
[
{
text: 'Cancel',
style: 'cancel',
},
{
text: 'Sign Out',
style: 'destructive',
onPress: () => {
// Dispatch logout action
dispatch(logout());
},
},
]
);
};
/**
* renderPasswordRule Function
*
* Purpose: Render individual password validation rule
*
* @param rule - Password rule to render
* @returns JSX element for the rule
*/
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={theme.colors.background} />
)}
</View>
<Text style={[styles.ruleText, rule.isValid && styles.ruleTextValid]}>
{rule.label}
</Text>
</View>
);
/**
* renderDocumentUploadStep Function
*
* Purpose: Render document upload step
*
* @returns JSX element for document upload step
*/
const renderDocumentUploadStep = () => (
<>
{/* Icon */}
<View style={styles.iconContainer}>
<Icon name="file-text" size={64} color={theme.colors.primary} />
</View>
{/* Title and Subtitle */}
<Text style={styles.title}>Upload Your ID Document</Text>
<Text style={styles.subtitle}>
Please upload a clear photo of your hospital-issued ID for verification
</Text>
{/* Document Upload Area */}
<TouchableOpacity
style={styles.uploadContainer}
onPress={handleImagePicker}
disabled={loading}
>
<View style={styles.uploadContent}>
{selectedImage ? (
<View style={styles.imagePreviewContainer}>
<Image source={{ uri: selectedImage.uri }} style={styles.imagePreview} />
<TouchableOpacity
style={styles.imageOverlay}
onPress={handleRemoveImage}
>
<Icon name="x" size={20} color={theme.colors.background} />
</TouchableOpacity>
<Text style={styles.uploadedText}>Document Selected</Text>
<Text style={styles.fileName}>{selectedImage.name}</Text>
<View style={styles.fileDetails}>
<Text style={styles.fileType}>{getFileTypeDisplay(selectedImage.type)}</Text>
{selectedImage.size && (
<Text style={styles.fileSize}> {formatFileSize(selectedImage.size)}</Text>
)}
</View>
</View>
) : (
<>
<Icon name="image" size={48} color={theme.colors.textSecondary} />
<Text style={styles.uploadText}>Tap to upload document</Text>
<Text style={styles.uploadSubtext}>JPG, PNG supported</Text>
</>
)}
</View>
</TouchableOpacity>
{selectedImage && (
<TouchableOpacity
style={styles.changeButton}
onPress={handleImagePicker}
disabled={loading}
>
<Text style={styles.changeButtonText}>Change Document</Text>
</TouchableOpacity>
)}
{/* Upload Button */}
<TouchableOpacity
style={[
styles.button,
styles.uploadButton,
(!selectedImage || loading) && styles.buttonDisabled,
]}
onPress={handleDocumentUpload}
disabled={!selectedImage || loading}
>
{loading ? (
<View style={styles.loadingContainer}>
<Text style={styles.buttonText}>Uploading Document...</Text>
</View>
) : (
<Text style={styles.buttonText}>Upload Document</Text>
)}
</TouchableOpacity>
</>
);
/**
* renderPasswordResetStep Function
*
* Purpose: Render password reset step
*
* @returns JSX element for password reset step
*/
const renderPasswordResetStep = () => (
<>
{/* Icon */}
<View style={styles.iconContainer}>
<Icon name="lock" size={64} color={theme.colors.primary} />
</View>
{/* Title and Subtitle */}
<Text style={styles.title}>Set Your Password</Text>
<Text style={styles.subtitle}>
Create a strong password to complete your account setup
</Text>
{/* Password Input */}
<View style={styles.inputContainer}>
<Icon name="lock" size={20} color={theme.colors.textSecondary} style={styles.inputIcon} />
<TextInput
placeholder="Password"
value={password}
onChangeText={handlePasswordChange}
style={styles.inputField}
secureTextEntry={!isPasswordVisible}
placeholderTextColor={theme.colors.textMuted}
editable={!loading}
/>
<TouchableOpacity
onPress={() => setPasswordVisible(!isPasswordVisible)}
style={styles.eyeIcon}
disabled={loading}
>
<Icon
name={isPasswordVisible ? 'eye-off' : 'eye'}
size={22}
color={theme.colors.textSecondary}
/>
</TouchableOpacity>
</View>
{/* Confirm Password Input */}
<View style={styles.inputContainer}>
<Icon name="lock" size={20} color={theme.colors.textSecondary} style={styles.inputIcon} />
<TextInput
placeholder="Confirm Password"
value={confirmPassword}
onChangeText={handleConfirmPasswordChange}
style={styles.inputField}
secureTextEntry={!isConfirmPasswordVisible}
placeholderTextColor={theme.colors.textMuted}
editable={!loading}
/>
<TouchableOpacity
onPress={() => setConfirmPasswordVisible(!isConfirmPasswordVisible)}
style={styles.eyeIcon}
disabled={loading}
>
<Icon
name={isConfirmPasswordVisible ? 'eye-off' : 'eye'}
size={22}
color={theme.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>
{/* Reset Button */}
<TouchableOpacity
style={[
styles.button,
styles.resetButton,
loading && styles.buttonDisabled,
]}
onPress={handleReset}
disabled={loading}
>
{loading ? (
<View style={styles.loadingContainer}>
<Text style={styles.buttonText}>Setting Password...</Text>
</View>
) : (
<Text style={styles.buttonText}>Set Password</Text>
)}
</TouchableOpacity>
</>
);
// ============================================================================
// RENDER SECTION
// ============================================================================
return (
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={styles.container}
>
<ScrollView
style={styles.scrollView}
contentContainerStyle={styles.scrollContent}
showsVerticalScrollIndicator={false}
>
{/* Header */}
<View style={styles.header}>
<TouchableOpacity onPress={handleBack} style={styles.backButton}>
<Icon name="log-out" size={24} color={theme.colors.textPrimary} />
</TouchableOpacity>
<Text style={styles.headerTitle}>
{currentStep === 'document' ? 'Upload Document' : 'Set Password'}
</Text>
<View style={styles.headerSpacer} />
</View>
{/* Step Indicator */}
{user?.platform === 'web' && (
<View style={styles.stepIndicator}>
<View style={styles.stepContainer}>
<View style={[styles.stepCircle, currentStep === 'document' && styles.stepCircleActive]}>
<Text style={[styles.stepNumber, currentStep === 'document' && styles.stepNumberActive]}>1</Text>
</View>
<Text style={[styles.stepLabel, currentStep === 'document' && styles.stepLabelActive]}>Document</Text>
</View>
<View style={styles.stepLine} />
<View style={styles.stepContainer}>
<View style={[styles.stepCircle, currentStep === 'password' && styles.stepCircleActive]}>
<Text style={[styles.stepNumber, currentStep === 'password' && styles.stepNumberActive]}>2</Text>
</View>
<Text style={[styles.stepLabel, currentStep === 'password' && styles.stepLabelActive]}>Password</Text>
</View>
</View>
)}
{/* Content based on current step */}
{currentStep === 'document' ? renderDocumentUploadStep() : renderPasswordResetStep()}
</ScrollView>
</KeyboardAvoidingView>
);
};
// ============================================================================
// STYLES SECTION
// ============================================================================
const styles = StyleSheet.create({
// Main container
container: {
flex: 1,
backgroundColor: theme.colors.background,
},
// Scroll view
scrollView: {
flex: 1,
},
// Scroll content
scrollContent: {
flexGrow: 1,
padding: theme.spacing.lg,
},
// Header section
header: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
marginBottom: theme.spacing.xl,
},
// Back button
backButton: {
padding: theme.spacing.sm,
borderRadius: theme.borderRadius.medium,
backgroundColor: theme.colors.backgroundAlt,
},
// Header title
headerTitle: {
fontSize: theme.typography.fontSize.displaySmall,
fontFamily: theme.typography.fontFamily.bold,
color: theme.colors.textPrimary,
},
// Header spacer
headerSpacer: {
width: 40,
},
// Step indicator
stepIndicator: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
marginBottom: theme.spacing.xl,
paddingHorizontal: theme.spacing.lg,
},
// Step container
stepContainer: {
alignItems: 'center',
},
// Step circle
stepCircle: {
width: 32,
height: 32,
borderRadius: 16,
backgroundColor: theme.colors.backgroundAlt,
borderWidth: 2,
borderColor: theme.colors.border,
alignItems: 'center',
justifyContent: 'center',
marginBottom: theme.spacing.xs,
},
// Step circle active
stepCircleActive: {
backgroundColor: theme.colors.primary,
borderColor: theme.colors.primary,
},
// Step number
stepNumber: {
fontSize: theme.typography.fontSize.bodyMedium,
fontFamily: theme.typography.fontFamily.medium,
color: theme.colors.textSecondary,
},
// Step number active
stepNumberActive: {
color: theme.colors.background,
},
// Step label
stepLabel: {
fontSize: theme.typography.fontSize.bodySmall,
fontFamily: theme.typography.fontFamily.regular,
color: theme.colors.textSecondary,
},
// Step label active
stepLabelActive: {
color: theme.colors.primary,
fontFamily: theme.typography.fontFamily.medium,
},
// Step line
stepLine: {
flex: 1,
height: 2,
backgroundColor: theme.colors.border,
marginHorizontal: theme.spacing.md,
},
// Icon container
iconContainer: {
alignItems: 'center',
marginBottom: theme.spacing.xl,
},
// Title
title: {
fontSize: theme.typography.fontSize.displayMedium,
fontFamily: theme.typography.fontFamily.bold,
color: theme.colors.textPrimary,
textAlign: 'center',
marginBottom: theme.spacing.sm,
},
// Subtitle
subtitle: {
fontSize: theme.typography.fontSize.bodyLarge,
fontFamily: theme.typography.fontFamily.regular,
color: theme.colors.textSecondary,
textAlign: 'center',
marginBottom: theme.spacing.xl,
},
// Upload container
uploadContainer: {
backgroundColor: theme.colors.backgroundAlt,
borderWidth: 2,
borderColor: theme.colors.border,
borderStyle: 'dashed',
borderRadius: theme.borderRadius.medium,
padding: theme.spacing.xl,
marginBottom: theme.spacing.md,
minHeight: 200,
},
// Upload content
uploadContent: {
alignItems: 'center',
},
// Image preview container
imagePreviewContainer: {
alignItems: 'center',
width: '100%',
},
// Image preview
imagePreview: {
width: '100%',
height: 150,
borderRadius: theme.borderRadius.medium,
marginBottom: theme.spacing.sm,
resizeMode: 'contain',
},
// Image overlay (remove button)
imageOverlay: {
position: 'absolute',
top: 0,
right: 20,
backgroundColor: 'rgba(0, 0, 0, 0.4)',
borderRadius: 17,
width: 34,
height: 34,
justifyContent: 'center',
alignItems: 'center',
},
// Upload text
uploadText: {
fontSize: theme.typography.fontSize.bodyLarge,
fontFamily: theme.typography.fontFamily.medium,
color: theme.colors.textPrimary,
marginTop: theme.spacing.sm,
},
// Upload subtext
uploadSubtext: {
fontSize: theme.typography.fontSize.bodySmall,
fontFamily: theme.typography.fontFamily.regular,
color: theme.colors.textSecondary,
marginTop: theme.spacing.xs,
},
// Uploaded text
uploadedText: {
fontSize: theme.typography.fontSize.bodyLarge,
fontFamily: theme.typography.fontFamily.medium,
color: theme.colors.success,
marginTop: theme.spacing.sm,
},
// File name
fileName: {
fontSize: theme.typography.fontSize.bodySmall,
fontFamily: theme.typography.fontFamily.regular,
color: theme.colors.textSecondary,
marginTop: theme.spacing.xs,
textAlign: 'center',
maxWidth: '80%',
},
// File details
fileDetails: {
flexDirection: 'row',
alignItems: 'center',
marginTop: theme.spacing.xs,
},
// File type
fileType: {
fontSize: theme.typography.fontSize.bodySmall,
fontFamily: theme.typography.fontFamily.regular,
color: theme.colors.textMuted,
},
// File size
fileSize: {
fontSize: theme.typography.fontSize.bodySmall,
fontFamily: theme.typography.fontFamily.regular,
color: theme.colors.textMuted,
},
// Change button
changeButton: {
alignSelf: 'center',
marginBottom: theme.spacing.xl,
},
// Change button text
changeButtonText: {
fontSize: theme.typography.fontSize.bodyMedium,
fontFamily: theme.typography.fontFamily.medium,
color: theme.colors.primary,
},
// Input container
inputContainer: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: theme.colors.backgroundAlt,
borderWidth: 1,
borderColor: theme.colors.border,
borderRadius: theme.borderRadius.medium,
marginBottom: theme.spacing.md,
paddingHorizontal: theme.spacing.md,
paddingVertical: 2,
},
// Input icon
inputIcon: {
marginRight: theme.spacing.sm,
},
// Input field
inputField: {
flex: 1,
paddingVertical: theme.spacing.md,
fontSize: theme.typography.fontSize.bodyLarge,
fontFamily: theme.typography.fontFamily.regular,
color: theme.colors.textPrimary,
},
// Eye icon
eyeIcon: {
padding: theme.spacing.sm,
},
// Rules container
rulesContainer: {
marginTop: theme.spacing.sm,
marginBottom: theme.spacing.xl,
paddingHorizontal: theme.spacing.sm,
},
// Rules title
rulesTitle: {
fontSize: theme.typography.fontSize.bodyMedium,
fontFamily: theme.typography.fontFamily.medium,
color: theme.colors.textPrimary,
marginBottom: theme.spacing.sm,
},
// Rules grid
rulesGrid: {
gap: theme.spacing.xs,
},
// Rule container
ruleContainer: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: theme.spacing.xs,
},
// Checkbox
checkbox: {
width: 18,
height: 18,
borderRadius: 4,
borderWidth: 2,
borderColor: theme.colors.border,
backgroundColor: theme.colors.backgroundAlt,
marginRight: theme.spacing.sm,
alignItems: 'center',
justifyContent: 'center',
},
// Checkbox checked
checkboxChecked: {
backgroundColor: theme.colors.primary,
borderColor: theme.colors.primary,
},
// Rule text
ruleText: {
fontSize: theme.typography.fontSize.bodySmall,
fontFamily: theme.typography.fontFamily.regular,
color: theme.colors.textSecondary,
flex: 1,
},
// Rule text valid
ruleTextValid: {
color: theme.colors.success,
},
// Base button
button: {
paddingVertical: theme.spacing.md,
paddingHorizontal: theme.spacing.lg,
borderRadius: theme.borderRadius.medium,
alignItems: 'center',
marginBottom: theme.spacing.md,
},
// Upload button
uploadButton: {
backgroundColor: theme.colors.primary,
...theme.shadows.primary,
},
// Reset button
resetButton: {
backgroundColor: theme.colors.primary,
...theme.shadows.primary,
},
// Disabled button
buttonDisabled: {
opacity: 0.6,
},
// Button text
buttonText: {
color: theme.colors.background,
fontSize: theme.typography.fontSize.bodyLarge,
fontFamily: theme.typography.fontFamily.medium,
},
// Loading container
loadingContainer: {
flexDirection: 'row',
alignItems: 'center',
},
});
export default ResetPasswordScreen;
/*
* End of File: ResetPasswordScreen.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/