| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244 |
- // hooks
- import { useForm, Controller } from 'react-hook-form';
- import React, { useCallback, useState } from 'react';
- import styles from './styles.module.scss';
- // utils
- import classNames from 'classnames';
- import PhoneInput from "@components/UInput/PhoneInput";
- import { useDispatch, useSelector } from "react-redux";
- import {
- getSendCodeStatus,
- getSendCodeTS,
- } from "@features/mainSlice";
- import { useEffect } from 'react';
- import { REQ_STATUS } from "@api/config";
- import { reqSendCode } from "@features/api";
- import { getCountryPhoneError, validateCountryPhone } from "@utils/helpers";
- import { CircularProgress } from "@mui/material";
- import { toast } from 'react-toastify';
- import MainButton from '@components/Buttons/MainButton';
- import { ReactComponent as AlertIcon } from '@assets/singlesvg/icon_alert.svg';
- import { getPhoneCode } from '@constants/region';
- const i18n = global.i18n;
- const objInfo = {
- cdTimer: null,
- inSubmit: false
- }
- const CODE_MIN = 4;
- const CODE_MAX = 4;
- const DEFAULT_COUNTRY = String(getPhoneCode()).replace(/^\+/, "") || "44";
- const PhoneSMS = ({ children, callback, buttonText, handleStatus, onCountryChange, options = {}, defValues = {}, className = '' }) => {
- const [country, setCountry] = useState(DEFAULT_COUNTRY);
- const [phoneValid, setPhoneValid] = useState(Boolean(defValues.phone));
- const [phoneMessage] = useState("");
- const [sendCount, setSendCount] = useState(0);
- const dispatch = useDispatch();
- const { handleSubmit, control, watch } = useForm({
- defaultValues: {
- // country 由当前区域配置决定。
- account: '',
- password: '',
- email: "",
- repassword: "",
- lang: "",
- code: "",
- phone: defValues.phone || "",
- newpassword: "",
- renewpassword: ""
- }
- });
- const otpTimeStamp = useSelector(getSendCodeTS);
- const [otpCD, setOtpCD] = useState(-1);
- const otpStatus = useSelector(getSendCodeStatus);
- const onSendCode = (e) => {
- if (otpCD > 0) return;
- if (otpStatus === REQ_STATUS.LOADING) return;
- let phone = control._formValues.phone;
- let validate = validatePhone(phone);
- if (!validate) {
- let msg = getCountryPhoneError(country);
- toast.warn(i18n.t(msg), { toastId: "phone_sms" });
- // toast.warn(i18n.t("phoneSms.tips_phone"), { toastId: "phone_sms" });
- control.setError("phone", true);
- return;
- }
- let params = { phone: phone, country: country };
- dispatch(reqSendCode(params));
- }
- const getLeftCD = useCallback(() => {
- let now = Date.now();
- let delta = now - otpTimeStamp;
- delta = Math.floor(delta / 1000);
- delta = 60 - delta;
- if (delta < 0) return -1;
- else return delta;
- }, [otpTimeStamp]);
- const checkPhone = (value) => {
- const ok = validatePhone(value);
- if (!ok) {
- setPhoneValid(false);
- } else {
- setPhoneValid(true);
- }
- return ok;
- }
- const clearTimer = () => {
- if (objInfo.cdTimer != null) {
- clearInterval(objInfo.cdTimer);
- objInfo.cdTimer = null;
- }
- }
- //effect_______
- useEffect(() => {
- return () => {
- global.authLockPath = null;
- clearTimer();
- objInfo.inSubmit = false;
- }
- }, [])
- useEffect(() => {
- let left = getLeftCD();
- setOtpCD(left);
- clearTimer();
- if (left > 0) {
- objInfo.cdTimer = setInterval(() => {
- left = getLeftCD();
- setOtpCD(left);
- if (left < 0) {
- setSendCount(prev => prev + 1);
- clearTimer();
- }
- }, 1000)
- }
- }, [getLeftCD])
- const validatePhone = (value, countryCode = country) => {
- if (String(defValues.phone) === String(value) && value.length > 0) return true;
- return validateCountryPhone(countryCode, value || "");
- }
- const codeValue = watch("code");
- const phoneValue = watch("phone");
- const checkCode = () => {
- return (!codeValue || codeValue.length < CODE_MIN || codeValue.length > CODE_MAX)
- }
- const checkFormOnClaim = (tips = true) => {
- if (!checkPhone(phoneValue)) {
- control.setError("phone", true);
- if (tips) toast.warn(i18n.t("phoneSms.tips_phone"), { toastId: "phone_sms" });
- return false;
- }
- if (!codeValue || codeValue.length < CODE_MIN || codeValue.length > CODE_MAX) {
- control.setError("code", true);
- if (tips) toast.warn(i18n.t("phoneSms.tips_info"), { toastId: "phone_sms" });
- return false;
- }
- return true;
- }
- const onSubmitOTPLogin = (data) => {
- if (!checkFormOnClaim()) return;
- //data.phone data.code
- data.country = country;
- callback && callback(data);
- }
- return (
- <form key={"otp"} autoComplete="off" className={`${styles.l_form} ${className && className}`} onSubmit={handleSubmit((data) => {
- onSubmitOTPLogin(data);
- })}>
- <Controller control={control}
- name="phone"
- rules={{ required: true }}
- render={({ field: { ref, onChange, value }, fieldState: { error } }) => {
- let message = phoneMessage;
- return (<PhoneInput
- key={"phone"}
- className={classNames(styles.input_box, { 'input-control--error': error })}
- value={value}
- message={message}
- onChange={
- e => {
- onChange(e.target.value);
- setPhoneValid(validatePhone(e.target.value));
- }
- }
- onChangeCountry={(country) => {
- setCountry(country);
- setPhoneValid(validatePhone(control._formValues.phone, country));
- if (onCountryChange != null) onCountryChange(country);
- }}
- placeholder={i18n.t('login.phone_ph')}
- innerRef={ref}
- {...options}
- />)
- }}
- />
- <Controller control={control}
- name="code"
- rules={{ required: true, minLength: CODE_MIN, maxLength: CODE_MAX }}
- render={({ field: { ref, onChange, value }, fieldState: { error } }) => {
- return (
- <div className={`d-flex flex-column g-6`}>
- <div
- className={`${styles.sms_box} `}>
- <input
- type="text"
- inputMode="numeric"
- autoComplete="off"
- className={styles.input}
- ref={ref}
- maxLength={CODE_MAX}
- onChange={e => {
- e.target.value = e.target.value.replace(/\D+/g, '');
- onChange(e.target.value)
- }}
- placeholder={i18n.t('login.otp_ph')}
- />
- <button type='button'
- disabled={!phoneValid || otpCD > 0 || otpStatus === REQ_STATUS.LOADING}
- className={styles.otp_btn}
- onClick={e => {
- onSendCode(e);
- }}>
- {
- otpStatus === REQ_STATUS.LOADING ? (
- <CircularProgress color='inherit' size={22} thickness={5} />) : (
- <>
- {
- otpCD > 0
- ? i18n.t('login.btn_otp_cd', { times: otpCD })
- : (
- sendCount > 0 ? i18n.t("login.btn_otp_re") : i18n.t("login.btn_otp")
- )
- }
- </>
- )
- }
- </button>
- </div>
- </div>
- )
- }}
- />
- <div className={styles.tips}>
- <AlertIcon />
- <span>{i18n.t('login.otp_tips')}</span>
- </div>
- <MainButton
- disabled={!phoneValid || checkCode()}
- className={styles.btn_claim}
- loading={handleStatus === REQ_STATUS.LOADING}
- onClick={checkFormOnClaim}>
- <span>{buttonText}</span>
- {children}
- </MainButton>
- </form>
- )
- }
- export default PhoneSMS;
|