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) => {
setShowResult(false);
if (!file) {
setUploadError('Please select a file');
return;
@ -140,12 +141,15 @@ const Compare = () => {
}
// Set file and filename if validation passes
const previewUrl = URL.createObjectURL(file);
setImageUrl(previewUrl);
setFile(file);
setSelectedImageName(file.name); // Add this line
setUploadError(''); // Clear any previous errors
};
const handleCompareImageUpload = (file) => {
setShowResult(false);
if (!file) {
setCompareUploadError('Please select a file');
return;
@ -173,6 +177,8 @@ const Compare = () => {
}
// Set file and filename if validation passes
const previewUrl = URL.createObjectURL(file);
setImageCompareUrl(previewUrl);
setCompareFile(file);
setSelectedCompareImageName(file.name); // Add this line
setCompareUploadError(''); // Clear any previous errors
@ -181,13 +187,17 @@ const Compare = () => {
const handleImageCancel = () => {
setSelectedImageName('');
setFile(null);
setImageUrl('');
setShowResult(false);
if (fileInputRef.current) fileInputRef.current.value = '';
};
const handleCompareImageCancel = () => {
setSelectedCompareImageName('');
setCompareFile(null);
setImageCompareUrl('');
setShowResult(false);
if (fileCompareInputRef.current) fileCompareInputRef.current.value = '';
};
@ -534,6 +544,17 @@ const Compare = () => {
{/* Display uploaded image name */}
{selectedImageName && (
<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>
{file && (
<p style={styles.fileSize}>
@ -583,6 +604,17 @@ const Compare = () => {
{/* Display uploaded image name */}
{selectedCompareImageName && (
<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>
{compareFile && (
<p style={styles.fileSize}>

View File

@ -606,6 +606,16 @@ const Enroll = () => {
{selectedImageName && (
<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>
<button className="btn btn-danger" onClick={handleImageCancel}>
<FontAwesomeIcon icon={faTimes} className="me-2" />Cancel

View File

@ -128,12 +128,16 @@ const Search = () => {
// Add this after your existing state declarations
const handleImageUpload = (file) => {
setShowResult(false);
if (!file) {
setImageError('Please select a file');
return;
}
// Store the uploaded file
const previewUrl = URL.createObjectURL(file);
setImageUrls([previewUrl]);
setUploadedFile(file);
setFile(file);
setSelectedImageName(file.name);
@ -161,9 +165,6 @@ const Search = () => {
return;
}
// Create preview URL
const previewUrl = URL.createObjectURL(file);
// Check image dimensions
const img = new Image();
img.onerror = () => {
@ -752,6 +753,17 @@ const Search = () => {
{selectedImageName && (
<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>
{file && (
<p style={styles.fileSize}>

View File

@ -795,6 +795,17 @@ const Verify = () => {
{/* Display uploaded image name */}
{selectedImageName && (
<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>
<button className="btn btn-danger" onClick={handleImageCancel}>
<FontAwesomeIcon icon={faTimes} className="me-2" />Cancel

View File

@ -1,35 +1,60 @@
import React, { useState, useRef, useEffect } from 'react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faChevronLeft, faChevronDown } from '@fortawesome/free-solid-svg-icons';
import { ServerDownAnimation } from '../../../../assets/images';
import Select from 'react-select'
const BASE_URL = process.env.REACT_APP_BASE_URL
const API_KEY = process.env.REACT_APP_API_KEY
const Verify = ({ phoneId, onVerify }) => {
const Verify = ({ onVerify, generateId }) => {
const [otpCode, setOtpCode] = useState('');
const [generateCode, setGenerateCode] = useState('');
const [generateCode, setGenerateCode] = useState(generateId);
const [error, setError] = useState('');
const handleClick = async () => {
console.log('Verify Click');
console.log('otp code: ', otpCode);
console.log('generate code: ', generateCode);
console.log('phone number: ', phoneId);
useEffect(() => {
setGenerateCode(generateId);
}, [generateId]);
const handleClick = async () => {
// Validation logic
if (!otpCode) {
setError("OTP Code is required."); // Set error message directly
return; // Exit the function if there are errors
} 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("OTP Code is required.");
return;
}
setError(''); // Clear error if validation passes
onVerify(); // Call the onVerify function passed from Auth
const requestData = {
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 (
<div style={styles.section}>
@ -41,29 +66,13 @@ const Verify = ({ phoneId, onVerify }) => {
type="text"
id="generateCode"
className="form-control"
placeholder="e133d538-dc40-4c43-b042-06aad864d634"
value={generateCode}
placeholder="Generated Code"
value={`${generateTimestampId()}-${generateCode}`}
onChange={(e) => setGenerateCode(e.target.value)}
readOnly
/>
</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>
{/* OTP Code */}
@ -77,14 +86,12 @@ const Verify = ({ phoneId, onVerify }) => {
value={otpCode}
onChange={(e) => {
const value = e.target.value;
// Allow only digits and enforce exactly 6 digits
if (/^\d{0,6}$/.test(value)) {
setOtpCode(value);
}
}}
maxLength={6} // Prevents entering more than 6 digits
maxLength={6}
/>
{error && <small className="text-danger">{error}</small>}
</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('');
// Update OTP whenever otpLength changes
useEffect(() => {
setInputValue(generateOTP(parseInt(otpLength))); // Re-generate OTP on otpLength change
}, [otpLength]); // Dependency array with otpLength
const handleCopy = () => {
navigator.clipboard.writeText(inputValue);
console.log('Copied');
@ -116,8 +138,7 @@ const Preview = () => {
type="text"
id="inputValue"
className="form-control"
placeholder="7976 is your verification code. For your security, do not share this code."
value={inputValue}
value={`${inputValue} is your verification code. For your security, do not share this code.`}
onChange={(e) => setInputValue(e.target.value)}
readOnly
/>
@ -131,19 +152,18 @@ const Preview = () => {
</div>
</div>
</div>
)
}
);
};
const Auth = () => {
const [isSelectOpen, setIsSelectOpen] = useState(false);
const [applicationId, setApplicationId] = useState('');
const [expiryId, setExpiryId] = useState(0);
const [otpId, setOtpId] = useState('');
const [phoneId, setPhoneId] = useState('');
const [templateId, setTemplateId] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [errorMessage, setErrorMessage] = useState('');
const [applicationIds, setApplicationIds] = useState([]);
const [showVerify, setShowVerify] = useState(false);
@ -159,6 +179,9 @@ const Auth = () => {
label: app.name
}));
const [templateOptions, setTemplateOptions] = useState([]);
const [generateId, setgenerateId] = useState('');
const handleApplicationChange = (selectedOption) => {
const selectedId = selectedOption.value;
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) => {
// Limit input to 15 characters for Application ID
if (newInputValue.length <= 15) {
setInputValueApplication(newInputValue);
}
};
useEffect(() => {
const fetchApplicationIds = async () => {
try {
const fetchData = () => {
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',
headers: {
'accept': 'application/json',
'x-api-key': API_KEY,
},
});
if (!response.ok) {
throw new Error('Failed to fetch application IDs');
}
const data = await response.json();
console.log('Response Data:', data);
if (data.status_code === 200) {
setApplicationIds(data.details.data);
setIsServer(false);
setErrorMessage('Failed to fetch application IDs');
throw new Error('Failed to fetch application IDs');
})
.then(response => response.json())
.then(templateData => {
if (templateData.status_code === 200) {
const templates = templateData.details.data.map(template => ({
value: template.id,
label: template.name
}));
setTemplateOptions(templates);
setIsServer(true);
} else {
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);
} finally {
setErrorMessage(error.message || 'Server connection failed');
})
.finally(() => {
setIsLoading(false);
}
});
};
fetchApplicationIds();
fetchData();
}, []);
const handleFocus = () => {
setIsSelectOpen(true);
};
const handleBlur = () => {
setIsSelectOpen(false);
};
// Server Down Component
if (!isServer) {
return (
<div style={{ textAlign: 'center', marginTop: '50px' }}>
<img
src={ServerDownAnimation}
alt="Server Down Animation"
style={{ width: '18rem', height: '18rem', marginBottom: '20px' }}
/>
<h2 style={{ color: 'red' }}>Server tidak dapat diakses</h2>
<p>{errorMessage || 'Silakan periksa koneksi internet Anda atau coba lagi nanti.'}</p>
<button
onClick={() => window.location.reload()}
style={{
padding: '10px 20px',
backgroundColor: '#0542cc',
color: '#fff',
border: 'none',
borderRadius: '5px',
cursor: 'pointer'
}}>
Coba Lagi
</button>
</div>
);
}
const handleVerify = () => {
setShowPreview(true); // Show the preview when verified
};
@ -239,18 +301,55 @@ const Auth = () => {
const handleClick = async () => {
if (validate()) {
// Log the input data
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);
setIsLoading(true);
console.log('Make Auth Demo');
setShowVerify(true);
const requestData = {
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 (
<>
<div style={styles.section}>
@ -299,62 +398,61 @@ const Auth = () => {
{/* Expiry and OTP */}
<div className="form-group row align-items-center">
{/* Expiry ID/Interval */}
<div className="col-md-6">
<div style={styles.selectWrapper}>
<input
type="number"
id="expiryId"
className="form-control"
style={styles.select}
value={expiryId}
value={expiryId === 0 ? '' : expiryId}
onChange={(e) => {
const value = e.target.value;
// Check if the value is empty or a valid number
if (value === '' || /^[0-9]+$/.test(value)) {
setExpiryId(value === '' ? '' : parseInt(value, 10)); // Update state as number
if (/^[0-9]+$/.test(value)) {
setExpiryId(parseInt(value, 10));
} else if (value === '') {
setExpiryId(0);
}
}}
onFocus={handleFocus}
onBlur={handleBlur}
placeholder="Expiry Time"
/>
{errors.expiryId && <small className="text-danger">{errors.expiryId}</small>}
</div>
</div>
{/* Digits */}
<div className="col-md-6">
<input
type="text"
<select
id="otpId"
className="form-control"
placeholder="OTP Length"
value={otpId}
onChange={(e) => {
const value = e.target.value;
// Allow only digits and enforce length constraints
if (/^\d{0,6}$/.test(value)) {
setOtpId(value);
}
}}
minLength={6} // Enforce minimum length (for validation)
maxLength={6} // Enforce maximum length
/>
onChange={(e) => setOtpId(e.target.value)}
>
<option value="">Select OTP Length</option>
{[1, 2, 3, 4, 5, 6, 7, 8].map(length => (
<option key={length} value={length}>
{length}
</option>
))}
</select>
{errors.otpId && <small className="text-danger">{errors.otpId}</small>}
</div>
</div>
{/* Message and Phone */}
<div className="form-group row align-items-center mt-4">
<div className="col-md-6">
<div style={styles.selectWrapper}>
<input
type="text"
<Select
id="templateId"
className="form-control"
placeholder="Template Name"
value={templateId}
onChange={(e) => setTemplateId(e.target.value)}
value={templateOptions.find(option => option.value === templateId)}
onChange={handleTemplateChange}
options={templateOptions}
placeholder="Select Template"
isSearchable
menuPortalTarget={document.body}
menuPlacement="auto"
/>
{errors.templateId && <small className="text-danger">{errors.templateId}</small>}
</div>
@ -363,7 +461,7 @@ const Auth = () => {
<div className="col-md-6">
<div className="input-group">
<span className="input-group-prepend">
<span className="input-group-text">+62</span>
<span className="input-group-text">Phone Number</span>
</span>
<input
type="text"
@ -395,8 +493,8 @@ const Auth = () => {
</div>
</div>
{showVerify && <Verify phoneId={phoneId} onVerify={handleVerify} />}
{showPreview && <Preview />}
{showVerify && <Verify phoneId={phoneId} onVerify={handleVerify} generateId={generateId} />}
<Preview otpLength={otpId} />
</>
);
}
@ -408,9 +506,6 @@ const styles = {
section: {
margin: '1rem 0 1rem 0'
},
formGroup: {
marginTop: '-45px',
},
selectWrapper: {
position: 'relative',
marginTop: '0',
@ -423,13 +518,6 @@ const styles = {
border: 'none',
outline: 'none',
},
chevronIcon: {
position: 'absolute',
right: '10px',
top: '50%',
transform: 'translateY(-50%)',
pointerEvents: 'none',
},
remainingQuota: {
display: 'flex',
flexDirection: 'row',
@ -446,17 +534,6 @@ const styles = {
verticalAlign: 'super',
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: {
background: 'none',
border: 'none',
@ -475,41 +552,6 @@ const styles = {
position: 'relative',
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: {
position: 'fixed',
top: 0,