NeoScan_Physician/app/modules/Auth/components/DocumentUploadScreen.tsx

409 lines
11 KiB
TypeScript

"use client"
/*
* File: DocumentUploadScreen.tsx
* Description: Image upload screen for onboarding flow with react-native-image-picker.
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import type React from "react"
import { useState } from "react"
import {
View,
Text,
StyleSheet,
TouchableWithoutFeedback,
Keyboard,
TouchableOpacity,
Alert,
PermissionsAndroid,
Platform,
Image
} from "react-native"
import {
launchImageLibrary,
launchCamera,
ImagePickerResponse,
MediaType,
ImagePickerOptions
} from 'react-native-image-picker'
import { Button } from "../../../../shared/src/components/Button"
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
name: string
type: string
size?: number
}) => void
onBack: () => void
Data:SignUpData
}
interface ImageData {
uri: string
name: string
type: string
size?: number
}
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
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(err)
return false
}
}
return true
}
const handleImagePicker = () => {
Alert.alert("Select Image", "Choose how you want to upload your image", [
{
text: "Camera",
onPress: () => handleCameraCapture(),
},
{
text: "Gallery",
onPress: () => handleGalleryPicker(),
},
{
text: "Cancel",
style: "cancel",
},
])
}
const handleCameraCapture = async () => {
const hasPermission = await requestCameraPermission()
if (!hasPermission) {
showError("Permission Error", "Camera permission is required to capture images.")
return
}
const options: ImagePickerOptions = {
mediaType: 'photo' as MediaType,
quality: 0.8,
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 || `image_${Date.now()}.jpg`,
type: asset.type || 'image/jpeg',
size: asset.fileSize,
}
setSelectedImage(imageData)
showSuccess("Success", "Image captured successfully!")
}
})
}
const handleGalleryPicker = () => {
const options: ImagePickerOptions = {
mediaType: 'photo' as MediaType,
quality: 0.8,
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 || `image_${Date.now()}.jpg`,
type: asset.type || 'image/jpeg',
size: asset.fileSize,
}
setSelectedImage(imageData)
showSuccess("Success", "Image selected from gallery!")
}
})
}
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]
}
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'
}
const handleContinue = () => {
if (!selectedImage) {
showError("Validation Error", "Please upload an image to continue.")
return
}
setLoading(true)
setTimeout(() => {
setLoading(false)
onContinue(selectedImage)
}, 1000)
}
return (
<OnboardingContainer onBack={onBack}>
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
<View style={styles.container}>
<View style={styles.content}>
<IconContainer Icon={()=><HospitalUploadSVG/>} />
<Text style={styles.title}>Upload Image</Text>
<Text style={styles.subtitle}>Please upload your profile picture or identification image.</Text>
<TouchableOpacity style={styles.uploadContainer} onPress={handleImagePicker}>
<View style={styles.uploadContent}>
{selectedImage ? (
<View style={styles.imagePreviewContainer}>
<Image source={{ uri: selectedImage.uri }} style={styles.imagePreview} />
<TouchableOpacity style={styles.imageOverlay} onPress={()=>setSelectedImage(null)}>
<Icon name="x" size={24} color={'#FFFFFF'} />
</TouchableOpacity>
<Text style={styles.uploadedText}>Image Uploaded</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={Colors.textSecondary} />
<Text style={styles.uploadText}>Tap to upload image</Text>
<Text style={styles.uploadSubtext}>JPG, PNG supported</Text>
</>
)}
</View>
</TouchableOpacity>
{selectedImage && (
<TouchableOpacity style={styles.changeButton} onPress={handleImagePicker}>
<Text style={styles.changeButtonText}>Change Image</Text>
</TouchableOpacity>
)}
<Button
title="Continue"
onPress={handleContinue}
style={[styles.button, !selectedImage && styles.buttonDisabled]}
loading={loading}
/>
</View>
</View>
</TouchableWithoutFeedback>
</OnboardingContainer>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
content: {
flex: 1,
justifyContent: "center",
},
iconContainer: {
alignItems: "center",
marginBottom: Spacing.xl,
},
iconWrapper: {
width: 80,
height: 80,
borderRadius: 20,
backgroundColor: Colors.backgroundAlt,
justifyContent: "center",
alignItems: "center",
borderWidth: 1,
borderColor: Colors.border,
},
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,
},
uploadContainer: {
backgroundColor: Colors.backgroundAlt,
borderWidth: 2,
borderColor: Colors.border,
borderStyle: "dashed",
borderRadius: 12,
padding: Spacing.xl,
marginBottom: Spacing.md,
minHeight: 200,
},
uploadContent: {
alignItems: "center",
},
imagePreviewContainer: {
alignItems: "center",
width: '100%',
},
imagePreview: {
width: '100%',
height: 150,
borderRadius: 12,
marginBottom: Spacing.sm,
resizeMode: 'contain',
},
imageOverlay: {
position: 'absolute',
top: 0,
right: 20,
marginRight: -20, // Half of image width minus half of overlay
backgroundColor: 'rgba(0, 0, 0, 0.4)',
borderRadius: 17,
width: 34,
height: 34,
justifyContent: 'center',
alignItems: 'center',
padding:2
},
uploadText: {
fontFamily: Typography.fontFamily.medium,
fontSize: Typography.fontSize.md,
color: Colors.textPrimary,
marginTop: Spacing.sm,
},
uploadSubtext: {
fontFamily: Typography.fontFamily.regular,
fontSize: Typography.fontSize.sm,
color: Colors.textSecondary,
marginTop: Spacing.xs,
},
uploadedText: {
fontFamily: Typography.fontFamily.medium,
fontSize: Typography.fontSize.md,
color: Colors.success,
marginTop: Spacing.sm,
},
fileName: {
fontFamily: Typography.fontFamily.regular,
fontSize: Typography.fontSize.sm,
color: Colors.textSecondary,
marginTop: Spacing.xs,
textAlign: "center",
maxWidth: '80%',
},
fileDetails: {
flexDirection: "row",
alignItems: "center",
marginTop: Spacing.xs,
},
fileType: {
fontFamily: Typography.fontFamily.regular,
fontSize: Typography.fontSize.xs,
color: Colors.textMuted,
},
fileSize: {
fontFamily: Typography.fontFamily.regular,
fontSize: Typography.fontSize.xs,
color: Colors.textMuted,
},
changeButton: {
alignSelf: "center",
marginBottom: Spacing.xl,
},
changeButtonText: {
fontFamily: Typography.fontFamily.medium,
fontSize: Typography.fontSize.md,
color: Colors.primary,
},
button: {
marginTop: Spacing.md,
},
buttonDisabled: {
opacity: 0.5,
},
})
export default DocumentUploadScreen
/*
* End of File: DocumentUploadScreen.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/