OTP-WA
This commit is contained in:
parent
5a23d77b23
commit
2f97160893
@ -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}>
|
||||||
|
@ -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
|
||||||
|
@ -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}>
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user