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

511 lines
14 KiB
TypeScript

/*
* File: SignUpScreen.tsx
* Description: Multi-step signup screen with validation and Redux integration
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React, { useEffect, useState } from 'react';
import {
View,
StyleSheet,
Alert,
Text,
ScrollView,
KeyboardAvoidingView,
Platform
} from 'react-native';
import { theme } from '../../../theme/theme';
import { useAppDispatch, useAppSelector } from '../../../store/hooks';
// Import signup step components
import EmailStep from '../components/signup/EmailStep';
import PasswordStep from '../components/signup/PasswordStep';
import NameStep from '../components/signup/NameStep';
import DocumentUploadStep from '../components/signup/DocumentUploadStep';
import HospitalSelectionStep from '../components/signup/HospitalSelectionStep';
import EmailAlreadyRegisteredModal from '../components/signup/EmailAlreadyRegisteredModal';
// Import API service
// Import hospital Redux functionality
import { fetchHospitals } from '../redux/hospitalSlice';
import { selectHospitalLoading, selectHospitals } from '../redux/hospitalSelectors';
// Import types
import { SignUpData, SignUpStep } from '../types/signup';
import { authAPI } from '../services/authAPI';
import { showError, showSuccess } from '../../../shared/utils/toast';
import { createFormDataWithFile, validateFileType, validateFileSize } from '../../../shared/utils/fileUpload';
// ============================================================================
// INTERFACES
// ============================================================================
interface SignUpScreenProps {
navigation: any;
}
// ============================================================================
// SIGNUP SCREEN COMPONENT
// ============================================================================
/**
* SignUpScreen Component
*
* Purpose: Multi-step signup flow with validation and Redux integration
*
* Features:
* - Step-by-step signup process (email → password → name → document → hospital)
* - Real-time validation with visual feedback
* - Email and username availability checks
* - Hospital selection with search
* - Document upload with preview
* - Progress tracking with visual progress bar
* - Modern UI with icons and proper typography
* - Keyboard-aware layout for better UX
* - Redux state management
* - Loading states and error handling
*/
const SignUpScreen: React.FC<SignUpScreenProps> = ({ navigation }) => {
// ============================================================================
// STATE MANAGEMENT
// ============================================================================
const [currentStep, setCurrentStep] = useState<SignUpStep>('email');
const [showEmailRegisteredModal, setShowEmailRegisteredModal] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [signUpData, setSignUpData] = useState<Partial<SignUpData>>({
email: '',
password: '',
first_name: '',
last_name: '',
username: '',
id_photo_url: null,
hospital_id: '',
});
const dispatch = useAppDispatch();
// ============================================================================
// REDUX STATE
// ============================================================================
const hospitals = useAppSelector(selectHospitals);
const hospitalLoading = useAppSelector(selectHospitalLoading);
// ============================================================================
// STEP CONFIGURATION
// ============================================================================
const steps: SignUpStep[] = ['email', 'password', 'name', 'document', 'hospital'];
const currentStepIndex = steps.indexOf(currentStep);
// ============================================================================
// EFFECTS
// ============================================================================
useEffect(() => {
// Fetch hospitals on component mount
dispatch(fetchHospitals());
}, [dispatch]);
// ============================================================================
// STEP HANDLERS
// ============================================================================
/**
* Handle Email Step Continue
*
* Purpose: Validate email and proceed to next step
*/
const handleEmailContinue = async (email: string) => {
setIsLoading(true);
try {
const response :any = await authAPI.validatemail({email});
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){
setSignUpData(prev => ({ ...prev, email }));
setCurrentStep('password');
}
} catch (error) {
Alert.alert('Error', 'Failed to validate email. Please try again.');
} finally {
setIsLoading(false);
}
};
/**
* Handle Password Step Continue
*
* Purpose: Validate password and proceed to next step
*/
const handlePasswordContinue = (password: string) => {
setSignUpData(prev => ({ ...prev, password }));
setCurrentStep('name');
};
/**
* Handle Name Step Continue
*
* Purpose: Validate name and username, then proceed to next step
*/
const handleNameContinue = async (firstName: string, lastName: string, username: string) => {
setIsLoading(true);
try {
const response:any = await authAPI.validateusername(username);
if(response.status==409&&response.data.message){
showError(response.data.message);
}
if(response.status==200&&response.data.message){
setSignUpData(prev => ({
...prev,
first_name: firstName,
last_name: lastName,
username: username
}));
setCurrentStep('document');
}
} catch (error) {
Alert.alert('Error', 'Failed to validate username. Please try again.');
} finally {
setIsLoading(false);
}
};
/**
* Handle Document Upload Step Continue
*
* Purpose: Save document and proceed to next step
*/
const handleDocumentContinue = (documentUri: string) => {
setSignUpData(prev => ({ ...prev, id_photo_url: documentUri }));
setCurrentStep('hospital');
};
/**
* Handle Hospital Selection Step Continue
*
* Purpose: Complete signup process
*/
const handleHospitalContinue = async (hospitalId: string) => {
const finalData: SignUpData = {
...signUpData,
hospital_id: hospitalId,
} as SignUpData;
setSignUpData(finalData);
// Call completion handler
await onSignUpComplete(finalData);
};
/**
* Complete Signup Process
*
* Purpose: Submit final signup data to API
*/
const onSignUpComplete = async (payload: SignUpData) => {
setIsLoading(true);
try {
let role = 'radiologist';
// Prepare form data with proper file handling
const formFields = {
email: payload.email,
password: payload.password,
first_name: payload.first_name,
last_name: payload.last_name,
username: payload.username,
dashboard_role: role,
hospital_id: payload.hospital_id,
};
let formData: FormData;
// Handle file upload with validation
if (payload.id_photo_url) {
const fileData = {
uri: payload.id_photo_url,
name: `id_photo_${Date.now()}.jpg`,
type: 'image/jpeg',
};
// Validate file type and size
if (!validateFileType(fileData)) {
showError('Invalid file type. Please select a JPEG, JPG, or PNG image.');
return;
}
if (!validateFileSize(fileData, 10)) {
showError('File size too large. Please select an image under 10MB.');
return;
}
// Create FormData with file
formData = createFormDataWithFile(formFields, fileData, 'id_photo_url');
} else {
// Create FormData without file
formData = createFormDataWithFile(formFields);
}
const response :any = await authAPI.signup(formData);
if(response.ok && response.data && response.data.success ) {
//@ts-ignore
showSuccess('Sign Up Successfully')
navigation.navigate('Login');
// dispatch(setHospitals(response.data.data))
} else {
showError('error while signup');
if( response.data && response.data.message ) {
//@ts-ignore
showError(response.data.message)
}
}
} catch (error: any) {
console.log('error', error);
Alert.alert('Error', 'Failed to create account. Please try again.');
} finally {
setIsLoading(false);
}
};
// ============================================================================
// NAVIGATION HANDLERS
// ============================================================================
/**
* Handle Back Navigation
*
* Purpose: Navigate to previous step or go back to previous screen
*/
const handleBack = () => {
if (currentStepIndex > 0) {
const previousStep = steps[currentStepIndex - 1];
setCurrentStep(previousStep);
} else {
navigation.goBack();
}
};
/**
* Handle Modal Close
*
* Purpose: Close email already registered modal
*/
const handleCloseModal = () => {
setShowEmailRegisteredModal(false);
};
/**
* Handle Go To Login
*
* Purpose: Navigate to login screen
*/
const handleGoToLogin = () => {
setShowEmailRegisteredModal(false);
navigation.navigate('Login');
};
// ============================================================================
// RENDER FUNCTIONS
// ============================================================================
/**
* Render Current Step
*
* Purpose: Render the appropriate step component based on current step
*/
const renderCurrentStep = () => {
const commonProps = {
onBack: handleBack,
data: signUpData,
isLoading,
};
switch (currentStep) {
case 'email':
return (
<EmailStep
{...commonProps}
onContinue={handleEmailContinue}
/>
);
case 'password':
return (
<PasswordStep
{...commonProps}
onContinue={handlePasswordContinue}
/>
);
case 'name':
return (
<NameStep
{...commonProps}
onContinue={handleNameContinue}
/>
);
case 'document':
return (
<DocumentUploadStep
{...commonProps}
onContinue={handleDocumentContinue}
/>
);
case 'hospital':
return (
<HospitalSelectionStep
{...commonProps}
onContinue={handleHospitalContinue}
hospitals={hospitals}
hospitalLoading={hospitalLoading}
/>
);
default:
return (
<EmailStep
{...commonProps}
onContinue={handleEmailContinue}
/>
);
}
};
// ============================================================================
// RENDER
// ============================================================================
return (
<KeyboardAvoidingView
style={styles.container}
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
>
{/* Conditional Content Rendering */}
{currentStep === 'hospital' ? (
// For hospital step, render without ScrollView to avoid conflicts
<View style={styles.content}>
{renderCurrentStep()}
</View>
) : (
// For other steps, use ScrollView for proper scrolling
<ScrollView
style={styles.content}
showsVerticalScrollIndicator={false}
contentContainerStyle={styles.contentContainer}
>
{renderCurrentStep()}
</ScrollView>
)}
{/* Progress Bar - Bottom */}
<View style={styles.progressContainer}>
<View style={styles.progressBar}>
<View
style={[
styles.progressFill,
{ width: `${((currentStepIndex + 1) / steps.length) * 100}%` }
]}
/>
</View>
<Text style={styles.progressText}>
{Math.round(((currentStepIndex + 1) / steps.length) * 100)}% Complete
</Text>
</View>
<EmailAlreadyRegisteredModal
visible={showEmailRegisteredModal}
onClose={handleCloseModal}
onGoToLogin={handleGoToLogin}
/>
</KeyboardAvoidingView>
);
};
// ============================================================================
// STYLES
// ============================================================================
const styles = StyleSheet.create({
// Main container
container: {
flex: 1,
backgroundColor: theme.colors.background,
},
// Progress container
progressContainer: {
paddingHorizontal: theme.spacing.lg,
paddingVertical: theme.spacing.md,
backgroundColor: theme.colors.background,
borderTopWidth: 1,
borderTopColor: theme.colors.border,
},
// Progress bar
progressBar: {
height: 4,
backgroundColor: theme.colors.border,
borderRadius: theme.borderRadius.round,
marginBottom: theme.spacing.sm,
overflow: 'hidden',
},
// Progress fill
progressFill: {
height: '100%',
backgroundColor: theme.colors.primary,
borderRadius: theme.borderRadius.round,
},
// Progress text
progressText: {
fontSize: theme.typography.fontSize.bodySmall,
fontFamily: theme.typography.fontFamily.medium,
color: theme.colors.textSecondary,
textAlign: 'center',
},
// Content area
content: {
flex: 1,
},
// Content container
contentContainer: {
flexGrow: 1,
padding: theme.spacing.lg,
paddingBottom: theme.spacing.xl,
},
});
export default SignUpScreen;
/*
* End of File: SignUpScreen.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/