vfi-2fa
Version:
- Headless (100% customizable, Bring-your-own-UI) - Auto out of the box, fully controllable API - Sorting (Multi and Stable) - Filters - Row Selection - Row Expansion - Column Ordering - Animatable - Resizable - Server-side/controlled data/state
120 lines (119 loc) • 9.14 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
/* eslint-disable no-unused-vars */
import { useEffect, useRef, useState } from "react";
import { Button, Col, Collapse, Input, Modal, ModalBody, Row } from "reactstrap";
import ModalHeader from "./ModalHeader";
import { requester } from "../utils/requester";
const MaskHelper = {
MaskEmail: (email) => {
if (!email) {
return '';
}
const parts = email.split('@');
return `${parts[0].slice(0, 3)}***@${parts[1]}`;
},
MaskPhone: (phone) => {
if (!phone) {
return '';
}
return phone.replace(/\d(?=\d{4})/g, '*');
}
};
export const OTPHandler = (props) => {
const { isOpen, onSubmit, onClose, otpData, notificationError, sendEndpoint, email, phone } = props;
const refOTP = useRef(null);
const [otp, setOtp] = useState("");
const [secret, setSecret] = useState();
const [isOpenMore, setIsOpenMore] = useState();
const [selectedType, setSelectedType] = useState(otpData?.type);
const [pendingType, setPendingType] = useState(otpData?.type);
const [otpMessage, setOtpMessage] = useState(otpData?.message);
const sendOTPApi = ({ params, endpoint }) => {
return requester.post(endpoint, params);
};
const isNeedSendOTP = () => {
return pendingType !== "TOTP" && !secret || (pendingType !== selectedType);
};
const handleSubmit = (e) => {
e.preventDefault();
if (isNeedSendOTP()) {
const params = {
params: {
type: pendingType
},
endpoint: sendEndpoint
};
sendOTPApi(params).then(response => {
if (response && response.isValid) {
setSelectedType(pendingType);
setSecret(response.data?.secretData);
switch (pendingType) {
case "TOTP":
setOtpMessage("Vui lòng kiểm tra ứng dụng bảo mật và nhập mã OTP của bạn.");
break;
case "SMS_OTP":
const markPhone = MaskHelper.MaskPhone(phone || otpData?.user2FAConfigDto?.phone);
setOtpMessage(markPhone
? `Mã bảo mật đã được gửi tới ${markPhone}. Vui lòng kiểm tra ứng dụng tin nhắn và nhập mã OTP của bạn.`
: "Vui lòng kiểm tra ứng dụng tin nhắn và nhập mã OTP của bạn.");
break;
case "EMAIL_OTP":
const markEmail = MaskHelper.MaskEmail(email || otpData?.user2FAConfigDto?.email);
setOtpMessage(markEmail
? `Mã bảo mật đã được gửi tới ${markEmail}. Vui lòng kiểm tra OTP được gửi tới hòm thư của bạn.`
: "Vui lòng kiểm tra OTP được gửi tới hòm thư của bạn.");
break;
}
refOTP?.current?.focus();
setIsOpenMore(false);
}
else {
setPendingType(selectedType);
notificationError?.("Không thể lấy mã OTP, vui lòng thử lại sau");
}
}).catch(e => {
setPendingType(selectedType);
notificationError?.("Không thể lấy mã OTP, vui lòng thử lại sau");
refOTP?.current?.focus();
});
}
else {
onSubmit(otp, secret, selectedType);
}
};
// Handle modal close
const handleCancel = () => {
};
const handleClosed = () => {
setOtp("");
setSecret("");
setOtpMessage("");
};
const handleModalOpened = () => {
setSelectedType(otpData?.type);
setPendingType(otpData?.type);
if (otpData?.message) {
setOtpMessage(otpData?.message);
}
setOtp('');
refOTP?.current?.focus();
};
useEffect(() => {
setSelectedType(otpData?.type);
setPendingType(otpData?.type);
setOtp('');
}, [otpData]);
return (_jsxs(Modal, { className: 'modal-dialog modal-dialog-centered', isOpen: isOpen,
// toggle={handleCancel}
contentClassName: 'p-0', onOpened: handleModalOpened, onClosed: handleClosed,
// backdrop={"static"}
keyboard: true, children: [_jsx(ModalHeader, { title: 'X\u00E1c minh b\u1EA3o m\u1EADt', handleModal: onClose }), _jsxs(ModalBody, { children: [selectedType === 'TOTP' && (_jsx("div", { className: "mb-1", children: _jsxs("p", { children: ["Vui l\u00F2ng nh\u1EADp m\u00E3 OTP \u0111\u00E3 \u0111\u01B0\u1EE3c thi\u1EBFt l\u1EADp trong \u1EE9ng d\u1EE5ng x\u00E1c th\u1EF1c c\u1EE7a b\u1EA1n (", _jsx("span", { className: "fw-bold text-primary", children: "Google Authenticator" }), ", ", ' ', _jsx("span", { className: "fw-bold text-primary", children: "Microsoft Authenticator" }), ",", ' ', _jsx("span", { className: "fw-bold text-primary", children: "Authy" }), ",", ' ', _jsx("span", { className: "fw-bold text-primary", children: "1Password" }), ",...)"] }) })), selectedType === 'EMAIL_OTP' && (_jsxs("div", { className: "mb-1", children: ["Vui l\u00F2ng nh\u1EADp m\u00E3 OTP \u0111\u00E3 \u0111\u01B0\u1EE3c g\u1EEDi \u0111\u1EBFn email c\u1EE7a b\u1EA1n \u0111\u1EC3 x\u00E1c minh b\u1EA3o m\u1EADt v\u00E0 ti\u1EBFp t\u1EE5c thao t\u00E1c.", _jsx("br", {})] })), selectedType === 'SMS_OTP' && (_jsxs("div", { className: "mb-1", children: ["Vui l\u00F2ng nh\u1EADp m\u00E3 OTP \u0111\u00E3 \u0111\u01B0\u1EE3c g\u1EEDi \u0111\u1EBFn s\u1ED1 \u0111i\u1EC7n tho\u1EA1i c\u1EE7a b\u1EA1n \u0111\u1EC3 x\u00E1c minh b\u1EA3o m\u1EADt v\u00E0 ti\u1EBFp t\u1EE5c thao t\u00E1c.", _jsx("br", {})] })), _jsxs(Row, { className: 'gy-1', children: [_jsx(Col, { xs: 12, children: _jsx(Input, { innerRef: refOTP, placeholder: 'Nh\u1EADp m\u00E3 x\u00E1c th\u1EF1c', autoComplete: 'off', className: "py-1 px-1 fs-3", maxLength: 6, minLength: 6, autoFocus: true, value: otp, onChange: (e) => {
setOtp(e.currentTarget.value);
} }) }), otpMessage && (_jsx(Col, { xs: 12, children: _jsx("div", { className: secret ? "text-primary" : "text-warning", children: otpMessage }) })), _jsx("hr", {}), _jsxs(Col, { xs: 12, children: [_jsx("a", { onClick: () => setIsOpenMore(!isOpenMore), children: "Ho\u1EB7c th\u1EED v\u1EDBi c\u00E1c ph\u01B0\u01A1ng th\u1EE9c kh\u00E1c" }), _jsxs(Collapse, { isOpen: isOpenMore, children: [otpData?.user2FAConfigDto?.enableTOTP && (_jsxs("div", { className: "form-check", children: [_jsx("input", { className: "form-check-input", type: "radio", name: "Type", value: "TOTP", id: "flexRadioTOTP", checked: pendingType === 'TOTP', onChange: (e) => {
setPendingType(e.currentTarget.value);
} }), _jsx("label", { className: "form-check-label", htmlFor: "flexRadioTOTP", children: "\u1EE8ng d\u1EE5ng x\u00E1c th\u1EF1c" })] })), otpData?.user2FAConfigDto?.enableEmail && (_jsxs("div", { className: "form-check", children: [_jsx("input", { className: "form-check-input", type: "radio", name: "Type", value: "EMAIL_OTP", id: "flexRadioEmail", checked: pendingType === 'EMAIL_OTP', onChange: (e) => {
setPendingType(e.currentTarget.value);
} }), _jsxs("label", { className: "form-check-label", htmlFor: "flexRadioEmail", children: ["Email ", MaskHelper.MaskEmail(email ?? otpData?.user2FAConfigDto?.email)] })] })), otpData?.user2FAConfigDto?.enableSms && (_jsxs("div", { className: "form-check", children: [_jsx("input", { className: "form-check-input", type: "radio", name: "Type", value: "SMS_OTP", id: "flexRadioSMS", checked: pendingType === 'SMS_OTP', onChange: (e) => {
setPendingType(e.currentTarget.value);
} }), _jsxs("label", { className: "form-check-label", htmlFor: "flexRadioSMS", children: ["S\u1ED1 \u0111i\u1EC7n tho\u1EA1i ", MaskHelper.MaskPhone(phone || otpData?.user2FAConfigDto?.phone)] })] }))] })] }), _jsxs(Col, { className: 'd-flex justify-content-end mt-2', xs: 12, children: [_jsx(Button, { outline: true, color: 'secondary', className: 'me-1', onClick: onClose, children: "H\u1EE7y" }), isNeedSendOTP() ? (_jsx(Button, { color: 'primary', className: '', onClick: handleSubmit, outline: true, children: _jsx("span", { className: 'me-50', children: "G\u1EEDi OTP" }) })) : (_jsx(Button, { color: 'primary', className: '', onClick: handleSubmit, children: _jsx("span", { className: 'me-50', children: "X\u00E1c th\u1EF1c" }) }))] })] })] })] }));
};