Fixing Issue

This commit is contained in:
Rizqika 2024-12-05 17:59:06 +07:00
parent 2a2662df25
commit 2a559da0d3
7 changed files with 905 additions and 548 deletions

View File

@ -1,262 +1,192 @@
// src/components/dataMenu.js // src/components/dataMenu.js
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')
];
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 = [ const dataMenu = [
{ {
items: [ items: [createSubMenu('Home', 'collapseHome', mainDashboardMenus)],
{ iconClass: 'fas fa-tachometer-alt'
name: 'Main Dashboard', // Changed the name
target: 'collapseHome',
subMenus: [
{
name: 'Getting Started',
link: '/getting-started'
}, },
{ {
name: 'Dashboard Overview', // Changed the name items: [{
link: '/dashboard' name: 'Biometric',
},
{
name: 'Application Settings', // Changed the name
link: '/application'
},
],
},
],
iconClass: 'fas fa-tachometer-alt',
},
{
items: [
{
name: 'Biometric Systems', // Changed the name
target: 'collapseBiometric', target: 'collapseBiometric',
subMenus: [ subMenus: [
{ createSubMenu('Face Recognition System', 'collapseFaceRecog', biometricSubMenus.faceRecognition),
name: 'Face Recognition System', // Changed the name createSubMenu('KTP OCR', 'collapseOcrKtp', biometricSubMenus.ktpOcr),
target: 'collapseFaceRecog', createSubMenu('NPWP OCR', 'collapseOcrNpwp', biometricSubMenus.npwpOcr),
subMenus: [ createSubMenu('SIM OCR', 'collapseOcrSim', biometricSubMenus.simOcr),
{ name: 'Verify Identity', link: '/face-verify'}, // Changed the name createSubMenu('Document OCR', 'collapseOcrDocument', biometricSubMenus.documentOcr)
{ name: 'Summary Report', link: '/face-summary'}, // Changed the name ]
{ name: 'Transaction Log', link: '/face-transaction'}, // Changed the name }],
], iconClass: 'fas fa-user'
}, },
{ {
name: 'KTP OCR', // Changed the name items: [{
target: 'collapseOcrKtp', name: 'SMS',
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', target: 'collapseSms',
subMenus: [ subMenus: [
{ createMenuItem('SMS Verification', '/sms-verify'),
name: 'SMS Verification', // Changed the name createSubMenu('SMS OTP Management', 'collapseSmsOtp', smsServiceMenus.smsOtp),
link: '/sms-verify' createSubMenu('SMS Announcements', 'collapseAnnouncement', smsServiceMenus.announcement),
createMenuItem('Blocked Numbers', '/sms-block'),
createMenuItem('SMS Anomaly Report', '/sms-anomaly')
]
}],
iconClass: 'fas fa-phone'
}, },
{ {
name: 'SMS OTP Management', // Changed the name items: [{
target: 'collapseSmsOtp', name: 'WhatsApp',
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', target: 'collapseWa',
subMenus: [ subMenus: [
{ createMenuItem('Verify WhatsApp Account', '/wa-verify'),
name: 'Verify WhatsApp Account', // Changed the name createSubMenu('WhatsApp Management', 'collapseWaManage', whatsAppMenus.management),
link: '/wa-verify' createSubMenu('WhatsApp Activity', 'collapseActivity', whatsAppMenus.activity),
createMenuItem('WhatsApp Inbox', '/wa-inbox'),
createMenuItem('Blocked WhatsApp Numbers', '/wa-block')
]
}],
iconClass: 'fab fa-whatsapp'
}, },
{ {
name: 'WhatsApp Management', // Changed the name items: [{
target: 'collapseWaManage', name: 'Identity',
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', target: 'collapseIdentify',
subMenus: [ subMenus: [
{ createSubMenu('Electronic Certificate Verification', 'collapseElectro', identityMenus.electronic),
name: 'Electronic Certificate Verification', // Changed the name createSubMenu('NPWP Verification', 'collapseIdentifyNpwp', identityMenus.npwp),
target: 'collapseElectro', createSubMenu('Tax Number Verification', 'collapseTax', identityMenus.tax),
subMenus: [ createSubMenu('Income Verification', 'collapseIncome', identityMenus.income),
{ name: 'Verify Certificate', link: '/identity-electro-verify'}, // Changed the name createSubMenu('ID Verification', 'collapseIdVerification', identityMenus.id)
{ name: 'Electronic Transaction', link: '/identity-electro-transaction'}, ]
], }],
iconClass: 'fas fa-edit'
}, },
{ {
name: 'NPWP Verification', // Changed the name items: [{
target: 'collapseIdentifyNpwp', name: 'Watchlist',
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', target: 'collapseWatchlist',
subMenus: [ subMenus: [
{ createSubMenu('Watchlist Screening', 'collapseScreening', watchlistMenus.screening)
name: 'Watchlist Screening', // Changed the name ]
target: 'collapseScreening', }],
subMenus: [ iconClass: 'fas fa-calendar'
{ 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: [ items: [{
{ name: 'File',
name: 'File Management', // Changed the name
target: 'collapseFiles', target: 'collapseFiles',
subMenus: [ subMenus: [
{ createSubMenu('File Screening', 'collapseScreening', fileMenus.screening)
name: 'File Screening', // Changed the name ]
target: 'collapseScreening', }],
subMenus: [ iconClass: 'fas fa-cogs'
{ 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',
} }
]; ];

View File

@ -28,7 +28,7 @@ const Compare = () => {
const [imageCompareUrl, setImageCompareUrl] = useState(''); const [imageCompareUrl, setImageCompareUrl] = useState('');
const [verified, setVerified] = useState(null); const [verified, setVerified] = useState(null);
const fileTypes = ["JPG", "JPEG", "PNG"]; const fileTypes = ["JPG", "JPEG"];
const [file, setFile] = useState(null); // For the first image const [file, setFile] = useState(null); // For the first image
const [compareFile, setCompareFile] = useState(null); // For the second imag const [compareFile, setCompareFile] = useState(null); // For the second imag
@ -113,21 +113,120 @@ const Compare = () => {
}; };
const handleImageUpload = (file) => { const handleImageUpload = (file) => {
if (file && fileTypes.includes(file.name.split('.').pop().toUpperCase())) { if (!file) {
setSelectedImageName(file.name); setUploadError('Please select a file');
setFile(file); // Store the file directly in state return;
setUploadError(''); // Clear error if valid
} }
// 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) => { const handleCompareImageUpload = (file) => {
if (file && fileTypes.includes(file.name.split('.').pop().toUpperCase())) { if (!file) {
setSelectedCompareImageName(file.name); setCompareUploadError('Please select a file');
setCompareFile(file); // Store the compare file directly in state return;
setCompareUploadError(''); // Clear error if valid
} }
// 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 = () => { const handleImageCancel = () => {
setSelectedImageName(''); setSelectedImageName('');
setImageUrl(''); setImageUrl('');
@ -289,23 +388,23 @@ const Compare = () => {
</tbody> </tbody>
</table> </table>
<div style={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'center', gap: '20px' }}> <div style={styles.imagesWrapper}>
<div style={styles.imageContainer}> <div style={styles.imageContainer}>
<img <img
src={imageUrl || "path-to-your-image"} src={imageUrl || "path-to-your-image"}
alt="Original Foto" alt="Original Foto"
style={styles.imageStyle} style={styles.responsiveImage}
/> />
<p style={{ marginTop: '1rem', textAlign: 'center' }}>File Name: {resultImageLabel}</p> <p style={styles.imageLabel}>File Name: {resultImageLabel}</p>
</div> </div>
<div style={styles.imageCompareContainer}> <div style={styles.imageContainer}>
<img <img
src={imageCompareUrl || "path-to-your-image"} src={imageCompareUrl || "path-to-your-image"}
alt="Compare Foto" alt="Compare Foto"
style={styles.imageStyle} style={styles.responsiveImage}
/> />
<p style={{ marginTop: '1rem', textAlign: 'center' }}>File Name: {resultCompareImageLabel}</p> <p style={styles.imageLabel}>File Name: {resultCompareImageLabel}</p>
</div> </div>
</div> </div>
</div> </div>
@ -445,18 +544,27 @@ const Compare = () => {
name="file" name="file"
types={fileTypes} types={fileTypes}
multiple={false} multiple={false}
onTypeError={(err) => {
setUploadError('Only JPG/JPEG files are allowed');
setFile(null);
setSelectedImageName('');
}}
children={ children={
<div style={styles.uploadArea}> <div style={styles.uploadArea}>
<i className="fas fa-cloud-upload-alt" style={styles.uploadIcon}></i> <i className="fas fa-cloud-upload-alt" style={styles.uploadIcon}></i>
<p style={styles.uploadText}>Drag and Drop Here</p> <p style={styles.uploadText}>Drag and Drop Here</p>
<p>Or</p> <p>Or</p>
<a href="#">Browse</a> <a href="#">Browse</a>
<p className="text-muted">Recommended size: 250x250 (Max File Size: 2MB)</p> <p className="text-muted">Recommended size: 300x300 (Max File Size: 2MB)</p>
<p className="text-muted">Supported file types: JPG, JPEG</p> <p className="text-muted">Supported file types: JPG, JPEG</p>
</div> </div>
} }
/> />
{uploadError && <small style={styles.uploadError}>{uploadError}</small>} {uploadError && (
<small className="text-danger mt-2" style={{ fontSize: '12px' }}>
{uploadError}
</small>
)}
</div> </div>
{/* Display uploaded image name */} {/* Display uploaded image name */}
@ -485,18 +593,27 @@ const Compare = () => {
name="file" name="file"
types={fileTypes} types={fileTypes}
multiple={false} multiple={false}
onTypeError={(err) => {
setCompareUploadError('Only JPG/JPEG files are allowed');
setCompareFile(null);
setSelectedCompareImageName('');
}}
children={ children={
<div style={styles.uploadArea}> <div style={styles.uploadArea}>
<i className="fas fa-cloud-upload-alt" style={styles.uploadIcon}></i> <i className="fas fa-cloud-upload-alt" style={styles.uploadIcon}></i>
<p style={styles.uploadText}>Drag and Drop Here</p> <p style={styles.uploadText}>Drag and Drop Here</p>
<p>Or</p> <p>Or</p>
<a href="#">Browse</a> <a href="#">Browse</a>
<p className="text-muted">Recommended size: 250x250 (Max File Size: 2MB)</p> <p className="text-muted">Recommended size: 300x300 (Max File Size: 2MB)</p>
<p className="text-muted">Supported file types: JPG, JPEG</p> <p className="text-muted">Supported file types: JPG, JPEG</p>
</div> </div>
} }
/> />
{compareUploadError && <small style={styles.uploadError}>{compareUploadError}</small>} {compareUploadError && (
<small className="text-danger mt-2" style={{ fontSize: '12px' }}>
{compareUploadError}
</small>
)}
</div> </div>
{/* Display uploaded image name */} {/* Display uploaded image name */}
@ -678,25 +795,42 @@ const styles = {
borderCollapse: 'collapse', borderCollapse: 'collapse',
marginBottom: '20px', marginBottom: '20px',
}, },
imagesWrapper: {
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'center',
gap: '2rem',
width: '100%',
margin: '2rem 0',
},
imageContainer: { imageContainer: {
flex: '1 1 300px',
maxWidth: '500px',
minWidth: '280px',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
alignItems: 'center', alignItems: 'center',
flex: 1,
padding: '10px',
}, },
imageCompareContainer: {
display: 'flex', responsiveImage: {
flexDirection: 'column',
alignItems: 'center',
flex: 1,
padding: '10px',
},
imageStyle: {
width: '100%', width: '100%',
height: 'auto', height: 'auto',
maxWidth: '150px', // Limit image width maxHeight: '500px',
objectFit: 'contain',
borderRadius: '8px', 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) => ({ similarityText: (verified) => ({
border: '0.1px solid gray', border: '0.1px solid gray',

View File

@ -10,7 +10,7 @@ const Enroll = () => {
const BASE_URL = process.env.REACT_APP_BASE_URL const BASE_URL = process.env.REACT_APP_BASE_URL
const API_KEY = process.env.REACT_APP_API_KEY const API_KEY = process.env.REACT_APP_API_KEY
const fileTypes = ["JPG", "JPEG", "PNG"]; const fileTypes = ["JPG", "JPEG"];
const [file, setFile] = useState(null); const [file, setFile] = useState(null);
const [errorMessage, setErrorMessage] = useState(''); const [errorMessage, setErrorMessage] = useState('');
@ -137,21 +137,60 @@ const Enroll = () => {
}; };
const handleImageUpload = (file) => { const handleImageUpload = (file) => {
// Ensure the file is not undefined or null before accessing its properties if (!file) {
if (file && file.name) { setImageError('Please select a file');
const fileExtension = file.name.split('.').pop().toUpperCase(); return;
if (fileTypes.includes(fileExtension)) { }
// 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); setSelectedImageName(file.name);
setFile(file); setFile(file);
setImageError(''); // Clear any previous errors setImageError('');
} else { };
alert('Image format is not supported');
setImageError('Image format is not supported'); img.onerror = () => {
URL.revokeObjectURL(objectUrl);
setImageError('Invalid image file');
setFile(null); setFile(null);
} setSelectedImageName('');
} else { };
console.error('No file selected or invalid file object.');
} img.src = objectUrl;
}; };
@ -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 = { const styles = {
formGroup: { formGroup: {
marginTop: '-45px', marginTop: '-45px',
@ -633,6 +661,12 @@ const Enroll = () => {
color: 'white', color: 'white',
marginTop: '10px', marginTop: '10px',
}, },
errorText: {
color: '#dc3545',
fontSize: '12px',
marginTop: '5px',
display: 'block'
}
}; };
if (!isServer) { if (!isServer) {
@ -745,6 +779,11 @@ const Enroll = () => {
name="file" name="file"
types={fileTypes} types={fileTypes}
multiple={false} multiple={false}
onTypeError={(err) => {
setImageError('Only JPG/JPEG files are allowed');
setFile(null);
setSelectedImageName('');
}}
onDrop={(files) => { onDrop={(files) => {
if (files && files[0]) { if (files && files[0]) {
handleImageUpload(files[0]); handleImageUpload(files[0]);
@ -761,26 +800,18 @@ const Enroll = () => {
</div> </div>
} }
/> />
<input
type="file" {imageError && (
id="fileUpload" <small className="text-danger mt-2" style={{ fontSize: '12px' }}>
ref={fileInputRef} {imageError}
style={{ display: 'none' }} </small>
accept="image/jpeg, image/jpg" )}
onChange={e => handleImageUpload(e.target.files[0])}
/>
{imageError && <small style={{ color: 'red' }}>{imageError}</small>}
</div> </div>
</div> </div>
{selectedImageName && ( {selectedImageName && (
<div className="mt-3"> <div className="mt-3">
<p><strong>File:</strong> {selectedImageName}</p> <p><strong>File:</strong> {selectedImageName}</p>
{file && (
<p style={styles.fileSize}>
Size: {formatFileSize(file.size)}
</p>
)}
<button className="btn btn-danger" onClick={handleImageCancel}> <button className="btn btn-danger" onClick={handleImageCancel}>
<FontAwesomeIcon icon={faTimes} className="me-2" />Cancel <FontAwesomeIcon icon={faTimes} className="me-2" />Cancel
</button> </button>
@ -810,10 +841,15 @@ const Enroll = () => {
<img <img
src={imageUrl || "path-to-your-image"} src={imageUrl || "path-to-your-image"}
alt="Contoh Foto" alt="Contoh Foto"
style={isMobile ? styles.imageStyleMobile : styles.imageStyle} style={{
maxWidth: '100%',
height: 'auto',
objectFit: 'contain',
borderRadius: '5px'
}}
/> />
<p style={isMobile ? { ...styles.imageDetails, fontSize: '14px' } : styles.imageDetails}> <p style={isMobile ? { ...styles.imageDetails, fontSize: '14px' } : styles.imageDetails}>
{resultImageLabel} {/* Display resultImageLabel instead of selectedImageName */} {resultImageLabel}
</p> </p>
</div> </div>
</div> </div>

View File

@ -5,7 +5,7 @@ import { FileUploader } from 'react-drag-drop-files';
import Select from 'react-select' import Select from 'react-select'
import { ServerDownAnimation } from '../../../../assets/images'; import { ServerDownAnimation } from '../../../../assets/images';
const fileTypes = ["JPG", "JPEG", "PNG"]; // Allowed file types const fileTypes = ["JPG", "JPEG"]; // Allowed file types
const Search = () => { const Search = () => {
@ -126,34 +126,83 @@ const Search = () => {
setIsSelectOpen(false); setIsSelectOpen(false);
}; };
// Add this after your existing state declarations
const handleImageUpload = (file) => { const handleImageUpload = (file) => {
// Ensure the file is not undefined or null before accessing its properties if (!file) {
if (file && file.name) { setImageError('Please select a file');
const fileExtension = file.name.split('.').pop().toUpperCase(); return;
if (fileTypes.includes(fileExtension)) { }
// 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); setSelectedImageName(file.name);
setFile(file); setFile(file);
setUploadedFile(file); // Set uploadedFile to the selected file setImageError('');
setImageError(''); // Clear any previous errors
} else {
alert('Image format is not supported');
setImageError('Image format is not supported');
setFile(null);
setUploadedFile(null); // Clear uploadedFile if the file is unsupported
}
} else {
console.error('No file selected or invalid file object.');
}
}; };
img.onerror = () => {
URL.revokeObjectURL(previewUrl);
setImageError('Invalid image file');
setFile(null);
setSelectedImageName('');
setUploadedFile(null);
};
img.src = previewUrl;
};
// Update the handleImageCancel function
const handleImageCancel = () => { const handleImageCancel = () => {
setSelectedImageName(''); setSelectedImageName('');
setFile(null); setFile(null);
setUploadedFile(null);
if (fileInputRef.current) { if (fileInputRef.current) {
fileInputRef.current.value = ''; fileInputRef.current.value = '';
} }
}; };
const handleCheckClick = async () => { const handleCheckClick = async () => {
// Clear existing errors // Clear existing errors
setApplicationIdError(''); setApplicationIdError('');
@ -356,10 +405,10 @@ const Search = () => {
}, },
uploadArea: { uploadArea: {
backgroundColor: '#e6f2ff', backgroundColor: '#e6f2ff',
height: '40svh', height: '250px',
cursor: 'pointer', cursor: 'pointer',
marginTop: '1rem', marginTop: '1rem',
padding: '1rem', paddingTop: '22px',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
justifyContent: 'center', justifyContent: 'center',
@ -367,6 +416,20 @@ const Search = () => {
border: '1px solid #ced4da', border: '1px solid #ced4da',
borderRadius: '0.25rem', borderRadius: '0.25rem',
}, },
uploadAreaMobile: {
backgroundColor: '#e6f2ff',
height: '50svh', // Use viewport height for a more responsive size
cursor: 'pointer',
marginTop: '1rem',
paddingTop: '18px',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
border: '1px solid #ced4da',
borderRadius: '0.25rem',
padding: '20px',
},
uploadIcon: { uploadIcon: {
fontSize: '40px', fontSize: '40px',
color: '#0542cc', color: '#0542cc',
@ -479,14 +542,37 @@ const Search = () => {
gridTemplateColumns: isMobile ? 'repeat(2, 1fr)' : 'repeat(5, 1fr)', // Adjust based on isMobile gridTemplateColumns: isMobile ? 'repeat(2, 1fr)' : 'repeat(5, 1fr)', // Adjust based on isMobile
gap: '16px', gap: '16px',
}, },
resultItem: { imageWrapper: {
border: '1px solid #ddd', width: '100%',
height: '200px',
overflow: 'hidden',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#f8f9fa',
borderRadius: '8px', borderRadius: '8px',
padding: '12px', },
boxShadow: '0 2px 5px rgba(0, 0, 0, 0.1)',
textAlign: 'center', responsiveImage: {
transition: 'transform 0.3s ease', // Added transition for hover effect maxWidth: '100%',
cursor: 'pointer', 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: { resultItemHover: {
transform: 'scale(1.05)', // Slightly enlarge the card on hover transform: 'scale(1.05)', // Slightly enlarge the card on hover
@ -627,7 +713,6 @@ const Search = () => {
</div> </div>
{/* Upload Section */} {/* Upload Section */}
{/* Drag and Drop File Uploader */}
<div className='col-md-6'> <div className='col-md-6'>
<div className="row form-group mt-4"> <div className="row form-group mt-4">
<CustomLabel htmlFor="uploadPhoto" style={styles.customLabel}> <CustomLabel htmlFor="uploadPhoto" style={styles.customLabel}>
@ -638,9 +723,18 @@ const Search = () => {
name="file" name="file"
types={fileTypes} types={fileTypes}
multiple={false} multiple={false}
onDrop={(files) => handleImageUpload(files[0])} onTypeError={(err) => {
setImageError('Only JPG/JPEG files are allowed');
setFile(null);
setSelectedImageName('');
}}
onDrop={(files) => {
if (files && files[0]) {
handleImageUpload(files[0]);
}
}}
children={ children={
<div style={styles.uploadArea}> <div style={isMobile ? styles.uploadAreaMobile : styles.uploadArea}>
<i className="fas fa-cloud-upload-alt" style={styles.uploadIcon}></i> <i className="fas fa-cloud-upload-alt" style={styles.uploadIcon}></i>
<p style={styles.uploadText}>Drag and Drop Here</p> <p style={styles.uploadText}>Drag and Drop Here</p>
<p>Or</p> <p>Or</p>
@ -650,15 +744,12 @@ const Search = () => {
</div> </div>
} }
/> />
<input
type="file" {imageError && (
id="fileUpload" <small className="text-danger mt-2" style={{ fontSize: '12px' }}>
ref={fileInputRef} {imageError}
style={{ display: 'none' }} </small>
accept="image/jpeg, image/jpg" )}
onChange={e => handleImageUpload(e.target.files[0])}
/>
{imageError && <small style={styles.uploadError}>{imageError}</small>}
</div> </div>
</div> </div>
@ -686,8 +777,7 @@ const Search = () => {
</div> </div>
{/* Results Section */} {/* Results Section */}
{ {showResult && results.length > 0 && (
showResult && results.length > 0 && (
<div style={styles.containerResultStyle}> <div style={styles.containerResultStyle}>
<h1 style={{ color: '#0542cc', textAlign: 'center' }}>Results</h1> <h1 style={{ color: '#0542cc', textAlign: 'center' }}>Results</h1>
<div style={styles.resultGrid}> <div style={styles.resultGrid}>
@ -698,13 +788,18 @@ const Search = () => {
<p style={styles.resultText}>Similarity: {result.similarity}%</p> <p style={styles.resultText}>Similarity: {result.similarity}%</p>
<p style={styles.resultText}>Distance: {result.distance}</p> <p style={styles.resultText}>Distance: {result.distance}</p>
</div> </div>
<img src={imageUrls[index]} alt={`Result ${index + 1}`} style={styles.resultImage} /> <div style={styles.imageWrapper}>
<img
src={imageUrls[index]}
alt={`Result ${index + 1}`}
style={styles.responsiveImage}
/>
</div>
</div> </div>
))} ))}
</div> </div>
</div> </div>
) )}
}
</div> </div>
); );

View File

@ -3,12 +3,13 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faChevronDown, faTimes } from '@fortawesome/free-solid-svg-icons'; import { faChevronDown, faTimes } from '@fortawesome/free-solid-svg-icons';
import Select from 'react-select' import Select from 'react-select'
import { ServerDownAnimation } from '../../../../assets/images'; import { ServerDownAnimation } from '../../../../assets/images';
import { FileUploader } from 'react-drag-drop-files';
const Verify = () => { const Verify = () => {
const BASE_URL = process.env.REACT_APP_BASE_URL; const BASE_URL = process.env.REACT_APP_BASE_URL;
const API_KEY = process.env.REACT_APP_API_KEY; const API_KEY = process.env.REACT_APP_API_KEY;
const fileTypes = ["JPG", "JPEG", "PNG"]; const fileTypes = ["JPG", "JPEG"];
const [file, setFile] = useState(null); const [file, setFile] = useState(null);
const [errorMessage, setErrorMessage] = useState(''); const [errorMessage, setErrorMessage] = useState('');
@ -18,7 +19,7 @@ const Verify = () => {
const [thresholdError, setThresholdError] = useState(''); const [thresholdError, setThresholdError] = useState('');
const [selectedImageName, setSelectedImageName] = useState(''); const [selectedImageName, setSelectedImageName] = useState('');
const [resultImageLabel, setResultImageLabel] = useState(''); const [resultImageLabel, setResultImageLabel] = useState('');
const fileInputRef = useRef(null); const inputRef = useRef(null);
const [showResult, setShowResult] = useState(false); const [showResult, setShowResult] = useState(false);
const [applicationId, setApplicationId] = useState(''); const [applicationId, setApplicationId] = useState('');
const [thresholdId, setThresholdId] = useState(''); const [thresholdId, setThresholdId] = useState('');
@ -33,6 +34,7 @@ const Verify = () => {
const [inputValueApplication, setInputValueApplication] = useState(''); // Controlled input value for Application ID const [inputValueApplication, setInputValueApplication] = useState(''); // Controlled input value for Application ID
const [isMobile, setIsMobile] = useState(window.innerWidth <= 768); const [isMobile, setIsMobile] = useState(window.innerWidth <= 768);
const [isServer, setIsServer] = useState(true); const [isServer, setIsServer] = useState(true);
const [imageError, setImageError] = useState('');
const thresholdIds = [ const thresholdIds = [
{ id: 1, name: 'cosine', displayName: 'Basic' }, { id: 1, name: 'cosine', displayName: 'Basic' },
@ -135,28 +137,68 @@ const Verify = () => {
const handleImageUpload = (file) => { const handleImageUpload = (file) => {
if (file && file.name) { if (!file) {
const fileExtension = file.name.split('.').pop().toUpperCase(); setImageError('Please select a file');
if (fileTypes.includes(fileExtension)) { 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); setSelectedImageName(file.name);
setFile(file); setFile(file);
setUploadError(''); setImageError('');
} else { };
alert('Image format is not supported');
setUploadError('Image format is not supported'); img.onerror = () => {
URL.revokeObjectURL(objectUrl);
setImageError('Invalid image file');
setFile(null); setFile(null);
} setSelectedImageName('');
} else { };
console.error('No file selected or invalid file object.');
} img.src = objectUrl;
}; };
const handleImageCancel = () => { const handleImageCancel = () => {
setSelectedImageName(''); setSelectedImageName('');
setFile(null); setFile(null);
if (fileInputRef.current) { if (inputRef.current) {
fileInputRef.current.value = ''; 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) { if (!isServer) {
return ( return (
<div style={{ textAlign: 'center', marginTop: '50px' }}> <div style={{ textAlign: 'center', marginTop: '50px' }}>
@ -734,60 +765,50 @@ const Verify = () => {
</div> </div>
{/* Upload Section */} {/* Upload Section */}
<div className="col-md-6"> <div className='col-md-6'>
<div className="row form-group mt-4"> <div className="row form-group mt-4">
<CustomLabel htmlFor="uploadPhoto" style={styles.customLabel}> <CustomLabel htmlFor="uploadPhoto" style={styles.customLabel}>
Upload Face Photo Upload Face Photo
</CustomLabel> </CustomLabel>
<div style={styles.uploadWrapper}> <FileUploader
<div handleChange={handleImageUpload}
style={styles.uploadArea} name="file"
onDrop={(e) => { types={fileTypes}
e.preventDefault(); multiple={false}
// Handle drop event onTypeError={(err) => {
const file = e.dataTransfer.files[0]; setImageError('Only JPG/JPEG files are allowed');
handleImageUpload(file); setFile(null);
setSelectedImageName('');
}} }}
onDragOver={(e) => e.preventDefault()} 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> <i className="fas fa-cloud-upload-alt" style={styles.uploadIcon}></i>
<p style={styles.uploadText}>Drag and Drop Here</p> <p style={styles.uploadText}>Drag and Drop Here</p>
<p>Or</p> <p>Or</p>
<a <span
href="#" style={{
onClick={(e) => { color: '#0542cc',
e.preventDefault(); textDecoration: 'underline',
// Only open file input if no file is selected cursor: 'pointer'
if (!fileInputRef.current.files.length) {
fileInputRef.current.click();
}
}} }}
style={styles.browseLink}
> >
Browse Browse
</a> </span>
<p className="text-muted" style={styles.uploadText}> <p className="text-muted">Recommended size: 300x300 (Max File Size: 2MB)</p>
Recommended size: 300x300 (Max File Size: 2MB) <p className="text-muted">Supported file types: JPG, JPEG</p>
</p>
<p className="text-muted" style={styles.uploadText}>
Supported file types: JPG, JPEG
</p>
</div> </div>
}
{/* File Input */}
<input
ref={fileInputRef}
type="file"
id="uploadPhoto"
className="form-control"
style={{ display: 'none' }}
accept="image/jpeg, image/jpg"
onChange={(e) => handleImageUpload(e.target.files[0])}
/> />
{imageError && (
{/* Error Message */} <small className="text-danger mt-2" style={{ fontSize: '12px' }}>
{uploadError && <small style={styles.uploadError}>{uploadError}</small>} {imageError}
</div> </small>
)}
</div> </div>
</div> </div>
@ -795,11 +816,6 @@ const Verify = () => {
{selectedImageName && ( {selectedImageName && (
<div className="mt-3"> <div className="mt-3">
<p><strong>File:</strong> {selectedImageName}</p> <p><strong>File:</strong> {selectedImageName}</p>
{file && (
<p style={styles.fileSize}>
Size: {formatFileSize(file.size)}
</p>
)}
<button className="btn btn-danger" onClick={handleImageCancel}> <button className="btn btn-danger" onClick={handleImageCancel}>
<FontAwesomeIcon icon={faTimes} className="me-2" />Cancel <FontAwesomeIcon icon={faTimes} className="me-2" />Cancel
</button> </button>
@ -835,15 +851,22 @@ const Verify = () => {
<img <img
src={imageUrl || "path-to-your-image"} src={imageUrl || "path-to-your-image"}
alt="Example Image" alt="Example Image"
style={styles.imageStyle} style={{
maxWidth: '100%',
width: 'auto',
height: 'auto',
objectFit: 'contain',
borderRadius: '5px'
}}
/> />
<p style={{ marginTop: '10px' }}> <p style={{ marginTop: '10px' }}>
File Name: {resultImageLabel} {/* Display the resultImageLabel here */} File Name: {resultImageLabel}
</p> </p>
</div> </div>
</div> </div>
</div> </div>
)} )}
</div> </div>
); );

View File

@ -12,7 +12,7 @@ const CustomLabel = ({ overRide, children, ...props }) => {
const Verify = () => { const Verify = () => {
const BASE_URL = process.env.REACT_APP_BASE_URL; const BASE_URL = process.env.REACT_APP_BASE_URL;
const API_KEY = process.env.REACT_APP_API_KEY; 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 fileInputRef = useRef(null);
const [isMobile, setIsMobile] = useState(false); const [isMobile, setIsMobile] = useState(false);
@ -119,17 +119,61 @@ 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); setFile(file);
setSelectedImageName(file.name); setSelectedImageName(file.name);
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 // Validate file type
if (!fileTypes.includes(file.type)) { if (!fileTypes.includes(file.type)) {
setImageError('Invalid file type. Only JPG, JPEG, and PNG are allowed.'); setImageError('Invalid file type. Only JPG and JPEG are allowed.');
} else if (file.size > 2 * 1024 * 1024) { // Max 2MB return;
}
// Validate file size
if (file.size > 2 * 1024 * 1024) {
setImageError('File size exceeds 2MB.'); setImageError('File size exceeds 2MB.');
} else { return;
}
// Validate image dimensions
await checkImageDimensions(file);
setImageError(''); setImageError('');
} catch (error) {
setImageError(error);
setFile(null);
setSelectedImageName('');
} }
}; };
@ -161,6 +205,33 @@ const Verify = () => {
return Object.values(errors).every(error => error === ''); 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 // Submit form and trigger OCR API
const handleCheckClick = async () => { const handleCheckClick = async () => {
if (!validateForm()) { if (!validateForm()) {
@ -183,16 +254,18 @@ const Verify = () => {
}); });
if (!response.ok) { 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(); const result = await response.json();
console.log('📡 API Response:', result);
// Log the full result to verify structure // Log the full result to verify structure
console.log('OCR API Response:', result); console.log('OCR API Response:', result);
if (result.status_code === 201) { if (result.status_code === 201) {
const responseData = result.details.data?.['data-ktp'] || {}; const responseData = result.details.data?.['data-ktp'] || {};
console.log('✅ OCR Success:', result);
const updateQuota = result.details.data.quota const updateQuota = result.details.data.quota
const data = { const data = {
@ -230,10 +303,22 @@ const Verify = () => {
await fetchImage(imageFileName); // Call the fetchImage function to fetch the image await fetchImage(imageFileName); // Call the fetchImage function to fetch the image
} }
} else { } else {
setErrorMessage('OCR processing failed'); const errorMsg = handleApiError(result);
setErrorMessage(errorMsg);
setShowResult(false);
console.error('❌ OCR Failed:', {
error: errorMsg,
response: result
});
} }
} catch (error) { } 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 { } finally {
setIsLoading(false); setIsLoading(false);
} }
@ -350,9 +435,10 @@ const Verify = () => {
borderRadius: '4px', borderRadius: '4px',
}, },
errorText: { errorText: {
color: '#721c24', color: '#dc3545', // Bootstrap danger color
fontSize: '14px', fontSize: '12px', // Small text
margin: '0', marginTop: '5px',
fontWeight: '400'
}, },
loadingOverlay: { loadingOverlay: {
position: 'fixed', // Gunakan fixed untuk overlay penuh layar position: 'fixed', // Gunakan fixed untuk overlay penuh layar
@ -501,8 +587,9 @@ const Verify = () => {
<CustomLabel htmlFor="imageInput" style={styles.customLabel}> <CustomLabel htmlFor="imageInput" style={styles.customLabel}>
Upload Image (KTP) Upload Image (KTP)
</CustomLabel> </CustomLabel>
<div style={styles.uploadWrapper}> <div style={styles.uploadWrapper}>
{/* Drag and Drop File Input */} {/* Existing drag & drop area */}
<div <div
style={styles.uploadArea} style={styles.uploadArea}
onDrop={(e) => { onDrop={(e) => {
@ -516,20 +603,21 @@ const Verify = () => {
<p>Or</p> <p>Or</p>
<a href="#" onClick={() => fileInputRef.current.click()} style={styles.browseLink}>Browse</a> <a href="#" onClick={() => fileInputRef.current.click()} style={styles.browseLink}>Browse</a>
<p className="text-muted" style={styles.uploadText}>Recommended size: 300x300 (Max File Size: 2MB)</p> <p className="text-muted" style={styles.uploadText}>Recommended size: 300x300 (Max File Size: 2MB)</p>
<p className="text-muted" style={styles.uploadText}>Supported file types: JPG, JPEG, PNG</p> <p className="text-muted" style={styles.uploadText}>Supported file types: JPG, JPEG</p>
</div> </div>
{/* File Input */} {/* File input */}
<input <input
ref={fileInputRef} ref={fileInputRef}
type="file" type="file"
id="imageInput" id="imageInput"
className="form-control" className="form-control"
style={{ display: 'none' }} style={{ display: 'none' }}
accept="image/jpeg, image/png" accept="image/jpeg, image/jpg"
onChange={(e) => handleImageUpload(e.target.files[0])} onChange={(e) => handleImageUpload(e.target.files[0])}
/> />
{/* Display selected file info */}
{selectedImageName && ( {selectedImageName && (
<div className="mt-3"> <div className="mt-3">
<p><strong>File:</strong> {selectedImageName}</p> <p><strong>File:</strong> {selectedImageName}</p>
@ -543,8 +631,12 @@ const Verify = () => {
</button> </button>
</div> </div>
)} )}
{/* Display validation errors */}
{validationErrors.file && <p style={styles.errorText}>{validationErrors.file}</p>} {validationErrors.file && <p style={styles.errorText}>{validationErrors.file}</p>}
{imageError && <p style={styles.errorText}>{imageError}</p>}
</div> </div>
</div> </div>
</div> </div>

View File

@ -12,7 +12,7 @@ const CustomLabel = ({ overRide, children, ...props }) => {
const Verify = () => { const Verify = () => {
const BASE_URL = process.env.REACT_APP_BASE_URL; const BASE_URL = process.env.REACT_APP_BASE_URL;
const API_KEY = process.env.REACT_APP_API_KEY; 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 fileInputRef = useRef(null);
const [isMobile, setIsMobile] = useState(false); const [isMobile, setIsMobile] = useState(false);
@ -119,17 +119,61 @@ 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); setFile(file);
setSelectedImageName(file.name); setSelectedImageName(file.name);
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 // Validate file type
if (!fileTypes.includes(file.type)) { if (!fileTypes.includes(file.type)) {
setImageError('Invalid file type. Only JPG, JPEG, and PNG are allowed.'); setImageError('Invalid file type. Only JPG and JPEG are allowed.');
} else if (file.size > 2 * 1024 * 1024) { // Max 2MB return;
}
// Validate file size
if (file.size > 2 * 1024 * 1024) {
setImageError('File size exceeds 2MB.'); setImageError('File size exceeds 2MB.');
} else { return;
}
// Validate image dimensions
await checkImageDimensions(file);
setImageError(''); setImageError('');
} catch (error) {
setImageError(error);
setFile(null);
setSelectedImageName('');
} }
}; };
@ -340,9 +384,10 @@ const Verify = () => {
borderRadius: '4px', borderRadius: '4px',
}, },
errorText: { errorText: {
color: '#721c24', color: '#dc3545',
fontSize: '14px', fontSize: '12px',
margin: '0', marginTop: '5px',
fontWeight: '400'
},loadingOverlay: { },loadingOverlay: {
position: 'fixed', // Gunakan fixed untuk overlay penuh layar position: 'fixed', // Gunakan fixed untuk overlay penuh layar
top: 0, top: 0,
@ -515,7 +560,7 @@ const Verify = () => {
id="imageInput" id="imageInput"
className="form-control" className="form-control"
style={{ display: 'none' }} style={{ display: 'none' }}
accept="image/jpeg, image/png" accept="image/jpeg, image/jpg"
onChange={(e) => handleImageUpload(e.target.files[0])} onChange={(e) => handleImageUpload(e.target.files[0])}
/> />
@ -532,7 +577,9 @@ const Verify = () => {
</button> </button>
</div> </div>
)} )}
{/* Display validation errors */}
{validationErrors.file && <p style={styles.errorText}>{validationErrors.file}</p>} {validationErrors.file && <p style={styles.errorText}>{validationErrors.file}</p>}
{imageError && <p style={styles.errorText}>{imageError}</p>}
</div> </div>
</div> </div>
</div> </div>