From 2a559da0d3f173ffb5e45f380c09f32e3afbcc9f Mon Sep 17 00:00:00 2001 From: Rizqika Date: Thu, 5 Dec 2024 17:59:06 +0700 Subject: [PATCH] Fixing Issue --- src/components/Sidebar/dataMenu.js | 448 ++++++++---------- .../FaceRecognition/Section/Compare.jsx | 208 ++++++-- .../FaceRecognition/Section/Enroll.jsx | 126 +++-- .../FaceRecognition/Section/Search.jsx | 223 ++++++--- .../FaceRecognition/Section/Verify.jsx | 239 +++++----- src/screens/Biometric/OcrKtp/Verify.jsx | 134 +++++- src/screens/Biometric/OcrNpwp/Verify.jsx | 75 ++- 7 files changed, 905 insertions(+), 548 deletions(-) diff --git a/src/components/Sidebar/dataMenu.js b/src/components/Sidebar/dataMenu.js index 19332cd..b786a10 100644 --- a/src/components/Sidebar/dataMenu.js +++ b/src/components/Sidebar/dataMenu.js @@ -1,263 +1,193 @@ // src/components/dataMenu.js -const dataMenu = [ - { - items: [ - { - name: 'Main Dashboard', // Changed the name - target: 'collapseHome', - subMenus: [ - { - name: 'Getting Started', - link: '/getting-started' - }, - { - name: 'Dashboard Overview', // Changed the name - link: '/dashboard' - }, - { - name: 'Application Settings', // Changed the name - link: '/application' - }, - ], - }, - ], - iconClass: 'fas fa-tachometer-alt', - }, - { - items: [ - { - name: 'Biometric Systems', // Changed the name - target: 'collapseBiometric', - subMenus: [ - { - name: 'Face Recognition System', // Changed the name - target: 'collapseFaceRecog', - subMenus: [ - { name: 'Verify Identity', link: '/face-verify'}, // Changed the name - { name: 'Summary Report', link: '/face-summary'}, // Changed the name - { name: 'Transaction Log', link: '/face-transaction'}, // Changed the name - ], - }, - { - name: 'KTP OCR', // Changed the name - target: 'collapseOcrKtp', - subMenus: [ - { name: 'Verify KTP', link: '/ktp-verify'}, // Changed the name - { name: 'Manage Basic Auth', link: '/ktp-manage'}, - { name: 'Summary of KTPs', link: '/ktp-summary'}, // Changed the name - { name: 'KTP Transaction History', link: '/ktp-transaction'}, // Changed the name - ], - }, - { - name: 'NPWP OCR', // Changed the name - target: 'collapseOcrNpwp', - subMenus: [ - { name: 'Verify NPWP', link: '/npwp-verify'}, // Changed the name - { name: 'NPWP Summary', link: '/npwp-summary'}, // Changed the name - { name: 'NPWP Transaction Log', link: '/npwp-transaction'}, // Changed the name - ], - }, - { - name: 'SIM OCR', // Changed the name - target: 'collapseOcrSim', - subMenus: [ - { name: 'Verify SIM', link: '/sim-verify'}, // Changed the name - { name: 'SIM Summary', link: '/sim-summary'}, // Changed the name - { name: 'SIM Transaction Log', link: '/sim-transaction'}, // Changed the name - ], - }, - { - name: 'Document OCR', // Changed the name - target: 'collapseOcrDocument', - subMenus: [ - { name: 'Verify Document', link: '/document-verify'}, // Changed the name - { name: 'Document Summary', link: '/document-summary'}, // Changed the name - { name: 'Document Transaction History', link: '/document-transaction'}, // Changed the name - ], - }, - ], - }, - ], - iconClass: 'fas fa-user', - }, - { - items: [ - { - name: 'SMS Services', // Changed the name - target: 'collapseSms', - subMenus: [ - { - name: 'SMS Verification', // Changed the name - link: '/sms-verify' - }, - { - name: 'SMS OTP Management', // Changed the name - target: 'collapseSmsOtp', - subMenus: [ - { name: 'Settings', link: '/sms-otp-settings'}, - { name: 'Summary Report', link: '/sms-otp-summary'}, // Changed the name - { name: 'Transaction Log', link: '/sms-otp-transaction'}, // Changed the name - { name: 'Detail View', link: '/sms-otp-detail'}, // Changed the name - ], - }, - { - name: 'SMS Announcements', // Changed the name - target: 'collapseAnnouncement', - subMenus: [ - { name: 'Bulk Message', link: '/sms-announcement-bulk'}, // Changed the name - { name: 'Announcement Summary', link: '/sms-announcement-summary'}, // Changed the name - { name: 'Transaction Logs', link: '/sms-announcement-transaction'}, - ], - }, - { - name: 'Blocked Numbers', // Changed the name - link: '/sms-block' - }, - { - name: 'SMS Anomaly Report', // Changed the name - link: '/sms-anomaly' - }, - ], - }, - ], - iconClass: 'fas fa-phone', - }, - { - items: [ - { - name: 'WhatsApp Communication', // Changed the name - target: 'collapseWa', - subMenus: [ - { - name: 'Verify WhatsApp Account', // Changed the name - link: '/wa-verify' - }, - { - name: 'WhatsApp Management', // Changed the name - target: 'collapseWaManage', - subMenus: [ - { name: 'Register Business Account', link: '/wa-registration'}, // Changed the name - { name: 'WhatsApp Profile Settings', link: '/wa-profile'}, // Changed the name - { name: 'Message Templates', link: '/wa-template'}, // Changed the name - { name: 'Integration Settings', link: '/wa-integration'}, // Changed the name - ], - }, - { - name: 'WhatsApp Activity', // Changed the name - target: 'collapseActivity', - subMenus: [ - { name: 'Settings', link: '/wa-settings'}, // Changed the name - { name: 'Activity Summary', link: '/wa-summary'}, // Changed the name - { name: 'WA Transaction Logs', link: '/wa-transaction'}, - { name: 'Bulk Sending', link: '/wa-bulk'}, // Changed the name - ], - }, - { - name: 'WhatsApp Inbox', // Changed the name - link: '/wa-inbox' - }, - { - name: 'Blocked WhatsApp Numbers', // Changed the name - link: '/wa-block' - }, - ], - }, - ], - iconClass: 'fab fa-whatsapp', - }, - { - items: [ - { - name: 'Identity Verification', // Changed the name - target: 'collapseIdentify', - subMenus: [ - { - name: 'Electronic Certificate Verification', // Changed the name - target: 'collapseElectro', - subMenus: [ - { name: 'Verify Certificate', link: '/identity-electro-verify'}, // Changed the name - { name: 'Electronic Transaction', link: '/identity-electro-transaction'}, - ], - }, - { - name: 'NPWP Verification', // Changed the name - target: 'collapseIdentifyNpwp', - subMenus: [ - { name: 'Npwp Transaction', link: '/identity-npwp-transaction'} - ], - }, - { - name: 'Tax Number Verification', // Changed the name - target: 'collapseTax', - subMenus: [ - { name: 'Verify Tax Number', link: '/identity-tax-verify'}, // Changed the name - { name: 'Tax Transaction', link: '/identity-tax-transaction'} - ], - }, - { - name: 'Income Verification', // Changed the name - target: 'collapseIncome', - subMenus: [ - { name: 'Verify Income', link: '/identity-income-verify'}, // Changed the name - { name: 'Income Transaction', link: '/identity-income-transaction'} - ], - }, - { - name: 'ID Verification', // Changed the name - target: 'collapseIdVerification', - subMenus: [ - { name: 'Verify ID', link: '/identity-id-verify'}, // Changed the name - { name: 'Verify ID Transaction', link: '/identity-id-transaction'} - ], - }, - ], - }, - ], - iconClass: 'fas fa-edit', - }, - { - items: [ - { - name: 'Watchlist Management', // Changed the name - target: 'collapseWatchlist', - subMenus: [ - { - name: 'Watchlist Screening', // Changed the name - target: 'collapseScreening', - subMenus: [ - { name: 'Verify Watchlist', link: '/watchlist-screening-verify'}, // Changed the name - { name: 'Admin Settings', link: '/watchlist-screening-admin'}, - { name: 'Search Watchlist', link: '/watchlist-screening-search'}, // Changed the name - { name: 'Transaction Logs', link: '/watchlist-screening-transaction'}, - { name: 'Monitor Watchlist', link: '/watchlist-screening-monitor'}, - ], - }, - ], - }, - ], - iconClass: 'fas fa-calendar', - }, - { - items: [ - { - name: 'File Management', // Changed the name - target: 'collapseFiles', - subMenus: [ - { - name: 'File Screening', // Changed the name - target: 'collapseScreening', - subMenus: [ - { name: 'Verify File', link: '/files-screening-verify'}, // Changed the name - { name: 'Search Files', link: '/files-screening-search'}, // Changed the name - { name: 'File Management Settings', link: '/files-screening-admin'}, - ], - }, - ], - }, - ], - iconClass: 'fas fa-cogs', - } +const createMenuItem = (name, link) => ({ name, link }); +const createSubMenu = (name, target, subMenus) => ({ name, target, subMenus }); + +const mainDashboardMenus = [ + createMenuItem('Getting Started', '/getting-started'), + createMenuItem('Dashboard Overview', '/dashboard'), + createMenuItem('Application Settings', '/application') ]; -export default dataMenu; +const biometricSubMenus = { + faceRecognition: [ + createMenuItem('Verify Identity', '/face-verify'), + createMenuItem('Summary Report', '/face-summary'), + createMenuItem('Transaction Log', '/face-transaction') + ], + ktpOcr: [ + createMenuItem('Verify KTP', '/ktp-verify'), + createMenuItem('Manage Basic Auth', '/ktp-manage'), + createMenuItem('Summary of KTPs', '/ktp-summary'), + createMenuItem('KTP Transaction History', '/ktp-transaction') + ], + npwpOcr: [ + createMenuItem('Verify NPWP', '/npwp-verify'), + createMenuItem('NPWP Summary', '/npwp-summary'), + createMenuItem('NPWP Transaction Log', '/npwp-transaction') + ], + simOcr: [ + createMenuItem('Verify SIM', '/sim-verify'), + createMenuItem('SIM Summary', '/sim-summary'), + createMenuItem('SIM Transaction Log', '/sim-transaction') + ], + documentOcr: [ + createMenuItem('Verify Document', '/document-verify'), + createMenuItem('Document Summary', '/document-summary'), + createMenuItem('Document Transaction History', '/document-transaction') + ] +}; + +const smsServiceMenus = { + smsOtp: [ + createMenuItem('Settings', '/sms-otp-settings'), + createMenuItem('Summary Report', '/sms-otp-summary'), + createMenuItem('Transaction Log', '/sms-otp-transaction'), + createMenuItem('Detail View', '/sms-otp-detail') + ], + announcement: [ + createMenuItem('Bulk Message', '/sms-announcement-bulk'), + createMenuItem('Announcement Summary', '/sms-announcement-summary'), + createMenuItem('Transaction Logs', '/sms-announcement-transaction') + ] +}; + +const whatsAppMenus = { + management: [ + createMenuItem('Register Business Account', '/wa-registration'), + createMenuItem('WhatsApp Profile Settings', '/wa-profile'), + createMenuItem('Message Templates', '/wa-template'), + createMenuItem('Integration Settings', '/wa-integration') + ], + activity: [ + createMenuItem('Settings', '/wa-settings'), + createMenuItem('Activity Summary', '/wa-summary'), + createMenuItem('WA Transaction Logs', '/wa-transaction'), + createMenuItem('Bulk Sending', '/wa-bulk') + ] +}; + +const identityMenus = { + electronic: [ + createMenuItem('Verify Certificate', '/identity-electro-verify'), + createMenuItem('Electronic Transaction', '/identity-electro-transaction') + ], + npwp: [ + createMenuItem('Npwp Transaction', '/identity-npwp-transaction') + ], + tax: [ + createMenuItem('Verify Tax Number', '/identity-tax-verify'), + createMenuItem('Tax Transaction', '/identity-tax-transaction') + ], + income: [ + createMenuItem('Verify Income', '/identity-income-verify'), + createMenuItem('Income Transaction', '/identity-income-transaction') + ], + id: [ + createMenuItem('Verify ID', '/identity-id-verify'), + createMenuItem('Verify ID Transaction', '/identity-id-transaction') + ] +}; + +const watchlistMenus = { + screening: [ + createMenuItem('Verify Watchlist', '/watchlist-screening-verify'), + createMenuItem('Admin Settings', '/watchlist-screening-admin'), + createMenuItem('Search Watchlist', '/watchlist-screening-search'), + createMenuItem('Transaction Logs', '/watchlist-screening-transaction'), + createMenuItem('Monitor Watchlist', '/watchlist-screening-monitor') + ] +}; + +const fileMenus = { + screening: [ + createMenuItem('Verify File', '/files-screening-verify'), + createMenuItem('Search Files', '/files-screening-search'), + createMenuItem('File Management Settings', '/files-screening-admin') + ] +}; + +const dataMenu = [ + { + items: [createSubMenu('Home', 'collapseHome', mainDashboardMenus)], + iconClass: 'fas fa-tachometer-alt' + }, + { + items: [{ + name: 'Biometric', + target: 'collapseBiometric', + subMenus: [ + createSubMenu('Face Recognition System', 'collapseFaceRecog', biometricSubMenus.faceRecognition), + createSubMenu('KTP OCR', 'collapseOcrKtp', biometricSubMenus.ktpOcr), + createSubMenu('NPWP OCR', 'collapseOcrNpwp', biometricSubMenus.npwpOcr), + createSubMenu('SIM OCR', 'collapseOcrSim', biometricSubMenus.simOcr), + createSubMenu('Document OCR', 'collapseOcrDocument', biometricSubMenus.documentOcr) + ] + }], + iconClass: 'fas fa-user' + }, + { + items: [{ + name: 'SMS', + target: 'collapseSms', + subMenus: [ + createMenuItem('SMS Verification', '/sms-verify'), + createSubMenu('SMS OTP Management', 'collapseSmsOtp', smsServiceMenus.smsOtp), + createSubMenu('SMS Announcements', 'collapseAnnouncement', smsServiceMenus.announcement), + createMenuItem('Blocked Numbers', '/sms-block'), + createMenuItem('SMS Anomaly Report', '/sms-anomaly') + ] + }], + iconClass: 'fas fa-phone' + }, + { + items: [{ + name: 'WhatsApp', + target: 'collapseWa', + subMenus: [ + createMenuItem('Verify WhatsApp Account', '/wa-verify'), + createSubMenu('WhatsApp Management', 'collapseWaManage', whatsAppMenus.management), + createSubMenu('WhatsApp Activity', 'collapseActivity', whatsAppMenus.activity), + createMenuItem('WhatsApp Inbox', '/wa-inbox'), + createMenuItem('Blocked WhatsApp Numbers', '/wa-block') + ] + }], + iconClass: 'fab fa-whatsapp' + }, + { + items: [{ + name: 'Identity', + target: 'collapseIdentify', + subMenus: [ + createSubMenu('Electronic Certificate Verification', 'collapseElectro', identityMenus.electronic), + createSubMenu('NPWP Verification', 'collapseIdentifyNpwp', identityMenus.npwp), + createSubMenu('Tax Number Verification', 'collapseTax', identityMenus.tax), + createSubMenu('Income Verification', 'collapseIncome', identityMenus.income), + createSubMenu('ID Verification', 'collapseIdVerification', identityMenus.id) + ] + }], + iconClass: 'fas fa-edit' + }, + { + items: [{ + name: 'Watchlist', + target: 'collapseWatchlist', + subMenus: [ + createSubMenu('Watchlist Screening', 'collapseScreening', watchlistMenus.screening) + ] + }], + iconClass: 'fas fa-calendar' + }, + { + items: [{ + name: 'File', + target: 'collapseFiles', + subMenus: [ + createSubMenu('File Screening', 'collapseScreening', fileMenus.screening) + ] + }], + iconClass: 'fas fa-cogs' + } +]; + +export default dataMenu; \ No newline at end of file diff --git a/src/screens/Biometric/FaceRecognition/Section/Compare.jsx b/src/screens/Biometric/FaceRecognition/Section/Compare.jsx index a02d065..0e3c39e 100644 --- a/src/screens/Biometric/FaceRecognition/Section/Compare.jsx +++ b/src/screens/Biometric/FaceRecognition/Section/Compare.jsx @@ -28,7 +28,7 @@ const Compare = () => { const [imageCompareUrl, setImageCompareUrl] = useState(''); const [verified, setVerified] = useState(null); - const fileTypes = ["JPG", "JPEG", "PNG"]; + const fileTypes = ["JPG", "JPEG"]; const [file, setFile] = useState(null); // For the first image const [compareFile, setCompareFile] = useState(null); // For the second imag @@ -113,20 +113,119 @@ const Compare = () => { }; const handleImageUpload = (file) => { - if (file && fileTypes.includes(file.name.split('.').pop().toUpperCase())) { - setSelectedImageName(file.name); - setFile(file); // Store the file directly in state - setUploadError(''); // Clear error if valid + if (!file) { + setUploadError('Please select a file'); + return; } + + // Check file size (2MB = 2 * 1024 * 1024 bytes) + const maxSize = 2 * 1024 * 1024; + if (file.size > maxSize) { + setUploadError('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)) { + setUploadError('Only JPG/JPEG files are allowed'); + setFile(null); + setSelectedImageName(''); + return; + } + + // Check image dimensions + const img = new Image(); + const objectUrl = URL.createObjectURL(file); + + img.onload = () => { + URL.revokeObjectURL(objectUrl); + + if (img.width > 300 || img.height > 300) { + setUploadError('Image dimensions must not exceed 300x300 pixels'); + setFile(null); + setSelectedImageName(''); + return; + } + + // All validations passed + setSelectedImageName(file.name); + setFile(file); + setUploadError(''); + }; + + img.onerror = () => { + URL.revokeObjectURL(objectUrl); + setUploadError('Invalid image file'); + setFile(null); + setSelectedImageName(''); + }; + + img.src = objectUrl; }; const handleCompareImageUpload = (file) => { - if (file && fileTypes.includes(file.name.split('.').pop().toUpperCase())) { - setSelectedCompareImageName(file.name); - setCompareFile(file); // Store the compare file directly in state - setCompareUploadError(''); // Clear error if valid + if (!file) { + setCompareUploadError('Please select a file'); + return; } - }; + + // Check file size (2MB = 2 * 1024 * 1024 bytes) + const maxSize = 2 * 1024 * 1024; + if (file.size > maxSize) { + setCompareUploadError('File size exceeds 2MB limit'); + setCompareFile(null); + setSelectedCompareImageName(''); + 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)) { + setCompareUploadError('Only JPG/JPEG files are allowed'); + setCompareFile(null); + setSelectedCompareImageName(''); + return; + } + + // Check image dimensions + const img = new Image(); + const objectUrl = URL.createObjectURL(file); + + img.onload = () => { + URL.revokeObjectURL(objectUrl); + + if (img.width > 300 || img.height > 300) { + setCompareUploadError('Image dimensions must not exceed 300x300 pixels'); + setCompareFile(null); + setSelectedCompareImageName(''); + return; + } + + // All validations passed + setSelectedCompareImageName(file.name); + setCompareFile(file); + setCompareUploadError(''); + }; + + img.onerror = () => { + URL.revokeObjectURL(objectUrl); + setCompareUploadError('Invalid image file'); + setCompareFile(null); + setSelectedCompareImageName(''); + }; + + img.src = objectUrl; + }; + const handleImageCancel = () => { setSelectedImageName(''); @@ -289,23 +388,23 @@ const Compare = () => { -
+
- Original Foto -

