Clean Code - Enroll
This commit is contained in:
parent
1ede4324fa
commit
895433ddee
@ -1,59 +1,69 @@
|
||||
import React, { useState, useRef, useEffect } from 'react'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faTimes, faImage } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FileUploader } from 'react-drag-drop-files';
|
||||
import { faTimes, faCloudUploadAlt } from '@fortawesome/free-solid-svg-icons';
|
||||
import Select from 'react-select'
|
||||
import { ServerDownAnimation } from '../../../../assets/images';
|
||||
|
||||
const Enroll = () => {
|
||||
|
||||
const BASE_URL = process.env.REACT_APP_BASE_URL
|
||||
const API_KEY = process.env.REACT_APP_API_KEY
|
||||
// Environment variables
|
||||
const BASE_URL = process.env.REACT_APP_BASE_URL;
|
||||
const API_KEY = process.env.REACT_APP_API_KEY;
|
||||
|
||||
const fileTypes = ["JPG", "JPEG"];
|
||||
// State hooks
|
||||
const [file, setFile] = useState(null);
|
||||
|
||||
const [errorMessage, setErrorMessage] = useState('');
|
||||
const [imageError, setImageError] = useState('');
|
||||
const [selectedImageName, setSelectedImageName] = useState('');
|
||||
const [resultImageLabel, setresultImageLabel] = useState("");
|
||||
const fileInputRef = useRef(null);
|
||||
const [showResult, setShowResult] = useState(false);
|
||||
const [applicationId, setApplicationId] = useState('');
|
||||
const [applicationIds, setApplicationIds] = useState([]);
|
||||
const [selectedQuota, setSelectedQuota] = useState(0);
|
||||
const [subjectId, setSubjectId] = useState('');
|
||||
const [subjectIds, setSubjectIds] = useState([]);
|
||||
const [imageUrl, setImageUrl] = useState('');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [applicationId, setApplicationId] = useState('');
|
||||
const [applicationIds, setApplicationIds] = useState([]);
|
||||
const [selectedQuota, setSelectedQuota] = useState(0);
|
||||
const [showResult, setShowResult] = useState(false);
|
||||
const [resultImageLabel, setresultImageLabel] = useState("");
|
||||
|
||||
// Error messages
|
||||
const [errorMessage, setErrorMessage] = useState('');
|
||||
const [applicationError, setApplicationError] = useState('');
|
||||
const [subjectError, setSubjectError] = useState('');
|
||||
const [imageError, setImageError] = useState('');
|
||||
const [subjectAvailabilityMessage, setSubjectAvailabilityMessage] = useState(''); // Message for subject availability
|
||||
const [subjectAvailabilityMessage, setSubjectAvailabilityMessage] = useState('');
|
||||
|
||||
const [inputValueApplication, setInputValueApplication] = useState(''); // Controlled input value for Application ID
|
||||
// Controlled inputs
|
||||
const [inputValueApplication, setInputValueApplication] = useState('');
|
||||
const [options, setOptions] = useState([]);
|
||||
|
||||
// Mobile check
|
||||
const [isMobile, setIsMobile] = useState(false);
|
||||
|
||||
// Server check
|
||||
const [isServer, setIsServer] = useState(true);
|
||||
|
||||
// Validation errors
|
||||
const [validationErrors, setValidationErrors] = useState({
|
||||
applicationId: '',
|
||||
file: ''
|
||||
});
|
||||
|
||||
// Application Options
|
||||
const applicationOptions = applicationIds.map(app => ({
|
||||
value: app.id,
|
||||
label: app.name
|
||||
}));
|
||||
|
||||
const fileInputRef = useRef(null);
|
||||
|
||||
// Fetch application IDs and subject options on component mount
|
||||
useEffect(() => {
|
||||
const fetchApplicationIds = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response = await fetch(`${BASE_URL}/application/list`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'accept': 'application/json',
|
||||
'x-api-key': API_KEY,
|
||||
},
|
||||
headers: { 'accept': 'application/json', 'x-api-key': API_KEY },
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch application IDs');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log('Response Data:', data);
|
||||
|
||||
if (data.status_code === 200) {
|
||||
setApplicationIds(data.details.data);
|
||||
@ -73,56 +83,44 @@ const Enroll = () => {
|
||||
fetchApplicationIds();
|
||||
setOptions(subjectIds.map(id => ({ value: id, label: id })));
|
||||
|
||||
// Handle mobile responsiveness
|
||||
const handleResize = () => {
|
||||
setIsMobile(window.innerWidth <= 768); // Deteksi apakah layar kecil (mobile)
|
||||
setIsMobile(window.innerWidth <= 768);
|
||||
};
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
handleResize(); // Initial check
|
||||
|
||||
return () => window.removeEventListener('resize', handleResize);
|
||||
|
||||
}, [subjectIds]);
|
||||
|
||||
// Handle application change
|
||||
const handleApplicationChange = async (selectedOption) => {
|
||||
const selectedId = selectedOption.value;
|
||||
const selectedApp = applicationIds.find(app => app.id === parseInt(selectedId));
|
||||
|
||||
if (!selectedOption) {
|
||||
console.error("Selected option is undefined");
|
||||
return;
|
||||
}
|
||||
if (selectedApp) {
|
||||
setSelectedQuota(selectedApp.quota);
|
||||
}
|
||||
|
||||
setApplicationId(selectedId);
|
||||
|
||||
// Fetch subjects related to the application
|
||||
await fetchSubjectIds(selectedId);
|
||||
};
|
||||
|
||||
// Handle input change for Application ID
|
||||
const handleInputChangeApplication = (newInputValue) => {
|
||||
// Limit input to 15 characters for Application ID
|
||||
if (newInputValue.length <= 15) {
|
||||
setInputValueApplication(newInputValue);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Fetch subject IDs based on application ID
|
||||
const fetchSubjectIds = async (appId) => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const response = await fetch(`${BASE_URL}/trx_face/list/subject?application_id=${appId}&search=${subjectId}&limit=99`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'accept': 'application/json',
|
||||
'x-api-key': API_KEY,
|
||||
},
|
||||
headers: { 'accept': 'application/json', 'x-api-key': API_KEY },
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
console.log("Fetched Subject IDs:", data); // Log data fetched from API
|
||||
|
||||
if (data.status_code === 200) {
|
||||
setSubjectIds(data.details.data);
|
||||
@ -136,105 +134,86 @@ const Enroll = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// Handle Drop File Image
|
||||
const handleFileDrop = (files) => {
|
||||
if (files && files[0]) {
|
||||
handleImageUpload(files[0]);
|
||||
} else {
|
||||
console.error('No valid files dropped');
|
||||
}
|
||||
};
|
||||
|
||||
// Handle image file upload
|
||||
const handleImageUpload = (file) => {
|
||||
if (!file) {
|
||||
setImageError('Please select a file');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check file size (2MB = 2 * 1024 * 1024 bytes)
|
||||
const maxSize = 2 * 1024 * 1024;
|
||||
const maxSize = 2 * 1024 * 1024; // 2MB
|
||||
if (file.size > maxSize) {
|
||||
setImageError('File size exceeds 2MB limit');
|
||||
setFile(null);
|
||||
setSelectedImageName('');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check file type using both extension and MIME type
|
||||
const fileExtension = file.name.split('.').pop().toLowerCase();
|
||||
const validExtensions = ['jpg', 'jpeg'];
|
||||
const validMimeTypes = ['image/jpeg', 'image/jpg'];
|
||||
|
||||
if (!validExtensions.includes(fileExtension) || !validMimeTypes.includes(file.type)) {
|
||||
setImageError('Only JPG/JPEG files are allowed');
|
||||
setFile(null);
|
||||
setSelectedImageName('');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check image dimensions
|
||||
const previewUrl = URL.createObjectURL(file);
|
||||
const img = new Image();
|
||||
const objectUrl = URL.createObjectURL(file);
|
||||
|
||||
img.onload = () => {
|
||||
URL.revokeObjectURL(objectUrl);
|
||||
|
||||
if (img.width > 300 || img.height > 300) {
|
||||
setImageError('Image dimensions must not exceed 300x300 pixels');
|
||||
setFile(null);
|
||||
setSelectedImageName('');
|
||||
return;
|
||||
}
|
||||
|
||||
// All validations passed
|
||||
setSelectedImageName(file.name);
|
||||
setFile(file);
|
||||
setSelectedImageName(file.name);
|
||||
setImageUrl(previewUrl);
|
||||
setImageError('');
|
||||
};
|
||||
|
||||
img.onerror = () => {
|
||||
URL.revokeObjectURL(objectUrl);
|
||||
URL.revokeObjectURL(previewUrl);
|
||||
setImageError('Invalid image file');
|
||||
setFile(null);
|
||||
setSelectedImageName('');
|
||||
};
|
||||
|
||||
img.src = objectUrl;
|
||||
img.src = previewUrl;
|
||||
};
|
||||
|
||||
|
||||
// Cancel image upload
|
||||
const handleImageCancel = () => {
|
||||
setSelectedImageName('');
|
||||
setFile(null);
|
||||
if (fileInputRef.current) {
|
||||
fileInputRef.current.value = '';
|
||||
}
|
||||
if (fileInputRef.current) fileInputRef.current.value = '';
|
||||
};
|
||||
|
||||
|
||||
// Handle form validation and enrollment submission
|
||||
const handleEnrollClick = async () => {
|
||||
let hasError = false;
|
||||
|
||||
// Validate inputs and set corresponding errors
|
||||
const validationErrors = {
|
||||
imageError: !selectedImageName ? 'Please upload a face photo before enrolling.' : '',
|
||||
applicationError: !applicationId ? 'Please select an Application ID before enrolling.' : '',
|
||||
subjectError: !subjectId ? 'Please enter a Subject ID before enrolling.' : '',
|
||||
};
|
||||
|
||||
// Update state with errors
|
||||
if (validationErrors.imageError) {
|
||||
setImageError(validationErrors.imageError);
|
||||
hasError = true;
|
||||
} else {
|
||||
setImageError(''); // Clear error if valid
|
||||
}
|
||||
|
||||
if (validationErrors.applicationError) {
|
||||
setApplicationError(validationErrors.applicationError);
|
||||
hasError = true;
|
||||
} else {
|
||||
setApplicationError(''); // Clear error if valid
|
||||
}
|
||||
|
||||
if (validationErrors.subjectError) {
|
||||
setSubjectError(validationErrors.subjectError);
|
||||
hasError = true;
|
||||
} else {
|
||||
setSubjectError(''); // Clear error if valid
|
||||
}
|
||||
|
||||
// If there are errors, return early
|
||||
if (hasError) return;
|
||||
|
||||
if (!file) {
|
||||
@ -247,98 +226,83 @@ const Enroll = () => {
|
||||
formData.append('subject_id', subjectId);
|
||||
formData.append('file', file);
|
||||
|
||||
console.log('Inputs:', {
|
||||
applicationId,
|
||||
subjectId,
|
||||
file: file.name,
|
||||
});
|
||||
|
||||
setIsLoading(true);
|
||||
setErrorMessage(''); // Clear previous error message
|
||||
setErrorMessage('');
|
||||
|
||||
try {
|
||||
const response = await fetch(`${BASE_URL}/face_recognition/enroll`, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
headers: {
|
||||
'accept': 'application/json',
|
||||
'x-api-key': `${API_KEY}`,
|
||||
}
|
||||
headers: { 'accept': 'application/json', 'x-api-key': API_KEY },
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorDetails = await response.json();
|
||||
console.error('Response error details:', errorDetails);
|
||||
if (errorDetails.detail && errorDetails.detail.includes('Subject ID')) {
|
||||
setSubjectError(errorDetails.detail);
|
||||
} else {
|
||||
setErrorMessage(errorDetails.detail || 'Failed to enroll, please try again');
|
||||
}
|
||||
setErrorMessage(errorDetails.detail || 'Failed to enroll, please try again');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
console.log('Enrollment response:', result);
|
||||
|
||||
// Update quota based on the response data
|
||||
if (result.details && result.details.data && result.details.data.quota !== undefined) {
|
||||
const updatedQuota = result.details.data.quota;
|
||||
setSelectedQuota(updatedQuota); // Update the state with the new quota
|
||||
if (result.details.data.quota !== undefined) {
|
||||
setSelectedQuota(result.details.data.quota);
|
||||
}
|
||||
|
||||
if (result.details && result.details.data && result.details.data.image_url) {
|
||||
const imageFileName = result.details.data.image_url.split('/').pop();
|
||||
console.log('Image URL:', result.details.data.image_url);
|
||||
await fetchImage(imageFileName);
|
||||
|
||||
// Set resultImageLabel after successful enrollment
|
||||
setresultImageLabel(selectedImageName); // Set resultImageLabel after success
|
||||
} else {
|
||||
console.error('Image URL not found in response:', result);
|
||||
setErrorMessage('Image URL not found in response. Please try again.');
|
||||
if (result.details.data.image_url) {
|
||||
await fetchImage(result.details.data.image_url.split('/').pop());
|
||||
setresultImageLabel(selectedImageName);
|
||||
}
|
||||
|
||||
setShowResult(true);
|
||||
console.log('Enrollment successful:', result);
|
||||
} catch (error) {
|
||||
console.error('Error during API call:', error);
|
||||
setErrorMessage('An unexpected error occurred. Please try again.');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Fetch image preview
|
||||
const fetchImage = async (imageFileName) => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const response = await fetch(`${BASE_URL}/preview/image/${imageFileName}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'accept': 'application/json',
|
||||
'x-api-key': API_KEY,
|
||||
}
|
||||
headers: { 'accept': 'application/json', 'x-api-key': API_KEY },
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorDetails = await response.json();
|
||||
console.error('Image fetch error details:', errorDetails);
|
||||
setErrorMessage('Failed to fetch image, please try again.');
|
||||
return;
|
||||
}
|
||||
|
||||
const imageBlob = await response.blob();
|
||||
const imageData = URL.createObjectURL(imageBlob);
|
||||
console.log('Fetched image URL:', imageData);
|
||||
|
||||
setImageUrl(imageData);
|
||||
setImageUrl(URL.createObjectURL(imageBlob));
|
||||
} catch (error) {
|
||||
console.error('Error fetching image:', error);
|
||||
setErrorMessage(error.message);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle subject ID change
|
||||
const handleSubjectIdChange = (e) => {
|
||||
const id = e.target.value;
|
||||
setSubjectId(id);
|
||||
|
||||
if (id) {
|
||||
const exists = subjectIds.includes(id);
|
||||
if (exists) {
|
||||
setSubjectAvailabilityMessage('Subject already exists.');
|
||||
setSubjectError('');
|
||||
} else {
|
||||
setSubjectAvailabilityMessage('This subject ID is available.');
|
||||
setSubjectError('');
|
||||
}
|
||||
} else {
|
||||
setSubjectAvailabilityMessage('');
|
||||
}
|
||||
};
|
||||
|
||||
// handle Labeling
|
||||
const CustomLabel = ({ overRide, children, ...props }) => {
|
||||
// We intentionally don't pass `overRide` to the label
|
||||
return (
|
||||
@ -348,52 +312,34 @@ const Enroll = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const applicationOptions = applicationIds.map(app => ({
|
||||
value: app.id,
|
||||
label: app.name
|
||||
}));
|
||||
|
||||
const handleSubjectIdChange = async (e) => {
|
||||
const id = e.target.value;
|
||||
setSubjectId(id);
|
||||
|
||||
console.log("Current Subject ID Input:", id); // Debugging: Log input
|
||||
|
||||
if (id) {
|
||||
const exists = subjectIds.includes(id);
|
||||
console.log("Subject IDs:", subjectIds); // Debugging: Log existing Subject IDs
|
||||
|
||||
if (exists) {
|
||||
setSubjectAvailabilityMessage('Subject already exists.'); // Error message
|
||||
setSubjectError(''); // Clear any subject error
|
||||
} else {
|
||||
setSubjectAvailabilityMessage('This subject ID is available.'); // Success message
|
||||
setSubjectError('');
|
||||
}
|
||||
} else {
|
||||
setSubjectAvailabilityMessage(''); // Clear message if input is empty
|
||||
}
|
||||
};
|
||||
// Handle Server Down
|
||||
if (!isServer) {
|
||||
return (
|
||||
<div style={{ textAlign: 'center', marginTop: '50px' }}>
|
||||
<img
|
||||
src={ServerDownAnimation}
|
||||
alt="Server Down Animation"
|
||||
style={{ width: '18rem', height: '18rem', marginBottom: '20px' }}
|
||||
/>
|
||||
<h2 style={{ color: 'red' }}>Server tidak dapat diakses</h2>
|
||||
<p>{errorMessage || 'Silakan periksa koneksi internet Anda atau coba lagi nanti.'}</p>
|
||||
<button
|
||||
onClick={() => window.location.reload()}
|
||||
style={{
|
||||
padding: '10px 20px',
|
||||
backgroundColor: '#0542cc',
|
||||
color: '#fff',
|
||||
border: 'none',
|
||||
borderRadius: '5px',
|
||||
cursor: 'pointer'
|
||||
}}>
|
||||
Coba Lagi
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = {
|
||||
formGroup: {
|
||||
marginTop: '-45px',
|
||||
},
|
||||
selectWrapper: {
|
||||
position: 'relative',
|
||||
marginTop: '0',
|
||||
},
|
||||
select: {
|
||||
width: '100%',
|
||||
paddingRight: '30px',
|
||||
},
|
||||
chevronIcon: {
|
||||
position: 'absolute',
|
||||
right: '10px',
|
||||
top: '50%',
|
||||
transform: 'translateY(-50%)',
|
||||
pointerEvents: 'none',
|
||||
},
|
||||
remainingQuota: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
@ -423,20 +369,6 @@ const Enroll = () => {
|
||||
border: '1px solid #ced4da',
|
||||
borderRadius: '0.25rem',
|
||||
},
|
||||
uploadAreaMobile: {
|
||||
backgroundColor: '#e6f2ff',
|
||||
height: '50svh',
|
||||
cursor: 'pointer',
|
||||
marginTop: '1rem',
|
||||
paddingTop: '18px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
border: '1px solid #ced4da',
|
||||
borderRadius: '0.25rem',
|
||||
padding: '20px',
|
||||
},
|
||||
uploadIcon: {
|
||||
fontSize: '40px',
|
||||
color: '#0542cc',
|
||||
@ -448,55 +380,10 @@ const Enroll = () => {
|
||||
fontSize: '16px',
|
||||
lineHeight: '13px',
|
||||
},
|
||||
wrapper: {
|
||||
border: '1px solid #ddd',
|
||||
borderRadius: '6px',
|
||||
padding: '18px 10px 0 8px',
|
||||
height: '13svh',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
backgroundColor: '#f9f9f9',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
fileWrapper: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
flex: '1',
|
||||
},
|
||||
textContainer: {
|
||||
flex: '1',
|
||||
fontSize: '16px',
|
||||
marginLeft: '6px',
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap',
|
||||
textOverflow: 'ellipsis',
|
||||
marginTop: '1rem',
|
||||
},
|
||||
fileSize: {
|
||||
fontSize: '12px',
|
||||
color: '#555',
|
||||
marginBottom: '2rem',
|
||||
},
|
||||
closeButtonContainer: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
marginLeft: 'auto',
|
||||
},
|
||||
closeButton: {
|
||||
background: 'transparent',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
padding: '0',
|
||||
},
|
||||
imageIcon: {
|
||||
color: '#0542cc',
|
||||
fontSize: '18px',
|
||||
marginRight: '6px',
|
||||
},
|
||||
closeIcon: {
|
||||
color: 'red',
|
||||
fontSize: '18px',
|
||||
customLabel: {
|
||||
fontWeight: 600,
|
||||
fontSize: '14px',
|
||||
color: '#212529',
|
||||
},
|
||||
submitButton: {
|
||||
marginLeft: 'auto',
|
||||
@ -505,10 +392,11 @@ const Enroll = () => {
|
||||
position: 'relative',
|
||||
zIndex: 1,
|
||||
},
|
||||
uploadError: {
|
||||
color: 'red',
|
||||
errorText: {
|
||||
color: '#dc3545',
|
||||
fontSize: '12px',
|
||||
marginTop: '5px',
|
||||
display: 'block'
|
||||
},
|
||||
containerResultStyle: {
|
||||
padding: '20px',
|
||||
@ -553,16 +441,6 @@ const Enroll = () => {
|
||||
width: '100%',
|
||||
marginTop: isMobile ? '10px' : '0',
|
||||
},
|
||||
imageStyle: {
|
||||
width: '300px',
|
||||
height: '300px',
|
||||
borderRadius: '5px',
|
||||
},
|
||||
imageStyleMobile: {
|
||||
width: '100%',
|
||||
height: 'auto',
|
||||
borderRadius: '5px',
|
||||
},
|
||||
imageDetails: {
|
||||
marginTop: '10px',
|
||||
fontSize: isMobile ? '14px' : '16px',
|
||||
@ -594,107 +472,9 @@ const Enroll = () => {
|
||||
fontSize: '1.2rem',
|
||||
color: '#fff',
|
||||
textAlign: 'center',
|
||||
},
|
||||
uploadedFileWrapper: {
|
||||
backgroundColor: '#fff',
|
||||
border: '0.2px solid gray',
|
||||
padding: '15px 0 0 17px',
|
||||
borderRadius: '5px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '10px',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
uploadedFileInfo: {
|
||||
marginRight: '18rem',
|
||||
marginTop: '0.2rem',
|
||||
},
|
||||
uploadedFileText: {
|
||||
fontSize: '16px',
|
||||
color: '#1f2d3d',
|
||||
},
|
||||
customLabel: {
|
||||
fontWeight: 600,
|
||||
fontSize: '14px',
|
||||
color: '#212529',
|
||||
},
|
||||
responsiveImageStyle: {
|
||||
width: '100%',
|
||||
maxHeight: '250px',
|
||||
objectFit: 'cover',
|
||||
marginTop: '20px',
|
||||
},
|
||||
responsiveResultContainer: {
|
||||
padding: '1rem',
|
||||
border: '1px solid #ccc',
|
||||
borderRadius: '8px',
|
||||
marginTop: '20px',
|
||||
},
|
||||
responsiveImageContainer: {
|
||||
marginTop: '20px',
|
||||
textAlign: 'center',
|
||||
},
|
||||
responsiveSubmitButton: {
|
||||
marginTop: '1rem',
|
||||
},
|
||||
responsiveLoadingOverlay: {
|
||||
position: 'absolute',
|
||||
top: '0',
|
||||
left: '0',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
zIndex: '10',
|
||||
},
|
||||
responsiveSpinner: {
|
||||
border: '4px solid #f3f3f3',
|
||||
borderTop: '4px solid #3498db',
|
||||
borderRadius: '50%',
|
||||
width: '50px',
|
||||
height: '50px',
|
||||
animation: 'spin 2s linear infinite',
|
||||
},
|
||||
responsiveLoadingText: {
|
||||
color: 'white',
|
||||
marginTop: '10px',
|
||||
},
|
||||
errorText: {
|
||||
color: '#dc3545',
|
||||
fontSize: '12px',
|
||||
marginTop: '5px',
|
||||
display: 'block'
|
||||
}
|
||||
};
|
||||
|
||||
if (!isServer) {
|
||||
return (
|
||||
<div style={{ textAlign: 'center', marginTop: '50px' }}>
|
||||
<img
|
||||
src={ServerDownAnimation}
|
||||
alt="Server Down Animation"
|
||||
style={{ width: '18rem', height: '18rem', marginBottom: '20px' }}
|
||||
/>
|
||||
<h2 style={{ color: 'red' }}>Server tidak dapat diakses</h2>
|
||||
<p>{errorMessage || 'Silakan periksa koneksi internet Anda atau coba lagi nanti.'}</p>
|
||||
<button
|
||||
onClick={() => window.location.reload()}
|
||||
style={{
|
||||
padding: '10px 20px',
|
||||
backgroundColor: '#0542cc',
|
||||
color: '#fff',
|
||||
border: 'none',
|
||||
borderRadius: '5px',
|
||||
cursor: 'pointer'
|
||||
}}>
|
||||
Coba Lagi
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<style>
|
||||
@ -769,56 +549,56 @@ const Enroll = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='col-md-6'>
|
||||
<div className="row form-group mt-4">
|
||||
<CustomLabel htmlFor="uploadPhoto" style={styles.customLabel}>
|
||||
{/* Image Upload */}
|
||||
<div className="col-md-6">
|
||||
<div className="form-group mt-4">
|
||||
<CustomLabel htmlFor="imageInput" style={styles.customLabel}>
|
||||
Upload Face Photo
|
||||
</CustomLabel>
|
||||
<FileUploader
|
||||
handleChange={handleImageUpload}
|
||||
name="file"
|
||||
types={fileTypes}
|
||||
multiple={false}
|
||||
onTypeError={(err) => {
|
||||
setImageError('Only JPG/JPEG files are allowed');
|
||||
setFile(null);
|
||||
setSelectedImageName('');
|
||||
}}
|
||||
onDrop={(files) => {
|
||||
if (files && files[0]) {
|
||||
handleImageUpload(files[0]);
|
||||
}
|
||||
}}
|
||||
children={
|
||||
<div style={isMobile ? styles.uploadAreaMobile : styles.uploadArea}>
|
||||
<i className="fas fa-cloud-upload-alt" style={styles.uploadIcon}></i>
|
||||
<p style={styles.uploadText}>Drag and Drop Here</p>
|
||||
<p>Or</p>
|
||||
<a href="#" onClick={() => fileInputRef.current.click()}>Browse</a>
|
||||
<p className="text-muted">Recommended size: 300x300 (Max File Size: 2MB)</p>
|
||||
<p className="text-muted">Supported file types: JPG, JPEG</p>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<div style={styles.uploadWrapper}>
|
||||
{/* Drag and Drop File Input */}
|
||||
<div
|
||||
style={styles.uploadArea}
|
||||
onDrop={(e) => {
|
||||
e.preventDefault();
|
||||
handleFileDrop(e.dataTransfer.files);
|
||||
}}
|
||||
onDragOver={(e) => e.preventDefault()}
|
||||
>
|
||||
<FontAwesomeIcon icon={faCloudUploadAlt} style={styles.uploadIcon} />
|
||||
<p style={styles.uploadText}>Drag and Drop Here</p>
|
||||
<p>Or</p>
|
||||
<a href="#" onClick={() => fileInputRef.current.click()} style={styles.browseLink}>Browse</a>
|
||||
<p className="text-muted" style={styles.uploadText}>Recommended size: 320x200 (Max File Size: 2MB)</p>
|
||||
<p className="text-muted" style={styles.uploadText}>Supported file types: JPG, JPEG</p>
|
||||
</div>
|
||||
|
||||
{imageError && (
|
||||
<small className="text-danger mt-2" style={{ fontSize: '12px' }}>
|
||||
{imageError}
|
||||
</small>
|
||||
)}
|
||||
{/* File Input */}
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
id="imageInput"
|
||||
className="form-control"
|
||||
style={{ display: 'none' }}
|
||||
accept="image/jpeg, image/jpg"
|
||||
onChange={(e) => handleImageUpload(e.target.files[0])}
|
||||
/>
|
||||
|
||||
{selectedImageName && (
|
||||
<div className="mt-3">
|
||||
<p><strong>File:</strong> {selectedImageName}</p>
|
||||
<button className="btn btn-danger" onClick={handleImageCancel}>
|
||||
<FontAwesomeIcon icon={faTimes} className="me-2" />Cancel
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{/* Display validation errors */}
|
||||
{validationErrors.file && <p style={styles.errorText}>{validationErrors.file}</p>}
|
||||
{imageError && <p style={styles.errorText}>{imageError}</p>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{selectedImageName && (
|
||||
<div className="mt-3">
|
||||
<p><strong>File:</strong> {selectedImageName}</p>
|
||||
<button className="btn btn-danger" onClick={handleImageCancel}>
|
||||
<FontAwesomeIcon icon={faTimes} className="me-2" />Cancel
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{errorMessage && <small style={styles.uploadError}>{errorMessage}</small>}
|
||||
|
||||
<div style={styles.submitButton}>
|
||||
<button onClick={handleEnrollClick} className="btn d-flex justify-content-center align-items-center me-2" style={{ backgroundColor: '#0542CC' }}>
|
||||
<p className="text-white mb-0">Enroll Now</p>
|
||||
@ -861,6 +641,3 @@ const Enroll = () => {
|
||||
}
|
||||
|
||||
export default Enroll;
|
||||
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user