/* * File: DocumentUploadStep.tsx * Description: Document upload step component for signup flow with image picker * Design & Developed by Tech4Biz Solutions * Copyright (c) Spurrin Innovations. All rights reserved. */ import React, { useState } from 'react'; import { View, Text, TouchableOpacity, StyleSheet, ScrollView, Image, Alert, PermissionsAndroid, Platform, KeyboardAvoidingView, } from 'react-native'; import { launchImageLibrary, launchCamera, ImagePickerResponse, MediaType, } from 'react-native-image-picker'; import { theme } from '../../../../theme/theme'; import { DocumentUploadStepProps } from '../../types/signup'; import Icon from 'react-native-vector-icons/Feather'; import { showError, showSuccess } from '../../../../shared/utils/toast'; import { validateFileType, validateFileSize, formatFileSize } from '../../../../shared/utils/fileUpload'; // ============================================================================ // INTERFACES // ============================================================================ /** * ImageData Interface * * Purpose: Defines the structure for image data */ interface ImageData { uri: string; name: string; type: string; size?: number; } // ============================================================================ // DOCUMENT UPLOAD STEP COMPONENT // ============================================================================ /** * DocumentUploadStep Component * * Purpose: Fourth step of signup flow - document upload with image picker * * Features: * - Camera and gallery image selection * - Image preview with file details * - Real-time file size and type display * - Permission handling for camera * - Modern UI with proper header alignment * - Continue button with loading state * - Back navigation */ const DocumentUploadStep: React.FC = ({ onContinue, onBack, data, isLoading, }) => { // ============================================================================ // STATE MANAGEMENT // ============================================================================ const [selectedImage, setSelectedImage] = useState( data.id_photo_url ? { uri: data.id_photo_url, name: 'uploaded_document.jpg', type: 'image/jpeg', } : null ); // ============================================================================ // PERMISSION HANDLERS // ============================================================================ /** * Request Camera Permission * * Purpose: Request camera permission for Android devices * * @returns Promise - Whether permission was granted */ const requestCameraPermission = async (): Promise => { 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; }; // ============================================================================ // IMAGE PICKER 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!'); } }); }; // ============================================================================ // UTILITY FUNCTIONS // ============================================================================ /** * 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'; }; // ============================================================================ // EVENT HANDLERS // ============================================================================ /** * Handle Continue * * Purpose: Proceed to next step with selected image */ const handleContinue = () => { if (!selectedImage) { showError('Validation Error', 'Please upload a document to continue.'); return; } onContinue(selectedImage.uri); }; /** * Handle Remove Image * * Purpose: Remove selected image */ const handleRemoveImage = () => { setSelectedImage(null); }; // ============================================================================ // RENDER // ============================================================================ return ( {/* Header */} Upload Document Step 4 of 5 {/* Content */} Upload your ID document Please upload a clear photo of your hospital-issued ID for verification. {/* Document Upload Area */} {selectedImage ? ( Document Uploaded {selectedImage.name} {getFileTypeDisplay(selectedImage.type)} {selectedImage.size && ( • {formatFileSize(selectedImage.size)} )} ) : ( <> Tap to upload document JPG, PNG supported )} {selectedImage && ( Change Document )} {/* Continue Button */} {isLoading ? 'Processing...' : 'Continue'} ); }; // ============================================================================ // STYLES // ============================================================================ const styles = StyleSheet.create({ // Main container container: { flex: 1, backgroundColor: theme.colors.background, }, // Scroll view scrollView: { flex: 1, }, // Scroll content scrollContent: { flexGrow: 1, paddingHorizontal: theme.spacing.sm, }, // Header section header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingTop: theme.spacing.xl, paddingBottom: theme.spacing.lg, marginBottom: theme.spacing.lg, }, // Back button backButton: { padding: theme.spacing.sm, borderRadius: theme.borderRadius.medium, backgroundColor: theme.colors.backgroundAlt, }, // Header content headerContent: { flex: 1, alignItems: 'center', }, // Header spacer headerSpacer: { width: 40, }, // Title title: { fontSize: theme.typography.fontSize.displaySmall, fontFamily: theme.typography.fontFamily.bold, color: theme.colors.textPrimary, marginBottom: theme.spacing.xs, }, // Subtitle subtitle: { fontSize: theme.typography.fontSize.bodyMedium, fontFamily: theme.typography.fontFamily.regular, color: theme.colors.textSecondary, }, // Content section content: { flex: 1, justifyContent: 'center', paddingBottom: theme.spacing.xl, }, // Section title sectionTitle: { fontSize: theme.typography.fontSize.displaySmall, fontFamily: theme.typography.fontFamily.bold, color: theme.colors.textPrimary, marginBottom: theme.spacing.sm, }, // Description description: { fontSize: theme.typography.fontSize.bodyMedium, fontFamily: theme.typography.fontFamily.regular, color: theme.colors.textSecondary, 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, }, // Continue button continueButton: { backgroundColor: theme.colors.primary, borderRadius: theme.borderRadius.medium, paddingVertical: theme.spacing.md, paddingHorizontal: theme.spacing.lg, alignItems: 'center', marginBottom: theme.spacing.lg, ...theme.shadows.primary, }, // Continue button disabled continueButtonDisabled: { backgroundColor: theme.colors.border, opacity: 0.6, }, // Continue button text continueButtonText: { fontSize: theme.typography.fontSize.bodyLarge, fontFamily: theme.typography.fontFamily.bold, color: theme.colors.background, }, // Continue button text disabled continueButtonTextDisabled: { color: theme.colors.textMuted, }, }); export default DocumentUploadStep; /* * End of File: DocumentUploadStep.tsx * Design & Developed by Tech4Biz Solutions * Copyright (c) Spurrin Innovations. All rights reserved. */