File Name: {resultImageLabel}

+

File Name: {resultImageLabel}

-
- Compare Foto + Compare Foto -

File Name: {resultCompareImageLabel}

+

File Name: {resultCompareImageLabel}

@@ -445,18 +544,27 @@ const Compare = () => { name="file" types={fileTypes} multiple={false} + onTypeError={(err) => { + setUploadError('Only JPG/JPEG files are allowed'); + setFile(null); + setSelectedImageName(''); + }} children={

Drag and Drop Here

Or

Browse -

Recommended size: 250x250 (Max File Size: 2MB)

+

Recommended size: 300x300 (Max File Size: 2MB)

Supported file types: JPG, JPEG

} /> - {uploadError && {uploadError}} + {uploadError && ( + + {uploadError} + + )} {/* Display uploaded image name */} @@ -485,18 +593,27 @@ const Compare = () => { name="file" types={fileTypes} multiple={false} + onTypeError={(err) => { + setCompareUploadError('Only JPG/JPEG files are allowed'); + setCompareFile(null); + setSelectedCompareImageName(''); + }} children={

Drag and Drop Here

Or

Browse -

Recommended size: 250x250 (Max File Size: 2MB)

+

Recommended size: 300x300 (Max File Size: 2MB)

Supported file types: JPG, JPEG

} /> - {compareUploadError && {compareUploadError}} + {compareUploadError && ( + + {compareUploadError} + + )} {/* Display uploaded image name */} @@ -678,25 +795,42 @@ const styles = { borderCollapse: 'collapse', marginBottom: '20px', }, + imagesWrapper: { + display: 'flex', + flexWrap: 'wrap', + justifyContent: 'center', + gap: '2rem', + width: '100%', + margin: '2rem 0', + }, imageContainer: { + flex: '1 1 300px', + maxWidth: '500px', + minWidth: '280px', display: 'flex', flexDirection: 'column', alignItems: 'center', - flex: 1, - padding: '10px', }, - imageCompareContainer: { - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - flex: 1, - padding: '10px', - }, - imageStyle: { + + responsiveImage: { width: '100%', height: 'auto', - maxWidth: '150px', // Limit image width + maxHeight: '500px', + objectFit: 'contain', borderRadius: '8px', + boxShadow: '0 2px 4px rgba(0,0,0,0.1)', + transition: 'transform 0.3s ease', + cursor: 'pointer', + '&:hover': { + transform: 'scale(1.02)', + }, + }, + + imageLabel: { + marginTop: '1rem', + textAlign: 'center', + fontSize: '0.9rem', + color: '#666', }, similarityText: (verified) => ({ border: '0.1px solid gray', diff --git a/src/screens/Biometric/FaceRecognition/Section/Enroll.jsx b/src/screens/Biometric/FaceRecognition/Section/Enroll.jsx index e94a6e1..dd0ec6a 100644 --- a/src/screens/Biometric/FaceRecognition/Section/Enroll.jsx +++ b/src/screens/Biometric/FaceRecognition/Section/Enroll.jsx @@ -10,7 +10,7 @@ const Enroll = () => { const BASE_URL = process.env.REACT_APP_BASE_URL const API_KEY = process.env.REACT_APP_API_KEY - const fileTypes = ["JPG", "JPEG", "PNG"]; + const fileTypes = ["JPG", "JPEG"]; const [file, setFile] = useState(null); const [errorMessage, setErrorMessage] = useState(''); @@ -137,24 +137,63 @@ const Enroll = () => { }; const handleImageUpload = (file) => { - // Ensure the file is not undefined or null before accessing its properties - if (file && file.name) { - const fileExtension = file.name.split('.').pop().toUpperCase(); - if (fileTypes.includes(fileExtension)) { - setSelectedImageName(file.name); - setFile(file); - setImageError(''); // Clear any previous errors - } else { - alert('Image format is not supported'); - setImageError('Image format is not supported'); - setFile(null); - } - } else { - console.error('No file selected or invalid file object.'); - } + if (!file) { + setImageError('Please select a file'); + return; + } + + // Check file size (2MB = 2 * 1024 * 1024 bytes) + const maxSize = 2 * 1024 * 1024; + 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 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); + setImageError(''); + }; + + img.onerror = () => { + URL.revokeObjectURL(objectUrl); + setImageError('Invalid image file'); + setFile(null); + setSelectedImageName(''); + }; + + img.src = objectUrl; }; + - const handleImageCancel = () => { setSelectedImageName(''); setFile(null); @@ -336,17 +375,6 @@ const Enroll = () => { } }; - // Fungsi untuk mengonversi ukuran file dari byte ke KB/MB - const formatFileSize = (sizeInBytes) => { - if (sizeInBytes < 1024) { - return `${sizeInBytes} bytes`; // Jika ukuran lebih kecil dari 1 KB - } else if (sizeInBytes < 1048576) { - return `${(sizeInBytes / 1024).toFixed(2)} KB`; // Jika ukuran lebih kecil dari 1 MB - } else { - return `${(sizeInBytes / 1048576).toFixed(2)} MB`; // Jika ukuran lebih besar dari 1 MB - } - }; - const styles = { formGroup: { marginTop: '-45px', @@ -633,6 +661,12 @@ const Enroll = () => { color: 'white', marginTop: '10px', }, + errorText: { + color: '#dc3545', + fontSize: '12px', + marginTop: '5px', + display: 'block' + } }; if (!isServer) { @@ -745,6 +779,11 @@ const Enroll = () => { 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]); @@ -761,26 +800,18 @@ const Enroll = () => { } /> - handleImageUpload(e.target.files[0])} - /> - {imageError && {imageError}} + + {imageError && ( + + {imageError} + + )} - + {selectedImageName && (

File: {selectedImageName}

- {file && ( -

- Size: {formatFileSize(file.size)} -

- )} @@ -810,10 +841,15 @@ const Enroll = () => { Contoh Foto

- {resultImageLabel} {/* Display resultImageLabel instead of selectedImageName */} + {resultImageLabel}

diff --git a/src/screens/Biometric/FaceRecognition/Section/Search.jsx b/src/screens/Biometric/FaceRecognition/Section/Search.jsx index 60ab6a2..da520ec 100644 --- a/src/screens/Biometric/FaceRecognition/Section/Search.jsx +++ b/src/screens/Biometric/FaceRecognition/Section/Search.jsx @@ -5,7 +5,7 @@ import { FileUploader } from 'react-drag-drop-files'; import Select from 'react-select' import { ServerDownAnimation } from '../../../../assets/images'; -const fileTypes = ["JPG", "JPEG", "PNG"]; // Allowed file types +const fileTypes = ["JPG", "JPEG"]; // Allowed file types const Search = () => { @@ -126,34 +126,83 @@ const Search = () => { setIsSelectOpen(false); }; + // Add this after your existing state declarations const handleImageUpload = (file) => { - // Ensure the file is not undefined or null before accessing its properties - if (file && file.name) { - const fileExtension = file.name.split('.').pop().toUpperCase(); - if (fileTypes.includes(fileExtension)) { + if (!file) { + setImageError('Please select a file'); + return; + } + + // Store the uploaded file + setUploadedFile(file); + + // Check file size (2MB = 2 * 1024 * 1024 bytes) + const maxSize = 2 * 1024 * 1024; + if (file.size > maxSize) { + setImageError('File size exceeds 2MB limit'); + setFile(null); + setSelectedImageName(''); + setUploadedFile(null); + 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(''); + setUploadedFile(null); + return; + } + + // Create preview URL for the uploaded image + const previewUrl = URL.createObjectURL(file); + + // Check image dimensions + const img = new Image(); + img.onload = () => { + URL.revokeObjectURL(previewUrl); + + if (img.width > 300 || img.height > 300) { + setImageError('Image dimensions must not exceed 300x300 pixels'); + setFile(null); + setSelectedImageName(''); + setUploadedFile(null); + return; + } + + // All validations passed setSelectedImageName(file.name); setFile(file); - setUploadedFile(file); // Set uploadedFile to the selected file - setImageError(''); // Clear any previous errors - } else { - alert('Image format is not supported'); - setImageError('Image format is not supported'); + setImageError(''); + }; + + img.onerror = () => { + URL.revokeObjectURL(previewUrl); + setImageError('Invalid image file'); setFile(null); - setUploadedFile(null); // Clear uploadedFile if the file is unsupported - } - } else { - console.error('No file selected or invalid file object.'); - } + setSelectedImageName(''); + setUploadedFile(null); + }; + + img.src = previewUrl; }; + // Update the handleImageCancel function const handleImageCancel = () => { setSelectedImageName(''); setFile(null); + setUploadedFile(null); if (fileInputRef.current) { fileInputRef.current.value = ''; } }; + const handleCheckClick = async () => { // Clear existing errors setApplicationIdError(''); @@ -355,17 +404,31 @@ const Search = () => { fontSize: '20px', }, uploadArea: { + backgroundColor: '#e6f2ff', + height: '250px', + cursor: 'pointer', + marginTop: '1rem', + paddingTop: '22px', + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + border: '1px solid #ced4da', + borderRadius: '0.25rem', + }, + uploadAreaMobile: { backgroundColor: '#e6f2ff', - height: '40svh', + height: '50svh', // Use viewport height for a more responsive size cursor: 'pointer', marginTop: '1rem', - padding: '1rem', + paddingTop: '18px', display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', border: '1px solid #ced4da', borderRadius: '0.25rem', + padding: '20px', }, uploadIcon: { fontSize: '40px', @@ -377,7 +440,7 @@ const Search = () => { fontWeight: '400', fontSize: '16px', lineHeight: '13px', - }, + }, wrapper: { border: '1px solid #ddd', borderRadius: '6px', @@ -479,14 +542,37 @@ const Search = () => { gridTemplateColumns: isMobile ? 'repeat(2, 1fr)' : 'repeat(5, 1fr)', // Adjust based on isMobile gap: '16px', }, - resultItem: { - border: '1px solid #ddd', + imageWrapper: { + width: '100%', + height: '200px', + overflow: 'hidden', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + backgroundColor: '#f8f9fa', borderRadius: '8px', - padding: '12px', - boxShadow: '0 2px 5px rgba(0, 0, 0, 0.1)', - textAlign: 'center', - transition: 'transform 0.3s ease', // Added transition for hover effect - cursor: 'pointer', + }, + + responsiveImage: { + maxWidth: '100%', + maxHeight: '100%', + width: 'auto', + height: 'auto', + objectFit: 'contain', + transition: 'transform 0.3s ease', + '&:hover': { + transform: 'scale(1.05)', + }, + }, + resultItem: { + padding: '15px', + backgroundColor: '#ffffff', + borderRadius: '10px', + boxShadow: '0 2px 4px rgba(0,0,0,0.1)', + transition: 'transform 0.3s ease', + '&:hover': { + transform: 'translateY(-5px)', + }, }, resultItemHover: { transform: 'scale(1.05)', // Slightly enlarge the card on hover @@ -556,10 +642,10 @@ const Search = () => { {isLoading && ( -
-
-

Loading...

-
+
+
+

Loading...

+
)} {/* Application ID Selection */} @@ -627,38 +713,43 @@ const Search = () => { {/* Upload Section */} - {/* Drag and Drop File Uploader */}
Upload Face Photo handleImageUpload(files[0])} - children={ -
- -

Drag and Drop Here

-

Or

- fileInputRef.current.click()}>Browse -

Recommended size: 300x300 (Max File Size: 2MB)

-

Supported file types: JPG, JPEG

-
- } + 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={ +
+ +

Drag and Drop Here

+

Or

+ fileInputRef.current.click()}>Browse +

Recommended size: 300x300 (Max File Size: 2MB)

+

Supported file types: JPG, JPEG

+
+ } /> - handleImageUpload(e.target.files[0])} - /> - {imageError && {imageError}} + + {imageError && ( + + {imageError} + + )}
@@ -679,15 +770,14 @@ const Search = () => { {errorMessage && {errorMessage}} {/* Submit Button */} -
- -
+
+ +
{/* Results Section */} - { - showResult && results.length > 0 && ( + {showResult && results.length > 0 && (

Results

@@ -698,13 +788,18 @@ const Search = () => {

Similarity: {result.similarity}%

Distance: {result.distance}

- {`Result +
+ {`Result +
))} - ) - } + )} ); diff --git a/src/screens/Biometric/FaceRecognition/Section/Verify.jsx b/src/screens/Biometric/FaceRecognition/Section/Verify.jsx index af005dd..9189fe3 100644 --- a/src/screens/Biometric/FaceRecognition/Section/Verify.jsx +++ b/src/screens/Biometric/FaceRecognition/Section/Verify.jsx @@ -3,12 +3,13 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faChevronDown, faTimes } from '@fortawesome/free-solid-svg-icons'; import Select from 'react-select' import { ServerDownAnimation } from '../../../../assets/images'; +import { FileUploader } from 'react-drag-drop-files'; const Verify = () => { const BASE_URL = process.env.REACT_APP_BASE_URL; const API_KEY = process.env.REACT_APP_API_KEY; - const fileTypes = ["JPG", "JPEG", "PNG"]; + const fileTypes = ["JPG", "JPEG"]; const [file, setFile] = useState(null); const [errorMessage, setErrorMessage] = useState(''); @@ -18,7 +19,7 @@ const Verify = () => { const [thresholdError, setThresholdError] = useState(''); const [selectedImageName, setSelectedImageName] = useState(''); const [resultImageLabel, setResultImageLabel] = useState(''); - const fileInputRef = useRef(null); + const inputRef = useRef(null); const [showResult, setShowResult] = useState(false); const [applicationId, setApplicationId] = useState(''); const [thresholdId, setThresholdId] = useState(''); @@ -33,6 +34,7 @@ const Verify = () => { const [inputValueApplication, setInputValueApplication] = useState(''); // Controlled input value for Application ID const [isMobile, setIsMobile] = useState(window.innerWidth <= 768); const [isServer, setIsServer] = useState(true); + const [imageError, setImageError] = useState(''); const thresholdIds = [ { id: 1, name: 'cosine', displayName: 'Basic' }, @@ -135,28 +137,68 @@ const Verify = () => { const handleImageUpload = (file) => { - if (file && file.name) { - const fileExtension = file.name.split('.').pop().toUpperCase(); - if (fileTypes.includes(fileExtension)) { - setSelectedImageName(file.name); - setFile(file); - setUploadError(''); - } else { - alert('Image format is not supported'); - setUploadError('Image format is not supported'); - setFile(null); - } - } else { - console.error('No file selected or invalid file object.'); + if (!file) { + setImageError('Please select a file'); + return; } - }; + + // Check file size (2MB = 2 * 1024 * 1024 bytes) + const maxSize = 2 * 1024 * 1024; + 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 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); + setImageError(''); + }; + + img.onerror = () => { + URL.revokeObjectURL(objectUrl); + setImageError('Invalid image file'); + setFile(null); + setSelectedImageName(''); + }; + + img.src = objectUrl; + }; const handleImageCancel = () => { setSelectedImageName(''); setFile(null); - if (fileInputRef.current) { - fileInputRef.current.value = ''; + if (inputRef.current) { + inputRef.current.value = ''; } }; @@ -308,17 +350,6 @@ const Verify = () => { } }; - // Fungsi untuk mengonversi ukuran file dari byte ke KB/MB - const formatFileSize = (sizeInBytes) => { - if (sizeInBytes < 1024) { - return `${sizeInBytes} bytes`; // Jika ukuran lebih kecil dari 1 KB - } else if (sizeInBytes < 1048576) { - return `${(sizeInBytes / 1024).toFixed(2)} KB`; // Jika ukuran lebih kecil dari 1 MB - } else { - return `${(sizeInBytes / 1048576).toFixed(2)} MB`; // Jika ukuran lebih besar dari 1 MB - } - }; - if (!isServer) { return (
@@ -734,60 +765,50 @@ const Verify = () => {
{/* Upload Section */} -
+
- - Upload Face Photo - -
-
{ - e.preventDefault(); - // Handle drop event - const file = e.dataTransfer.files[0]; - handleImageUpload(file); + + Upload Face Photo + + { + setImageError('Only JPG/JPEG files are allowed'); + setFile(null); + setSelectedImageName(''); }} - onDragOver={(e) => e.preventDefault()} - > - -

Drag and Drop Here

-

Or

- { - e.preventDefault(); - // Only open file input if no file is selected - if (!fileInputRef.current.files.length) { - fileInputRef.current.click(); + onDrop={(files) => { + if (files && files[0]) { + handleImageUpload(files[0]); } - }} - style={styles.browseLink} - > - Browse - -

- Recommended size: 300x300 (Max File Size: 2MB) -

-

- Supported file types: JPG, JPEG -

-
- - {/* File Input */} - handleImageUpload(e.target.files[0])} + }} + children={ +
+ +

Drag and Drop Here

+

Or

+ + Browse + +

Recommended size: 300x300 (Max File Size: 2MB)

+

Supported file types: JPG, JPEG

+
+ } /> - - {/* Error Message */} - {uploadError && {uploadError}} -
+ {imageError && ( + + {imageError} + + )}
@@ -795,11 +816,6 @@ const Verify = () => { {selectedImageName && (

File: {selectedImageName}

- {file && ( -

- Size: {formatFileSize(file.size)} -

- )} @@ -817,33 +833,40 @@ const Verify = () => { {/* Results Section */} {showResult && ( -
-

Results

-
- - - - - - - -
Similarity - {verified !== null ? (verified ? 'True' : 'False') : 'N/A'} -
+
+

Results

+
+ + + + + + + +
Similarity + {verified !== null ? (verified ? 'True' : 'False') : 'N/A'} +
-
- Example Image -

- File Name: {resultImageLabel} {/* Display the resultImageLabel here */} -

-
+
+ Example Image +

+ File Name: {resultImageLabel} +

+
+
-
)} +
); diff --git a/src/screens/Biometric/OcrKtp/Verify.jsx b/src/screens/Biometric/OcrKtp/Verify.jsx index 90850eb..43431ef 100644 --- a/src/screens/Biometric/OcrKtp/Verify.jsx +++ b/src/screens/Biometric/OcrKtp/Verify.jsx @@ -12,7 +12,7 @@ const CustomLabel = ({ overRide, children, ...props }) => { const Verify = () => { const BASE_URL = process.env.REACT_APP_BASE_URL; const API_KEY = process.env.REACT_APP_API_KEY; - const fileTypes = ["image/jpeg", "image/png"]; + const fileTypes = ["image/jpeg", "image/jpg"]; const fileInputRef = useRef(null); const [isMobile, setIsMobile] = useState(false); @@ -119,19 +119,63 @@ const Verify = () => { } }; - const handleImageUpload = (file) => { + const checkImageDimensions = (file) => { + return new Promise((resolve, reject) => { + const img = new Image(); + img.src = URL.createObjectURL(file); + + img.onload = () => { + URL.revokeObjectURL(img.src); + if (img.width > 300 || img.height > 300) { + reject('Image dimensions must not exceed 300x300 pixels'); + } else { + resolve(true); + } + }; + + img.onerror = () => { + URL.revokeObjectURL(img.src); + reject('Failed to load image'); + }; + }); + }; + + // Update handleImageUpload to include dimension checking + const handleImageUpload = async (file) => { setFile(file); setSelectedImageName(file.name); - - // Validate file type - if (!fileTypes.includes(file.type)) { - setImageError('Invalid file type. Only JPG, JPEG, and PNG are allowed.'); - } else if (file.size > 2 * 1024 * 1024) { // Max 2MB - setImageError('File size exceeds 2MB.'); - } else { + + try { + // Check if file is PNG + if (file.type === 'image/png') { + setImageError('The image format is not suitable. Only JPG and JPEG files are allowed.'); + setFile(null); + setSelectedImageName(''); + return; + } + + // Validate file type + if (!fileTypes.includes(file.type)) { + setImageError('Invalid file type. Only JPG and JPEG are allowed.'); + return; + } + + // Validate file size + if (file.size > 2 * 1024 * 1024) { + setImageError('File size exceeds 2MB.'); + return; + } + + // Validate image dimensions + await checkImageDimensions(file); setImageError(''); + + } catch (error) { + setImageError(error); + setFile(null); + setSelectedImageName(''); } - }; + }; const handleImageCancel = () => { setFile(null); @@ -161,6 +205,33 @@ const Verify = () => { return Object.values(errors).every(error => error === ''); }; + const handleApiError = (response) => { + // Handle 400 Bad Request + if (response.status_code === 400) { + console.error('❌ Bad Request:', { + status: response.status_code, + detail: response.detail || 'Mohon Upload KTP' + }); + return response.detail || 'Mohon Upload KTP'; + } + + // Handle 500 Internal Server Error + if (response.status_code >= 500) { + console.error('🔥 Server Error:', { + status: response.status_code, + message: 'Internal Server Error' + }); + return 'Internal Server Error'; + } + + // Default error message + console.error('⚠️ Unknown Error:', { + status: response.status_code, + response + }); + return 'Terjadi kesalahan. Silakan coba lagi.'; + }; + // Submit form and trigger OCR API const handleCheckClick = async () => { if (!validateForm()) { @@ -183,16 +254,18 @@ const Verify = () => { }); if (!response.ok) { - throw new Error('OCR processing failed'); + throw new Error('OCR processing failed, Please check your input, Please check your input'); } const result = await response.json(); - + console.log('📡 API Response:', result); + // Log the full result to verify structure console.log('OCR API Response:', result); if (result.status_code === 201) { const responseData = result.details.data?.['data-ktp'] || {}; + console.log('✅ OCR Success:', result); const updateQuota = result.details.data.quota const data = { @@ -230,10 +303,22 @@ const Verify = () => { await fetchImage(imageFileName); // Call the fetchImage function to fetch the image } } else { - setErrorMessage('OCR processing failed'); + const errorMsg = handleApiError(result); + setErrorMessage(errorMsg); + setShowResult(false); + console.error('❌ OCR Failed:', { + error: errorMsg, + response: result + }); } } catch (error) { - setErrorMessage(error.message || 'Error during OCR processing'); + const errorMsg = 'Internal Server Error'; + setErrorMessage(errorMsg); + setShowResult(false); + console.error('🔥 Request Failed:', { + error: error.message, + detail: error + }); } finally { setIsLoading(false); } @@ -350,9 +435,10 @@ const Verify = () => { borderRadius: '4px', }, errorText: { - color: '#721c24', - fontSize: '14px', - margin: '0', + color: '#dc3545', // Bootstrap danger color + fontSize: '12px', // Small text + marginTop: '5px', + fontWeight: '400' }, loadingOverlay: { position: 'fixed', // Gunakan fixed untuk overlay penuh layar @@ -501,8 +587,9 @@ const Verify = () => { Upload Image (KTP) +
- {/* Drag and Drop File Input */} + {/* Existing drag & drop area */}
{ @@ -516,20 +603,21 @@ const Verify = () => {

Or

fileInputRef.current.click()} style={styles.browseLink}>Browse

Recommended size: 300x300 (Max File Size: 2MB)

-

Supported file types: JPG, JPEG, PNG

+

Supported file types: JPG, JPEG

- {/* File Input */} + {/* File input */} handleImageUpload(e.target.files[0])} /> + {/* Display selected file info */} {selectedImageName && (

File: {selectedImageName}

@@ -543,8 +631,12 @@ const Verify = () => {
)} + + {/* Display validation errors */} {validationErrors.file &&

{validationErrors.file}

} + {imageError &&

{imageError}

}
+
diff --git a/src/screens/Biometric/OcrNpwp/Verify.jsx b/src/screens/Biometric/OcrNpwp/Verify.jsx index 5fbb1a5..c9aff44 100644 --- a/src/screens/Biometric/OcrNpwp/Verify.jsx +++ b/src/screens/Biometric/OcrNpwp/Verify.jsx @@ -12,7 +12,7 @@ const CustomLabel = ({ overRide, children, ...props }) => { const Verify = () => { const BASE_URL = process.env.REACT_APP_BASE_URL; const API_KEY = process.env.REACT_APP_API_KEY; - const fileTypes = ["image/jpeg", "image/png"]; + const fileTypes = ["image/jpeg", "image/jpg"]; const fileInputRef = useRef(null); const [isMobile, setIsMobile] = useState(false); @@ -119,19 +119,63 @@ const Verify = () => { } }; - const handleImageUpload = (file) => { + const checkImageDimensions = (file) => { + return new Promise((resolve, reject) => { + const img = new Image(); + img.src = URL.createObjectURL(file); + + img.onload = () => { + URL.revokeObjectURL(img.src); + if (img.width > 300 || img.height > 300) { + reject('Image dimensions must not exceed 300x300 pixels'); + } else { + resolve(true); + } + }; + + img.onerror = () => { + URL.revokeObjectURL(img.src); + reject('Failed to load image'); + }; + }); + }; + + // Update handleImageUpload function + const handleImageUpload = async (file) => { setFile(file); setSelectedImageName(file.name); - - // Validate file type - if (!fileTypes.includes(file.type)) { - setImageError('Invalid file type. Only JPG, JPEG, and PNG are allowed.'); - } else if (file.size > 2 * 1024 * 1024) { // Max 2MB - setImageError('File size exceeds 2MB.'); - } else { + + try { + // Check if file is PNG + if (file.type === 'image/png') { + setImageError('The image format is not suitable. Only JPG and JPEG files are allowed.'); + setFile(null); + setSelectedImageName(''); + return; + } + + // Validate file type + if (!fileTypes.includes(file.type)) { + setImageError('Invalid file type. Only JPG and JPEG are allowed.'); + return; + } + + // Validate file size + if (file.size > 2 * 1024 * 1024) { + setImageError('File size exceeds 2MB.'); + return; + } + + // Validate image dimensions + await checkImageDimensions(file); setImageError(''); + + } catch (error) { + setImageError(error); + setFile(null); + setSelectedImageName(''); } - }; + }; const handleImageCancel = () => { setFile(null); @@ -340,9 +384,10 @@ const Verify = () => { borderRadius: '4px', }, errorText: { - color: '#721c24', - fontSize: '14px', - margin: '0', + color: '#dc3545', + fontSize: '12px', + marginTop: '5px', + fontWeight: '400' },loadingOverlay: { position: 'fixed', // Gunakan fixed untuk overlay penuh layar top: 0, @@ -515,7 +560,7 @@ const Verify = () => { id="imageInput" className="form-control" style={{ display: 'none' }} - accept="image/jpeg, image/png" + accept="image/jpeg, image/jpg" onChange={(e) => handleImageUpload(e.target.files[0])} /> @@ -532,7 +577,9 @@ const Verify = () => { )} + {/* Display validation errors */} {validationErrors.file &&

{validationErrors.file}

} + {imageError &&

{imageError}

}