This commit is contained in:
Rizqika 2024-12-24 22:19:18 +07:00
parent 5a23d77b23
commit 2f97160893
5 changed files with 298 additions and 191 deletions

View File

@ -113,6 +113,7 @@ const Compare = () => {
}; };
const handleImageUpload = (file) => { const handleImageUpload = (file) => {
setShowResult(false);
if (!file) { if (!file) {
setUploadError('Please select a file'); setUploadError('Please select a file');
return; return;
@ -140,12 +141,15 @@ const Compare = () => {
} }
// Set file and filename if validation passes // Set file and filename if validation passes
const previewUrl = URL.createObjectURL(file);
setImageUrl(previewUrl);
setFile(file); setFile(file);
setSelectedImageName(file.name); // Add this line setSelectedImageName(file.name); // Add this line
setUploadError(''); // Clear any previous errors setUploadError(''); // Clear any previous errors
}; };
const handleCompareImageUpload = (file) => { const handleCompareImageUpload = (file) => {
setShowResult(false);
if (!file) { if (!file) {
setCompareUploadError('Please select a file'); setCompareUploadError('Please select a file');
return; return;
@ -173,6 +177,8 @@ const Compare = () => {
} }
// Set file and filename if validation passes // Set file and filename if validation passes
const previewUrl = URL.createObjectURL(file);
setImageCompareUrl(previewUrl);
setCompareFile(file); setCompareFile(file);
setSelectedCompareImageName(file.name); // Add this line setSelectedCompareImageName(file.name); // Add this line
setCompareUploadError(''); // Clear any previous errors setCompareUploadError(''); // Clear any previous errors
@ -181,13 +187,17 @@ const Compare = () => {
const handleImageCancel = () => { const handleImageCancel = () => {
setSelectedImageName(''); setSelectedImageName('');
setFile(null);
setImageUrl(''); setImageUrl('');
setShowResult(false);
if (fileInputRef.current) fileInputRef.current.value = ''; if (fileInputRef.current) fileInputRef.current.value = '';
}; };
const handleCompareImageCancel = () => { const handleCompareImageCancel = () => {
setSelectedCompareImageName(''); setSelectedCompareImageName('');
setCompareFile(null);
setImageCompareUrl(''); setImageCompareUrl('');
setShowResult(false);
if (fileCompareInputRef.current) fileCompareInputRef.current.value = ''; if (fileCompareInputRef.current) fileCompareInputRef.current.value = '';
}; };
@ -534,6 +544,17 @@ const Compare = () => {
{/* Display uploaded image name */} {/* Display uploaded image name */}
{selectedImageName && ( {selectedImageName && (
<div className="mt-3"> <div className="mt-3">
<img
src={imageUrl}
alt="Selected preview"
style={{
width: '100px',
height: '100px',
objectFit: 'cover',
marginBottom: '10px',
borderRadius: '4px'
}}
/>
<p><strong>File:</strong> {selectedImageName}</p> <p><strong>File:</strong> {selectedImageName}</p>
{file && ( {file && (
<p style={styles.fileSize}> <p style={styles.fileSize}>
@ -583,6 +604,17 @@ const Compare = () => {
{/* Display uploaded image name */} {/* Display uploaded image name */}
{selectedCompareImageName && ( {selectedCompareImageName && (
<div className="mt-3"> <div className="mt-3">
<img
src={imageCompareUrl}
alt="Compare preview"
style={{
width: '100px',
height: '100px',
objectFit: 'cover',
marginBottom: '10px',
borderRadius: '4px'
}}
/>
<p><strong>File:</strong> {selectedCompareImageName}</p> <p><strong>File:</strong> {selectedCompareImageName}</p>
{compareFile && ( {compareFile && (
<p style={styles.fileSize}> <p style={styles.fileSize}>

View File

@ -606,6 +606,16 @@ const Enroll = () => {
{selectedImageName && ( {selectedImageName && (
<div className="mt-3"> <div className="mt-3">
<img
src={imageUrl || "path-to-your-image"}
alt="Contoh Foto"
style={{
maxWidth: '20%',
height: 'auto',
objectFit: 'contain',
borderRadius: '5px'
}}
/>
<p><strong>File:</strong> {selectedImageName}</p> <p><strong>File:</strong> {selectedImageName}</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

View File

@ -128,12 +128,16 @@ const Search = () => {
// Add this after your existing state declarations // Add this after your existing state declarations
const handleImageUpload = (file) => { const handleImageUpload = (file) => {
setShowResult(false);
if (!file) { if (!file) {
setImageError('Please select a file'); setImageError('Please select a file');
return; return;
} }
// Store the uploaded file // Store the uploaded file
const previewUrl = URL.createObjectURL(file);
setImageUrls([previewUrl]);
setUploadedFile(file); setUploadedFile(file);
setFile(file); setFile(file);
setSelectedImageName(file.name); setSelectedImageName(file.name);
@ -161,9 +165,6 @@ const Search = () => {
return; return;
} }
// Create preview URL
const previewUrl = URL.createObjectURL(file);
// Check image dimensions // Check image dimensions
const img = new Image(); const img = new Image();
img.onerror = () => { img.onerror = () => {
@ -752,6 +753,17 @@ const Search = () => {
{selectedImageName && ( {selectedImageName && (
<div className="mt-3"> <div className="mt-3">
<img
src={imageUrls[0]}
alt="Selected preview"
style={{
width: '100px',
height: '100px',
objectFit: 'cover',
marginBottom: '10px',
borderRadius: '4px'
}}
/>
<p><strong>File:</strong> {selectedImageName}</p> <p><strong>File:</strong> {selectedImageName}</p>
{file && ( {file && (
<p style={styles.fileSize}> <p style={styles.fileSize}>

View File

@ -795,6 +795,17 @@ const Verify = () => {
{/* Display uploaded image name */} {/* Display uploaded image name */}
{selectedImageName && ( {selectedImageName && (
<div className="mt-3"> <div className="mt-3">
<img
src={imageUrl || "path-to-your-image"}
alt="Example Image"
style={{
maxWidth: '25%',
width: 'auto',
height: 'auto',
objectFit: 'contain',
borderRadius: '5px'
}}
/>
<p><strong>File:</strong> {selectedImageName}</p> <p><strong>File:</strong> {selectedImageName}</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

View File

@ -1,35 +1,60 @@
import React, { useState, useRef, useEffect } from 'react' import React, { useState, useRef, useEffect } from 'react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { ServerDownAnimation } from '../../../../assets/images';
import { faChevronLeft, faChevronDown } from '@fortawesome/free-solid-svg-icons';
import Select from 'react-select' import Select from 'react-select'
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 Verify = ({ phoneId, onVerify }) => { const Verify = ({ onVerify, generateId }) => {
const [otpCode, setOtpCode] = useState(''); const [otpCode, setOtpCode] = useState('');
const [generateCode, setGenerateCode] = useState(''); const [generateCode, setGenerateCode] = useState(generateId);
const [error, setError] = useState(''); const [error, setError] = useState('');
const handleClick = async () => { useEffect(() => {
console.log('Verify Click'); setGenerateCode(generateId);
console.log('otp code: ', otpCode); }, [generateId]);
console.log('generate code: ', generateCode);
console.log('phone number: ', phoneId);
const handleClick = async () => {
// Validation logic // Validation logic
if (!otpCode) { if (!otpCode) {
setError("OTP Code is required."); // Set error message directly setError("OTP Code is required.");
return; // Exit the function if there are errors return;
} else if (otpCode.length !== 6) {
setError("OTP Code must be exactly 6 characters long."); // Set error message directly
return; // Exit the function if there are errors
} }
setError(''); // Clear error if validation passes const requestData = {
onVerify(); // Call the onVerify function passed from Auth id: generateId,
otp: otpCode
}; };
fetch(`${BASE_URL}/wa/otp-verify`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': API_KEY
},
body: JSON.stringify(requestData)
})
.then(response => response.json())
.then(data => {
if (data.status_code === 201) {
setError(''); // Clear any existing error message
alert('OTP verification successful!'); // Show success alert
onVerify(); // Trigger verify callback
} else {
setError(data.details.message || "Verification failed");
}
})
.catch(error => {
console.error('Verification failed:', error);
setError("Failed to verify OTP");
});
};
const generateTimestampId = () => {
const timestamp = new Date().getTime().toString(16); // Convert timestamp to hex
const randomPart = Math.random().toString(16).substr(2, 8);
return `${timestamp}-${randomPart}-${randomPart}-${randomPart}`;
};
return ( return (
<div style={styles.section}> <div style={styles.section}>
@ -41,29 +66,13 @@ const Verify = ({ phoneId, onVerify }) => {
type="text" type="text"
id="generateCode" id="generateCode"
className="form-control" className="form-control"
placeholder="e133d538-dc40-4c43-b042-06aad864d634" placeholder="Generated Code"
value={generateCode} value={`${generateTimestampId()}-${generateCode}`}
onChange={(e) => setGenerateCode(e.target.value)} onChange={(e) => setGenerateCode(e.target.value)}
readOnly readOnly
/> />
</div> </div>
</div> </div>
<div className="col-md-6">
<div className="input-group">
<span className="input-group-prepend">
<span className="input-group-text">+62</span>
</span>
<input
type="text"
id="phoneId"
className="form-control"
placeholder="Phone Number"
value={phoneId}
readOnly
/>
</div>
</div>
</div> </div>
{/* OTP Code */} {/* OTP Code */}
@ -77,14 +86,12 @@ const Verify = ({ phoneId, onVerify }) => {
value={otpCode} value={otpCode}
onChange={(e) => { onChange={(e) => {
const value = e.target.value; const value = e.target.value;
// Allow only digits and enforce exactly 6 digits
if (/^\d{0,6}$/.test(value)) { if (/^\d{0,6}$/.test(value)) {
setOtpCode(value); setOtpCode(value);
} }
}} }}
maxLength={6} // Prevents entering more than 6 digits maxLength={6}
/> />
{error && <small className="text-danger">{error}</small>} {error && <small className="text-danger">{error}</small>}
</div> </div>
</div> </div>
@ -100,9 +107,24 @@ const Verify = ({ phoneId, onVerify }) => {
} }
const Preview = () => { const Preview = ({ otpLength }) => {
// Generate OTP when otpLength changes
const generateOTP = (length) => {
let otp = '';
for (let i = 0; i < length; i++) {
otp += Math.floor(Math.random() * 10);
}
return otp;
};
// State to store the generated OTP
const [inputValue, setInputValue] = useState(''); const [inputValue, setInputValue] = useState('');
// Update OTP whenever otpLength changes
useEffect(() => {
setInputValue(generateOTP(parseInt(otpLength))); // Re-generate OTP on otpLength change
}, [otpLength]); // Dependency array with otpLength
const handleCopy = () => { const handleCopy = () => {
navigator.clipboard.writeText(inputValue); navigator.clipboard.writeText(inputValue);
console.log('Copied'); console.log('Copied');
@ -116,8 +138,7 @@ const Preview = () => {
type="text" type="text"
id="inputValue" id="inputValue"
className="form-control" className="form-control"
placeholder="7976 is your verification code. For your security, do not share this code." value={`${inputValue} is your verification code. For your security, do not share this code.`}
value={inputValue}
onChange={(e) => setInputValue(e.target.value)} onChange={(e) => setInputValue(e.target.value)}
readOnly readOnly
/> />
@ -131,19 +152,18 @@ const Preview = () => {
</div> </div>
</div> </div>
</div> </div>
) );
} };
const Auth = () => { const Auth = () => {
const [isSelectOpen, setIsSelectOpen] = useState(false);
const [applicationId, setApplicationId] = useState(''); const [applicationId, setApplicationId] = useState('');
const [expiryId, setExpiryId] = useState(0); const [expiryId, setExpiryId] = useState(0);
const [otpId, setOtpId] = useState(''); const [otpId, setOtpId] = useState('');
const [phoneId, setPhoneId] = useState(''); const [phoneId, setPhoneId] = useState('');
const [templateId, setTemplateId] = useState(''); const [templateId, setTemplateId] = useState('');
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [errorMessage, setErrorMessage] = useState('');
const [applicationIds, setApplicationIds] = useState([]); const [applicationIds, setApplicationIds] = useState([]);
const [showVerify, setShowVerify] = useState(false); const [showVerify, setShowVerify] = useState(false);
@ -159,6 +179,9 @@ const Auth = () => {
label: app.name label: app.name
})); }));
const [templateOptions, setTemplateOptions] = useState([]);
const [generateId, setgenerateId] = useState('');
const handleApplicationChange = (selectedOption) => { const handleApplicationChange = (selectedOption) => {
const selectedId = selectedOption.value; const selectedId = selectedOption.value;
const selectedApp = applicationIds.find(app => app.id === parseInt(selectedId)); const selectedApp = applicationIds.find(app => app.id === parseInt(selectedId));
@ -170,58 +193,97 @@ const Auth = () => {
} }
}; };
const handleTemplateChange = (selectedOption) => {
setTemplateId(selectedOption ? selectedOption.value : '');
};
const handleInputChangeApplication = (newInputValue) => { const handleInputChangeApplication = (newInputValue) => {
// Limit input to 15 characters for Application ID // Limit input to 15 characters for Application ID
if (newInputValue.length <= 15) { if (newInputValue.length <= 15) {
setInputValueApplication(newInputValue); setInputValueApplication(newInputValue);
} }
}; };
useEffect(() => { useEffect(() => {
const fetchApplicationIds = async () => { const fetchData = () => {
try {
setIsLoading(true); setIsLoading(true);
const response = await fetch(`${BASE_URL}/application/list`, {
fetch(`${BASE_URL}/application/list`, {
method: 'GET',
headers: {
'accept': 'application/json',
'x-api-key': API_KEY,
},
})
.then(response => response.json())
.then(appData => {
if (appData.status_code === 200) {
setApplicationIds(appData.details.data);
return fetch(`${BASE_URL}/template/list?type=1`, {
method: 'GET', method: 'GET',
headers: { headers: {
'accept': 'application/json', 'accept': 'application/json',
'x-api-key': API_KEY, 'x-api-key': API_KEY,
}, },
}); });
if (!response.ok) {
throw new Error('Failed to fetch application IDs');
} }
setIsServer(false);
const data = await response.json(); setErrorMessage('Failed to fetch application IDs');
console.log('Response Data:', data); throw new Error('Failed to fetch application IDs');
})
if (data.status_code === 200) { .then(response => response.json())
setApplicationIds(data.details.data); .then(templateData => {
if (templateData.status_code === 200) {
const templates = templateData.details.data.map(template => ({
value: template.id,
label: template.name
}));
setTemplateOptions(templates);
setIsServer(true); setIsServer(true);
} else { } else {
setIsServer(false); setIsServer(false);
throw new Error(data.details.message || 'Failed to fetch application IDs'); setErrorMessage('Failed to fetch templates');
throw new Error('Failed to fetch templates');
} }
} catch (error) { })
setErrors(error.message || 'Error fetching application IDs'); .catch(error => {
console.error('Error:', error);
setIsServer(false); setIsServer(false);
} finally { setErrorMessage(error.message || 'Server connection failed');
})
.finally(() => {
setIsLoading(false); setIsLoading(false);
} });
}; };
fetchApplicationIds(); fetchData();
}, []); }, []);
const handleFocus = () => { // Server Down Component
setIsSelectOpen(true); if (!isServer) {
}; return (
<div style={{ textAlign: 'center', marginTop: '50px' }}>
const handleBlur = () => { <img
setIsSelectOpen(false); src={ServerDownAnimation}
}; alt="Server Down Animation"
style={{ width: '18rem', height: '18rem', marginBottom: '20px' }}
/>
<h2 style={{ color: 'red' }}>Server tidak dapat diakses</h2>
<p>{errorMessage || 'Silakan periksa koneksi internet Anda atau coba lagi nanti.'}</p>
<button
onClick={() => window.location.reload()}
style={{
padding: '10px 20px',
backgroundColor: '#0542cc',
color: '#fff',
border: 'none',
borderRadius: '5px',
cursor: 'pointer'
}}>
Coba Lagi
</button>
</div>
);
}
const handleVerify = () => { const handleVerify = () => {
setShowPreview(true); // Show the preview when verified setShowPreview(true); // Show the preview when verified
}; };
@ -239,18 +301,55 @@ const Auth = () => {
const handleClick = async () => { const handleClick = async () => {
if (validate()) { if (validate()) {
// Log the input data setIsLoading(true);
console.log('Application ID:', applicationId);
console.log('Expiry ID:', expiryId);
console.log('OTP Length:', otpId);
console.log('Phone Number:', '08' + phoneId); // Prepend "08" to the phone number
console.log('Template ID:', templateId);
console.log('Make Auth Demo'); const requestData = {
setShowVerify(true); application_id: parseInt(applicationId),
phone: phoneId,
digits: parseInt(otpId),
interval: parseInt(expiryId),
template_id: parseInt(templateId),
is_test: true,
mode_id: 9
};
console.log('Request Data:', requestData);
// Add timeout to ensure channel stays open
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Request timeout')), 30000)
);
Promise.race([
fetch(`${BASE_URL}/wa/otp`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': API_KEY
},
body: JSON.stringify(requestData)
}),
timeoutPromise
])
.then(response => response.json())
.then(data => {
console.log('API Response:', data);
if (data.status_code === 201 && data.details.message === "Successfully") {
setgenerateId(data.details.data.id);
setShowVerify(false);
alert('Authentication request successful!');
}
})
.catch(error => {
console.error('Request failed:', error);
})
.finally(() => {
setIsLoading(false);
});
} }
}; };
return ( return (
<> <>
<div style={styles.section}> <div style={styles.section}>
@ -299,62 +398,61 @@ const Auth = () => {
{/* Expiry and OTP */} {/* Expiry and OTP */}
<div className="form-group row align-items-center"> <div className="form-group row align-items-center">
{/* Expiry ID/Interval */}
<div className="col-md-6"> <div className="col-md-6">
<div style={styles.selectWrapper}> <div style={styles.selectWrapper}>
<input <input
type="number" type="number"
id="expiryId" id="expiryId"
className="form-control" className="form-control"
style={styles.select} value={expiryId === 0 ? '' : expiryId}
value={expiryId}
onChange={(e) => { onChange={(e) => {
const value = e.target.value; const value = e.target.value;
// Check if the value is empty or a valid number if (/^[0-9]+$/.test(value)) {
if (value === '' || /^[0-9]+$/.test(value)) { setExpiryId(parseInt(value, 10));
setExpiryId(value === '' ? '' : parseInt(value, 10)); // Update state as number } else if (value === '') {
setExpiryId(0);
} }
}} }}
onFocus={handleFocus}
onBlur={handleBlur}
placeholder="Expiry Time" placeholder="Expiry Time"
/> />
{errors.expiryId && <small className="text-danger">{errors.expiryId}</small>} {errors.expiryId && <small className="text-danger">{errors.expiryId}</small>}
</div> </div>
</div> </div>
{/* Digits */}
<div className="col-md-6"> <div className="col-md-6">
<input <select
type="text"
id="otpId" id="otpId"
className="form-control" className="form-control"
placeholder="OTP Length"
value={otpId} value={otpId}
onChange={(e) => { onChange={(e) => setOtpId(e.target.value)}
const value = e.target.value; >
// Allow only digits and enforce length constraints <option value="">Select OTP Length</option>
if (/^\d{0,6}$/.test(value)) { {[1, 2, 3, 4, 5, 6, 7, 8].map(length => (
setOtpId(value); <option key={length} value={length}>
} {length}
}} </option>
minLength={6} // Enforce minimum length (for validation) ))}
maxLength={6} // Enforce maximum length </select>
/>
{errors.otpId && <small className="text-danger">{errors.otpId}</small>} {errors.otpId && <small className="text-danger">{errors.otpId}</small>}
</div> </div>
</div> </div>
{/* Message and Phone */} {/* Message and Phone */}
<div className="form-group row align-items-center mt-4"> <div className="form-group row align-items-center mt-4">
<div className="col-md-6"> <div className="col-md-6">
<div style={styles.selectWrapper}> <div style={styles.selectWrapper}>
<input <Select
type="text"
id="templateId" id="templateId"
className="form-control" value={templateOptions.find(option => option.value === templateId)}
placeholder="Template Name" onChange={handleTemplateChange}
value={templateId} options={templateOptions}
onChange={(e) => setTemplateId(e.target.value)} placeholder="Select Template"
isSearchable
menuPortalTarget={document.body}
menuPlacement="auto"
/> />
{errors.templateId && <small className="text-danger">{errors.templateId}</small>} {errors.templateId && <small className="text-danger">{errors.templateId}</small>}
</div> </div>
@ -363,7 +461,7 @@ const Auth = () => {
<div className="col-md-6"> <div className="col-md-6">
<div className="input-group"> <div className="input-group">
<span className="input-group-prepend"> <span className="input-group-prepend">
<span className="input-group-text">+62</span> <span className="input-group-text">Phone Number</span>
</span> </span>
<input <input
type="text" type="text"
@ -395,8 +493,8 @@ const Auth = () => {
</div> </div>
</div> </div>
{showVerify && <Verify phoneId={phoneId} onVerify={handleVerify} />} {showVerify && <Verify phoneId={phoneId} onVerify={handleVerify} generateId={generateId} />}
{showPreview && <Preview />} <Preview otpLength={otpId} />
</> </>
); );
} }
@ -408,9 +506,6 @@ const styles = {
section: { section: {
margin: '1rem 0 1rem 0' margin: '1rem 0 1rem 0'
}, },
formGroup: {
marginTop: '-45px',
},
selectWrapper: { selectWrapper: {
position: 'relative', position: 'relative',
marginTop: '0', marginTop: '0',
@ -423,13 +518,6 @@ const styles = {
border: 'none', border: 'none',
outline: 'none', outline: 'none',
}, },
chevronIcon: {
position: 'absolute',
right: '10px',
top: '50%',
transform: 'translateY(-50%)',
pointerEvents: 'none',
},
remainingQuota: { remainingQuota: {
display: 'flex', display: 'flex',
flexDirection: 'row', flexDirection: 'row',
@ -446,17 +534,6 @@ const styles = {
verticalAlign: 'super', verticalAlign: 'super',
fontSize: '20px', fontSize: '20px',
}, },
buttonContainer: {
display: 'flex',
flexDirection: 'column',
justifyContent: 'center', // Center vertically within container
position: 'absolute',
right: '10px',
top: '0',
bottom: '0',
height: '47px',
margin: 'auto 0',
},
button: { button: {
background: 'none', background: 'none',
border: 'none', border: 'none',
@ -475,41 +552,6 @@ const styles = {
position: 'relative', position: 'relative',
zIndex: 1, zIndex: 1,
}, },
uploadError: {
color: 'red',
fontSize: '12px',
marginTop: '5px',
},
containerResultStyle: {
padding: '20px',
border: '1px solid #0053b3',
borderRadius: '5px',
width: '100%',
margin: '20px auto',
},
resultContainer: {
display: 'flex',
justifyContent: 'flex-start',
alignItems: 'center',
width: '100%',
},
tableStyle: {
width: '60%',
borderCollapse: 'collapse',
},
imageContainer: {
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-start',
width: '30%',
marginLeft: '10px'
},
imageStyle: {
width: '193px',
height: '242px',
borderRadius: '5px',
},
loadingOverlay: { loadingOverlay: {
position: 'fixed', position: 'fixed',
top: 0, top: 0,