upload document type modified
This commit is contained in:
parent
848bd3ea93
commit
9eb1416866
@ -28,6 +28,7 @@ import { theme } from '../../../../theme/theme';
|
|||||||
import { DocumentUploadStepProps } from '../../types/signup';
|
import { DocumentUploadStepProps } from '../../types/signup';
|
||||||
import Icon from 'react-native-vector-icons/Feather';
|
import Icon from 'react-native-vector-icons/Feather';
|
||||||
import { showError, showSuccess } from '../../../../shared/utils/toast';
|
import { showError, showSuccess } from '../../../../shared/utils/toast';
|
||||||
|
import { validateFileType, validateFileSize, formatFileSize } from '../../../../shared/utils/fileUpload';
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// INTERFACES
|
// INTERFACES
|
||||||
@ -183,6 +184,17 @@ const DocumentUploadStep: React.FC<DocumentUploadStepProps> = ({
|
|||||||
size: asset.fileSize,
|
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);
|
setSelectedImage(imageData);
|
||||||
showSuccess('Success', 'Document captured successfully!');
|
showSuccess('Success', 'Document captured successfully!');
|
||||||
}
|
}
|
||||||
@ -221,6 +233,17 @@ const DocumentUploadStep: React.FC<DocumentUploadStepProps> = ({
|
|||||||
size: asset.fileSize,
|
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);
|
setSelectedImage(imageData);
|
||||||
showSuccess('Success', 'Document selected from gallery!');
|
showSuccess('Success', 'Document selected from gallery!');
|
||||||
}
|
}
|
||||||
@ -231,23 +254,7 @@ const DocumentUploadStep: React.FC<DocumentUploadStepProps> = ({
|
|||||||
// UTILITY FUNCTIONS
|
// UTILITY FUNCTIONS
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
* Get File Type Display
|
||||||
|
|||||||
@ -29,6 +29,7 @@ import { useAppDispatch, useAppSelector } from '../../../store/hooks';
|
|||||||
import { updateOnboarded, logout } from '../redux/authSlice';
|
import { updateOnboarded, logout } from '../redux/authSlice';
|
||||||
import { authAPI } from '../services/authAPI';
|
import { authAPI } from '../services/authAPI';
|
||||||
import { showError, showSuccess } from '../../../shared/utils/toast';
|
import { showError, showSuccess } from '../../../shared/utils/toast';
|
||||||
|
import { validateFileType, validateFileSize, prepareFileForUpload } from '../../../shared/utils/fileUpload';
|
||||||
import { theme } from '../../../theme/theme';
|
import { theme } from '../../../theme/theme';
|
||||||
import Icon from 'react-native-vector-icons/Feather';
|
import Icon from 'react-native-vector-icons/Feather';
|
||||||
import { AuthNavigationProp } from '../navigation/navigationTypes';
|
import { AuthNavigationProp } from '../navigation/navigationTypes';
|
||||||
@ -364,6 +365,17 @@ export const ResetPasswordScreen: React.FC<ResetPasswordScreenProps> = ({
|
|||||||
size: asset.fileSize,
|
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);
|
setSelectedImage(imageData);
|
||||||
showSuccess('Success', 'Document captured successfully!');
|
showSuccess('Success', 'Document captured successfully!');
|
||||||
}
|
}
|
||||||
@ -402,6 +414,17 @@ export const ResetPasswordScreen: React.FC<ResetPasswordScreenProps> = ({
|
|||||||
size: asset.fileSize,
|
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);
|
setSelectedImage(imageData);
|
||||||
showSuccess('Success', 'Document selected from gallery!');
|
showSuccess('Success', 'Document selected from gallery!');
|
||||||
}
|
}
|
||||||
@ -433,12 +456,11 @@ export const ResetPasswordScreen: React.FC<ResetPasswordScreenProps> = ({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
const file = {
|
|
||||||
uri: selectedImage.uri,
|
// Prepare file with proper structure using utility function
|
||||||
name: selectedImage.name,
|
const preparedFile = prepareFileForUpload(selectedImage, 'id_photo');
|
||||||
type: selectedImage.type,
|
|
||||||
};
|
formData.append('id_photo', preparedFile as any);
|
||||||
formData.append('id_photo', file as any);
|
|
||||||
|
|
||||||
const response: any = await authAPI.uploadDocument(formData, user?.access_token);
|
const response: any = await authAPI.uploadDocument(formData, user?.access_token);
|
||||||
console.log('upload response',response)
|
console.log('upload response',response)
|
||||||
|
|||||||
@ -40,6 +40,7 @@ import { selectHospitalLoading, selectHospitals } from '../redux/hospitalSelecto
|
|||||||
import { SignUpData, SignUpStep } from '../types/signup';
|
import { SignUpData, SignUpStep } from '../types/signup';
|
||||||
import { authAPI } from '../services/authAPI';
|
import { authAPI } from '../services/authAPI';
|
||||||
import { showError, showSuccess } from '../../../shared/utils/toast';
|
import { showError, showSuccess } from '../../../shared/utils/toast';
|
||||||
|
import { createFormDataWithFile, validateFileType, validateFileSize } from '../../../shared/utils/fileUpload';
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// INTERFACES
|
// INTERFACES
|
||||||
@ -225,26 +226,45 @@ const SignUpScreen: React.FC<SignUpScreenProps> = ({ navigation }) => {
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const formData = new FormData();
|
|
||||||
let role = 'er_physician';
|
let role = 'er_physician';
|
||||||
|
|
||||||
formData.append('email', payload.email);
|
// Prepare form data with proper file handling
|
||||||
formData.append('password', payload.password);
|
const formFields = {
|
||||||
formData.append('first_name', payload.first_name);
|
email: payload.email,
|
||||||
formData.append('last_name', payload.last_name);
|
password: payload.password,
|
||||||
formData.append('username', payload.username);
|
first_name: payload.first_name,
|
||||||
formData.append('dashboard_role', role);
|
last_name: payload.last_name,
|
||||||
formData.append('hospital_id', payload.hospital_id);
|
username: payload.username,
|
||||||
|
dashboard_role: role,
|
||||||
// Attach file if exists
|
hospital_id: payload.hospital_id,
|
||||||
if (payload.id_photo_url) {
|
|
||||||
const filePath = payload.id_photo_url;
|
|
||||||
const file = {
|
|
||||||
uri: filePath,
|
|
||||||
name: 'id_photo',
|
|
||||||
type: 'image/jpg',
|
|
||||||
};
|
};
|
||||||
formData.append('id_photo_url', file as any);
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('payload prepared', formData);
|
console.log('payload prepared', formData);
|
||||||
|
|||||||
@ -21,7 +21,11 @@ export const authAPI = {
|
|||||||
//fetch hospital list
|
//fetch hospital list
|
||||||
gethospitals: () => api.get('/api/hospitals/hospitals/app_user/hospitals', {},buildHeaders()),
|
gethospitals: () => api.get('/api/hospitals/hospitals/app_user/hospitals', {},buildHeaders()),
|
||||||
//user signup
|
//user signup
|
||||||
signup: (formData:any) => api.post('/api/auth/auth/admin/create-user-fromapp', formData,buildHeaders({ contentType: 'multipart/form-data' })),
|
signup: (formData:any) => api.post('/api/auth/auth/admin/create-user-fromapp', formData, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data',
|
||||||
|
},
|
||||||
|
}),
|
||||||
//validate email
|
//validate email
|
||||||
validatemail: (payload:{email:string}) => api.post('/api/auth/auth/check-email', payload,buildHeaders()),
|
validatemail: (payload:{email:string}) => api.post('/api/auth/auth/check-email', payload,buildHeaders()),
|
||||||
//change password
|
//change password
|
||||||
@ -29,7 +33,12 @@ export const authAPI = {
|
|||||||
//validate username
|
//validate username
|
||||||
validateusername: (username:string|undefined) => api.post('/api/auth/auth/check-username', {username},buildHeaders()),
|
validateusername: (username:string|undefined) => api.post('/api/auth/auth/check-username', {username},buildHeaders()),
|
||||||
//upload document for onboarding
|
//upload document for onboarding
|
||||||
uploadDocument: (formData:any, token:string | undefined) => api.post('/api/auth/onboarding/upload-id-photo', formData,buildHeaders({token:token, contentType: 'multipart/form-data' }))
|
uploadDocument: (formData:any, token:string | undefined) => api.post('/api/auth/onboarding/upload-id-photo', formData, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data',
|
||||||
|
...(token && { 'Authorization': `Bearer ${token}` }),
|
||||||
|
},
|
||||||
|
})
|
||||||
// Add more endpoints as needed
|
// Add more endpoints as needed
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
228
app/shared/utils/fileUpload.ts
Normal file
228
app/shared/utils/fileUpload.ts
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
/*
|
||||||
|
* File: fileUpload.ts
|
||||||
|
* Description: Utility functions for handling file uploads in React Native
|
||||||
|
* Design & Developed by Tech4Biz Solutions
|
||||||
|
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// INTERFACES
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FileData Interface
|
||||||
|
*
|
||||||
|
* Purpose: Defines the structure for file data from image picker
|
||||||
|
*/
|
||||||
|
export interface FileData {
|
||||||
|
uri: string;
|
||||||
|
name?: string;
|
||||||
|
type?: string;
|
||||||
|
size?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UploadFile Interface
|
||||||
|
*
|
||||||
|
* Purpose: Defines the structure for file object in FormData
|
||||||
|
*/
|
||||||
|
export interface UploadFile {
|
||||||
|
uri: string;
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// FILE UPLOAD UTILITIES
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare File for Upload
|
||||||
|
*
|
||||||
|
* Purpose: Convert FileData to proper UploadFile structure for FormData
|
||||||
|
*
|
||||||
|
* @param fileData - File data from image picker
|
||||||
|
* @param fieldName - Form field name for the file
|
||||||
|
* @returns Properly formatted UploadFile object
|
||||||
|
*/
|
||||||
|
export const prepareFileForUpload = (
|
||||||
|
fileData: FileData,
|
||||||
|
fieldName: string = 'file'
|
||||||
|
): UploadFile => {
|
||||||
|
// Extract file extension from URI
|
||||||
|
const uri = fileData.uri;
|
||||||
|
const fileName = fileData.name || `${fieldName}_${Date.now()}`;
|
||||||
|
|
||||||
|
// Determine file extension and MIME type
|
||||||
|
let fileExtension = 'jpg';
|
||||||
|
let mimeType = 'image/jpeg';
|
||||||
|
|
||||||
|
if (fileData.type) {
|
||||||
|
// Use provided type
|
||||||
|
mimeType = fileData.type;
|
||||||
|
fileExtension = mimeType.split('/')[1] || 'jpg';
|
||||||
|
} else {
|
||||||
|
// Extract from URI
|
||||||
|
const uriParts = uri.split('.');
|
||||||
|
if (uriParts.length > 1) {
|
||||||
|
fileExtension = uriParts[uriParts.length - 1].toLowerCase();
|
||||||
|
|
||||||
|
// Map extension to MIME type
|
||||||
|
switch (fileExtension) {
|
||||||
|
case 'png':
|
||||||
|
mimeType = 'image/png';
|
||||||
|
break;
|
||||||
|
case 'jpeg':
|
||||||
|
case 'jpg':
|
||||||
|
mimeType = 'image/jpeg';
|
||||||
|
break;
|
||||||
|
case 'gif':
|
||||||
|
mimeType = 'image/gif';
|
||||||
|
break;
|
||||||
|
case 'webp':
|
||||||
|
mimeType = 'image/webp';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
mimeType = 'image/jpeg';
|
||||||
|
fileExtension = 'jpg';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure filename has proper extension
|
||||||
|
const finalFileName = fileName.includes('.')
|
||||||
|
? fileName
|
||||||
|
: `${fileName}.${fileExtension}`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
uri,
|
||||||
|
name: finalFileName,
|
||||||
|
type: mimeType,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create FormData with File
|
||||||
|
*
|
||||||
|
* Purpose: Create FormData object with proper file attachment
|
||||||
|
*
|
||||||
|
* @param data - Object containing form fields
|
||||||
|
* @param fileData - File data to upload
|
||||||
|
* @param fileFieldName - Form field name for the file
|
||||||
|
* @returns FormData object ready for upload
|
||||||
|
*/
|
||||||
|
export const createFormDataWithFile = (
|
||||||
|
data: Record<string, any>,
|
||||||
|
fileData?: FileData,
|
||||||
|
fileFieldName: string = 'file'
|
||||||
|
): FormData => {
|
||||||
|
const formData = new FormData();
|
||||||
|
|
||||||
|
// Add regular form fields
|
||||||
|
Object.keys(data).forEach(key => {
|
||||||
|
if (data[key] !== null && data[key] !== undefined) {
|
||||||
|
formData.append(key, String(data[key]));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add file if provided
|
||||||
|
if (fileData) {
|
||||||
|
const uploadFile = prepareFileForUpload(fileData, fileFieldName);
|
||||||
|
formData.append(fileFieldName, uploadFile as any);
|
||||||
|
}
|
||||||
|
|
||||||
|
return formData;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate File Type
|
||||||
|
*
|
||||||
|
* Purpose: Check if file type is allowed
|
||||||
|
*
|
||||||
|
* @param fileData - File data to validate
|
||||||
|
* @param allowedTypes - Array of allowed MIME types
|
||||||
|
* @returns Boolean indicating if file type is allowed
|
||||||
|
*/
|
||||||
|
export const validateFileType = (
|
||||||
|
fileData: FileData,
|
||||||
|
allowedTypes: string[] = ['image/jpeg', 'image/jpg', 'image/png']
|
||||||
|
): boolean => {
|
||||||
|
if (!fileData.type) {
|
||||||
|
// If no type provided, try to determine from URI
|
||||||
|
const uri = fileData.uri.toLowerCase();
|
||||||
|
if (uri.includes('.jpg') || uri.includes('.jpeg')) {
|
||||||
|
return allowedTypes.includes('image/jpeg') || allowedTypes.includes('image/jpg');
|
||||||
|
}
|
||||||
|
if (uri.includes('.png')) {
|
||||||
|
return allowedTypes.includes('image/png');
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return allowedTypes.includes(fileData.type.toLowerCase());
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate File Size
|
||||||
|
*
|
||||||
|
* Purpose: Check if file size is within limits
|
||||||
|
*
|
||||||
|
* @param fileData - File data to validate
|
||||||
|
* @param maxSizeMB - Maximum file size in MB
|
||||||
|
* @returns Boolean indicating if file size is acceptable
|
||||||
|
*/
|
||||||
|
export const validateFileSize = (
|
||||||
|
fileData: FileData,
|
||||||
|
maxSizeMB: number = 10
|
||||||
|
): boolean => {
|
||||||
|
if (!fileData.size) {
|
||||||
|
return true; // Skip validation if size not available
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxSizeBytes = maxSizeMB * 1024 * 1024;
|
||||||
|
return fileData.size <= maxSizeBytes;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format File Size
|
||||||
|
*
|
||||||
|
* Purpose: Convert bytes to human readable format
|
||||||
|
*
|
||||||
|
* @param bytes - File size in bytes
|
||||||
|
* @returns Formatted file size string
|
||||||
|
*/
|
||||||
|
export 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 Name
|
||||||
|
*
|
||||||
|
* Purpose: Get user-friendly display name for file type
|
||||||
|
*
|
||||||
|
* @param mimeType - MIME type string
|
||||||
|
* @returns Display name for file type
|
||||||
|
*/
|
||||||
|
export const getFileTypeDisplay = (mimeType: string): string => {
|
||||||
|
const typeMap: Record<string, string> = {
|
||||||
|
'image/jpeg': 'JPEG',
|
||||||
|
'image/jpg': 'JPEG',
|
||||||
|
'image/png': 'PNG',
|
||||||
|
'image/gif': 'GIF',
|
||||||
|
'image/webp': 'WebP',
|
||||||
|
};
|
||||||
|
|
||||||
|
return typeMap[mimeType.toLowerCase()] || 'Image';
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* End of File: fileUpload.ts
|
||||||
|
* Design & Developed by Tech4Biz Solutions
|
||||||
|
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||||
|
*/
|
||||||
Loading…
Reference in New Issue
Block a user