Procházet zdrojové kódy

绑定手机 验证码登录

zhejiushizhuce před 2 roky
rodič
revize
a6a7a2a254

+ 1 - 1
src/App.js

@@ -291,7 +291,7 @@ const App = () => {
             if (global.installed===1)return;
             global.installed=1;
             let time=promoteInstall*1000;
-            global.showBindPhone();
+            // global.showBindPhone();
             // time=1000;
             setTimeout(async ()=>{
                 // if (window.progressier==null)return ;

+ 6 - 0
src/api/config.js

@@ -95,3 +95,9 @@ export const APP_FAVO_REMOVE=BASE_PREFIX+"favo/remove";
 export const APP_FAVO_GET=BASE_PREFIX+"favo/get";
 //gameInfo
 export const APP_GAME_CARD_INFO=BASE_PREFIX+"gameinfo";
+//otp
+export const APP_SEND_CODE=BASE_PREFIX+"send_code";
+//bind sms
+export const APP_BIND_SMS=BASE_PREFIX+"bindsms";
+//smslogin
+export const APP_SMS_LOGIN=BASE_PREFIX+"smslogin";

+ 29 - 0
src/assets/singlesvg/userupgrade.svg

@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg fill="#000000" height="800px" width="800px" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" 
+	 viewBox="0 0 339.131 339.131" xml:space="preserve">
+<g>
+	<path d="M238.471,47.438c-2.438,0-4.845,0.127-7.232,0.328v-21.37C231.239,11.841,219.398,0,204.843,0H41.711
+		C27.156,0,15.315,11.841,15.315,26.396v286.338c0,14.555,11.841,26.396,26.396,26.396h163.132
+		c14.555,0,26.396-11.841,26.396-26.396v-94.926c2.401,0.204,4.812,0.319,7.232,0.319c47.059,0,85.345-38.285,85.345-85.344
+		C323.815,85.724,285.53,47.438,238.471,47.438z M101.311,25.249h38.179c4.694,0,8.5,3.806,8.5,8.5s-3.806,8.5-8.5,8.5h-38.179
+		c-4.694,0-8.5-3.806-8.5-8.5S96.616,25.249,101.311,25.249z M131.777,304.682c0,4.694-3.806,8.5-8.5,8.5
+		c-4.694,0-8.5-3.806-8.5-8.5v-8.501h17V304.682z M214.239,279.181H32.315V66.232c0-5.181,4.215-9.396,9.396-9.396h157.928
+		c-27.582,14.156-46.511,42.87-46.511,75.947c0,10.11,1.752,19.982,5.213,29.395l-13.984,55.138
+		c-0.866,3.417,0.13,7.037,2.622,9.529c1.899,1.899,4.452,2.929,7.072,2.929c0.818,0,1.644-0.101,2.458-0.307l57.731-14.64V279.181z
+		 M238.471,198.127c-6.952,0-13.831-1.104-20.444-3.282c-1.806-0.595-3.743-0.662-5.585-0.195l-44.57,11.302l10.643-41.963
+		c0.529-2.087,0.371-4.289-0.452-6.278c-3.275-7.923-4.935-16.31-4.935-24.928c0-36.031,29.313-65.345,65.343-65.345
+		c36.031,0,65.345,29.313,65.345,65.345C303.815,168.814,274.502,198.127,238.471,198.127z"/>
+	<path d="M240.057,123.186c-10.232-3.1-12.166-4.476-12.166-8.661c0-4.104,6.754-5.198,10.749-5.198
+		c3.844,0,8.417,1.22,11.123,2.966c0.571,0.369,1.266,0.496,1.929,0.353c0.665-0.143,1.247-0.544,1.615-1.115l5.287-8.185
+		c0.369-0.571,0.495-1.266,0.354-1.931c-0.145-0.665-0.545-1.247-1.116-1.616c-3.253-2.101-7.365-3.695-11.76-4.579V87.13
+		c0-1.416-1.147-2.564-2.564-2.564l-9.745-0.001c0,0,0,0,0,0c-0.68,0-1.332,0.271-1.813,0.751c-0.482,0.481-0.752,1.133-0.752,1.813
+		l-0.001,8.031c-11.12,2.235-18.18,9.657-18.18,19.365c0.002,16.012,13.678,20.153,22.728,22.895
+		c9.965,3.018,11.376,4.325,11.349,7.593c0,0.825-0.192,1.801-1.605,2.779c-1.841,1.274-4.982,2.007-8.62,2.01
+		c-0.018,0-0.038,0-0.058,0c-4.728-0.007-10.033-2.133-12.91-5.172c-0.972-1.029-2.595-1.074-3.625-0.1l-7.077,6.697
+		c-1.028,0.974-1.074,2.597-0.101,3.625c4.474,4.728,11.112,8.125,18.099,9.334v7.813c0,1.416,1.148,2.564,2.564,2.564h9.745
+		c1.416,0,2.564-1.148,2.564-2.564v-8.447c2.98-0.785,5.625-1.968,7.881-3.53c5.168-3.576,8.015-8.892,8.014-14.948
+		C262.094,129.859,248.833,125.843,240.057,123.186z"/>
+</g>
+</svg>

+ 85 - 9
src/features/api.js

@@ -1,24 +1,54 @@
 import {createAsyncThunk} from "@reduxjs/toolkit";
 import {
-    APP_ACCOUNTS_PAY_INFO, APP_AGENT_CHECK, APP_AGENT_GET, APP_AGENT_HISTORY,
+    APP_ACCOUNTS_PAY_INFO,
+    APP_AGENT_CHECK,
+    APP_AGENT_GET,
+    APP_AGENT_HISTORY,
+    APP_BIND_SMS,
     APP_CHANGE_EMAIL,
     APP_CHANGE_INFO,
     APP_CHANGE_PASS,
     APP_CHANGE_PAY_PASS,
-    APP_CHANGE_PHONE, APP_CHECKIN_CHECK, APP_CHECKIN_GET, APP_CUSTOMER_LEAVEMSG,
-    APP_CUSTOMER_SERVICE, APP_FAVO_ADD, APP_FAVO_GET, APP_FAVO_REMOVE,
-    APP_FIRSTPAY_CONFIG, APP_GAME_ACTIVITY, APP_GAME_CARD_INFO,
+    APP_CHANGE_PHONE,
+    APP_CHECKIN_CHECK,
+    APP_CHECKIN_GET,
+    APP_CUSTOMER_LEAVEMSG,
+    APP_CUSTOMER_SERVICE,
+    APP_FAVO_ADD,
+    APP_FAVO_GET,
+    APP_FAVO_REMOVE,
+    APP_FIRSTPAY_CONFIG,
+    APP_GAME_ACTIVITY,
+    APP_GAME_CARD_INFO,
     APP_GAME_INFO,
     APP_GAME_LIST,
     APP_LOGIN,
-    APP_LOGOUT, APP_MAIL_CHECK, APP_MAIL_DEL, APP_MAIL_LIST, APP_MAIL_READ, APP_MAIL_TAKE,
-    APP_PAGE_MODULES, APP_PAY_ORDER_LIST,
+    APP_LOGOUT,
+    APP_MAIL_CHECK,
+    APP_MAIL_DEL,
+    APP_MAIL_LIST,
+    APP_MAIL_READ,
+    APP_MAIL_TAKE,
+    APP_PAGE_MODULES,
+    APP_PAY_ORDER_LIST,
     APP_PAYMENT_ORDER,
-    APP_RECHARGE_CONFIG, APP_REDPACK_CHECK, APP_REDPACK_CONFIG, APP_REDPACK_RECEIVE,
+    APP_RECHARGE_CONFIG,
+    APP_REDPACK_CHECK,
+    APP_REDPACK_CONFIG,
+    APP_REDPACK_RECEIVE,
     APP_REGISTER,
-    APP_ROUTES, APP_TURNPLATE_BONUS, APP_TURNPLATE_HELP, APP_TURNPLATE_INDEX, APP_TURNPLATE_KAN,
+    APP_ROUTES,
+    APP_SEND_CODE,
+    APP_SMS_LOGIN,
+    APP_TURNPLATE_BONUS,
+    APP_TURNPLATE_HELP,
+    APP_TURNPLATE_INDEX,
+    APP_TURNPLATE_KAN,
     APP_WITHDRAW_BASE_INFO,
-    APP_WITHDRAW_INFO, APP_WITHDRAW_PAYOUT, APP_WITHDRAW_RECORD, APP_WITHDRAW_SET_INFO,
+    APP_WITHDRAW_INFO,
+    APP_WITHDRAW_PAYOUT,
+    APP_WITHDRAW_RECORD,
+    APP_WITHDRAW_SET_INFO,
     APP_WITHDRAW_TAX,
     REQ_STATUS
 } from "@api/config";
@@ -739,4 +769,50 @@ export const reqGameCardInfo=createAsyncThunk(
         return response;
     }
 )
+//otp
+export const reqSendCode=createAsyncThunk(
+    APP_SEND_CODE,
+    async (arg,thunkAPI)=>{
+        let params={
+            url:APP_SEND_CODE,
+            data:{
+                Phone:arg.phone
+            }
+        }
+        const response=await Http.post(params);
+        return response;
+    }
+)
+//bind sms
+export const reqBindSMS=createAsyncThunk(
+    APP_BIND_SMS,
+    async (arg,thunkAPI)=>{
+        let params={
+            url:APP_BIND_SMS,
+            data:{
+                phone:arg.phone,
+                code:arg.code,
+                password:arg.password,
+                repassword:arg.repassword
+            }
+        }
+        const response=await Http.post(params);
+        return response;
+    }
+)
+//smslogin
+export const reqSMSLogin=createAsyncThunk(
+    APP_SMS_LOGIN,
+    async (arg,thunkAPI)=>{
+        let params={
+            url:APP_SMS_LOGIN,
+            data:{
+                phone:arg.phone,
+                code:arg.code
+            }
+        }
+        const response=await Http.post(params);
+        return response;
+    }
+)
 //api

+ 52 - 3
src/features/mainSlice.js

@@ -12,13 +12,13 @@ import {
 } from "@constants/routes";
 import {REQ_STATUS} from "@api/config";
 import {
-    buildReqReducer, reqChangeEmail,
+    buildReqReducer, reqBindSMS, reqChangeEmail,
     reqChangeInfo,
     reqChangePass, reqChangePhone, reqCustomLeaveMsg, reqCustomService, reqFavoGet, reqGameCardInfo, reqGameInfo,
     reqGetRoutes,
     reqLogin,
     reqLogout,
-    reqRegister, reqUpdateFavoInfo
+    reqRegister, reqSendCode, reqSMSLogin, reqUpdateFavoInfo
 } from "@features/api";
 import {toast} from 'react-toastify';
 import {getHttpMsg, localStorageGet, localStorageSet} from "@utils/helpers";
@@ -405,6 +405,36 @@ const reducerGameCardInfo=(state,action,arg)=>{
 const gameCardInfoStatus=(state,status,arg)=>{
     state.gameCardInfoStatus=status;
 }
+//send code
+const reducerSendCode=(state,action,arg)=>{
+    let payload=action.payload;
+    let msg=getHttpMsg(payload);
+    if (payload&&payload.code===200){
+        toast.success(msg);
+        state.otpTimeStamp=Date.now();
+    }
+    else
+    {
+        toast.error(msg);
+    }
+}
+const sendCodeStatus=(state,status,arg)=>{
+    state.loginStatus.send_code=status;
+}
+//bind phone
+const reducerBindSMS=(state,action,arg)=>{
+    parseLogin(state,action,arg);
+}
+const bindSMSStatus=(state,status,arg)=>{
+    state.loginStatus.bind=status;
+}
+//sms login
+const reducerSMSLogin=(state,action,arg)=>{
+    parseLogin(state,action,arg);
+}
+const smsLoginStatus=(state,status,arg)=>{
+    state.loginStatus.smslogin=status;
+}
 //custom
 export const Main=createSlice({
     name: 'main',
@@ -419,6 +449,9 @@ export const Main=createSlice({
             login:REQ_STATUS.NONE,
             register:REQ_STATUS.NONE,
             logout:REQ_STATUS.NONE,
+            bind:REQ_STATUS.NONE,
+            send_code:REQ_STATUS.NONE,
+            smslogin:REQ_STATUS.NONE,
         },
         userInfo: null,
         isLogin:0,
@@ -439,7 +472,8 @@ export const Main=createSlice({
         promoteInstall:0,
         openPay:0,
         gameCardInfoStatus:REQ_STATUS.NONE,
-        gameCardInfo:{}
+        gameCardInfo:{},
+        otpTimeStamp:0,
     },
     reducers: {
         logout:(state, action)=>{
@@ -529,6 +563,9 @@ export const Main=createSlice({
         buildReqReducer(builder,reqUpdateFavoInfo,reducerUpdateFavoInfo,null);
         buildReqReducer(builder,reqFavoGet,reducerFavoGet,null);
         buildReqReducer(builder,reqGameCardInfo,reducerGameCardInfo,gameCardInfoStatus);
+        buildReqReducer(builder,reqSendCode,reducerSendCode,sendCodeStatus);
+        buildReqReducer(builder,reqBindSMS,reducerBindSMS,bindSMSStatus);
+        buildReqReducer(builder,reqSMSLogin,reducerSMSLogin,smsLoginStatus);
     },
 })
 export const {logout,updateUserInfo,updateUserScore,updateAddScore,updateGameLike,updateCurrGame,updateServerKick,updateOpenPay,updateGameRoute,updateGameCardInfo} = Main.actions;
@@ -629,6 +666,18 @@ export const getRegStatus=(state)=>{
 export const getLogoutStatus=(state)=>{
     return state.main.loginStatus.logout;
 }
+export const getSendCodeStatus=(state)=>{
+    return state.main.loginStatus.send_code;
+}
+export const getBindSMSStatus=(state)=>{
+    return state.main.loginStatus.bind;
+}
+export const getSMSLoginStatus=(state)=>{
+    return state.main.loginStatus.smslogin;
+}
+export const getSendCodeTS=(state)=>{
+    return state.main.otpTimeStamp;
+}
 //modify
 export const getChangeInfoStatus=(state)=>{
     return state.main.modifyStatus.info;

+ 2 - 1
src/locales/languages/en_US.json

@@ -150,7 +150,8 @@
     "email": "Email",
     "security": "Security",
     "install_banner": "Show Install Notice",
-    "sound": "Sound"
+    "sound": "Sound",
+    "bindPhone": "Account Upgrade"
   },
   "copy_success": "Copy successful",
   "user": {

+ 15 - 2
src/locales/languages/es_MX.json

@@ -105,7 +105,13 @@
       "empty": "¡Por favor, ingresa la contraseña!"
     },
     "success": "Inicio de sesión exitoso, ¡bienvenido de nuevo!",
-    "notfound": "Tu cuenta no fue encontrada, por favor regístrate o intenta nuevamente!"
+    "notfound": "Tu cuenta no fue encontrada, por favor regístrate o intenta nuevamente!",
+    "login_type": {
+      "password": "Contraseña",
+      "otp": "OTP"
+    },
+    "otp_ph": "Código de verificación",
+    "guest_login": "Acceso de usuario invitado"
   },
   "logout": {
     "success": "Has salido exitosamente."
@@ -141,7 +147,8 @@
     "email": "Correo Electrónico",
     "security": "Seguridad",
     "install_banner": "Mostrar Aviso de Instalación",
-    "sound": "Sonido"
+    "sound": "Sonido",
+    "bindPhone": "Actualización de cuenta"
   },
   "copy_success": "Copia exitosa",
   "user": {
@@ -591,6 +598,12 @@
     "btn_cancel": "Cancelar",
     "btn_install": "Instalar"
   },
+  "bindPhone": {
+    "title_link_phone": "Actualización de la Cuenta",
+    "phone_award": "Premio:",
+    "btn_submit": "ENVIAR",
+    "tips": "Vincula tu móvil y obtén una recompensa de 5 fichas"
+  },
   "web": {
     "activity": {
       "redpack_not_available": "No hay RedPack",

+ 15 - 2
src/locales/languages/pt_BR.json

@@ -105,7 +105,13 @@
       "empty": "Por favor, insira a senha!"
     },
     "success": "Login bem-sucedido, bem-vindo de volta!",
-    "notfound": "Sua conta não foi encontrada, por favor registre-se ou tente novamente!"
+    "notfound": "Sua conta não foi encontrada, por favor registre-se ou tente novamente!",
+    "login_type": {
+      "password": "Senha",
+      "otp": "OTP"
+    },
+    "otp_ph": "Código de verificação",
+    "guest_login": "Login como convidado"
   },
   "logout": {
     "success": "Você saiu com sucesso."
@@ -141,7 +147,8 @@
     "email": "E-mail",
     "security": "Segurança",
     "install_banner": "Mostrar Aviso de Instalação",
-    "sound": "Som"
+    "sound": "Som",
+    "bindPhone": "Atualização de conta"
   },
   "copy_success": "Cópia bem-sucedida",
   "user": {
@@ -591,6 +598,12 @@
     "btn_cancel": "Cancelar",
     "btn_install": "Instalar"
   },
+  "bindPhone": {
+    "title_link_phone": "Atualização da Conta",
+    "phone_award": "Prêmio:",
+    "btn_submit": "ENVIAR",
+    "tips": "Vincule seu celular e ganhe 5 fichas de recompensa"
+  },
   "web": {
     "activity": {
       "redpack_not_available": "Não há RedPack",

+ 15 - 2
src/locales/languages/ru_RU.json

@@ -105,7 +105,13 @@
       "empty": "Пожалуйста, введите пароль!"
     },
     "success": "Успешный вход, добро пожаловать обратно!",
-    "notfound": "Ваш аккаунт не найден, пожалуйста, зарегистрируйтесь или попробуйте снова!"
+    "notfound": "Ваш аккаунт не найден, пожалуйста, зарегистрируйтесь или попробуйте снова!",
+    "login_type": {
+      "password": "Пароль",
+      "otp": "ОТП"
+    },
+    "otp_ph": "Проверочный код",
+    "guest_login": "Гостевой вход"
   },
   "logout": {
     "success": "Вы успешно вышли."
@@ -141,7 +147,8 @@
     "email": "Электронная почта",
     "security": "Безопасность",
     "install_banner": "Показать уведомление об установке",
-    "sound": "Звук"
+    "sound": "Звук",
+    "bindPhone": "Обновление учетной записи"
   },
   "copy_success": "Копирование успешно",
   "user": {
@@ -591,6 +598,12 @@
     "btn_cancel": "Отмена",
     "btn_install": "Установить"
   },
+  "bindPhone": {
+    "title_link_phone": "Повышение учетной записи",
+    "phone_award": "Награда:",
+    "btn_submit": "ПОДТВЕРДИТЬ",
+    "tips": "Свяжите свой мобильный телефон и получите награду - 5 фишек"
+  },
   "web": {
     "activity": {
       "redpack_not_available": "Нет RedPack",

+ 15 - 2
src/locales/languages/zh_TW.json

@@ -105,7 +105,13 @@
       "empty": "請輸入密碼!"
     },
     "success": "登入成功,歡迎回來!",
-    "notfound": "未找到您的帳戶,請註冊或重試!"
+    "notfound": "未找到您的帳戶,請註冊或重試!",
+    "login_type": {
+      "password": "密碼",
+      "otp": "驗證碼"
+    },
+    "otp_ph": "驗證碼",
+    "guest_login": "訪客登入"
   },
   "logout": {
     "success": "您已成功退出。"
@@ -141,7 +147,8 @@
     "email": "電子郵件",
     "security": "安全",
     "install_banner": "顯示安裝通知",
-    "sound": "聲音"
+    "sound": "聲音",
+    "bindPhone": "帳號升級"
   },
   "copy_success": "複製成功",
   "user": {
@@ -591,6 +598,12 @@
     "btn_cancel": "取消",
     "btn_install": "安裝"
   },
+  "bindPhone": {
+    "title_link_phone": "帳戶升級",
+    "phone_award": "獎勵:",
+    "btn_submit": "提交",
+    "tips": "連接您的手機並獲得 5 積分獎勵"
+  },
   "web": {
     "activity": {
       "redpack_not_available": "沒有紅包",

+ 78 - 7
src/widgets/LoginForm/index.jsx

@@ -19,15 +19,16 @@ import PhoneInput from "@components/UInput/PhoneInput";
 import {animated,useSpring} from "@react-spring/web";
 import {toast} from 'react-toastify';
 import {useDispatch, useSelector} from "react-redux";
-import {getLoginStatus, getRegStatus} from "@features/mainSlice";
+import {getLoginStatus, getRegStatus, getSendCodeStatus, getSendCodeTS, getSMSLoginStatus} from "@features/mainSlice";
 import {useLocation} from "react-router";
 import {useEffect} from 'react';
 import SmallLoading from "@components/SmallLoading";
 import {REQ_STATUS} from "@api/config";
-import {reqLogin, reqRegister} from "@features/api";
+import {reqLogin, reqRegister, reqSendCode, reqSMSLogin} from "@features/api";
 import {ROUTE_PATH} from "@constants/routes";
 import {APP_THEME} from "@constants/app";
 import {validateCountryPhone} from "@utils/helpers";
+import {CircularProgress} from "@mui/material";
 
 const i18n=global.i18n;
 const TYPE_LOGIN="login";
@@ -35,6 +36,9 @@ const TYPE_REGISTER="register";
 
 const LOGIN_TYPE_PASS=0;
 const LOGIN_TYPE_OTP=1;
+const objInfo={
+    cdTimer:null
+}
 
 const LoginForm = ({close}) => {
     const {theme,isMobile} = useThemeProvider();
@@ -55,13 +59,51 @@ const LoginForm = ({close}) => {
             email:"",
             repassword:"",
             lang:"",
-            code:""
+            code:"",
+            phone:""
         }
     });
     const {pathname,hash} = useLocation();
+    const otpTimeStamp=useSelector(getSendCodeTS);
+    const [otpCD,setOtpCD]=useState(-1);
+    const otpStatus=useSelector(getSendCodeStatus);
+    const smsLoginStatus=useSelector(getSMSLoginStatus);
+    const onSendCode=(e)=>{
+        if (otpCD>0)return;
+        if (otpStatus===REQ_STATUS.LOADING)return;
+        let phone=control._formValues.phone;
+        if (phone==null||phone==="")return;
+        let validate=validatePhone(phone);
+        if (!validate){
+            control.setError("phone",true)
+            return;
+        }
+        let params={phone:phone};
+        dispatch(reqSendCode(params));
+    }
+    const getLeftCD=()=>{
+        let now=Date.now();
+        let delta=now-otpTimeStamp;
+        delta=Math.floor(delta/1000);
+        delta=60-delta;
+        if (delta<0)return -1;
+        else return delta;
+    }
+    const clearTimer=()=>{
+        if (objInfo.cdTimer!=null){
+            clearInterval(objInfo.cdTimer);
+            objInfo.cdTimer=null;
+        }
+    }
+    const onSubmitOTPLogin=(data)=>{
+        if (smsLoginStatus===REQ_STATUS.LOADING)return;
+        data.callback=onCallback;
+        dispatch(reqSMSLogin(data));
+    }
     useEffect(()=>{
        return ()=>{
            global.authLockPath=null;
+           clearTimer();
        }
     },[])
     useEffect(() => {
@@ -72,6 +114,20 @@ const LoginForm = ({close}) => {
             setShowType(TYPE_REGISTER);
         }
     }, [pathname,hash]);
+    useEffect(()=>{
+        let left=getLeftCD();
+        setOtpCD(left);
+        clearTimer();
+        if (left>0){
+            objInfo.cdTimer=setInterval(()=>{
+                left=getLeftCD();
+                setOtpCD(left);
+                if (left<0){
+                    clearTimer();
+                }
+            },1000)
+        }
+    },[otpTimeStamp])
     const navigatRoute=(route)=>{
         navigate(route,{replace:true});
     }
@@ -89,11 +145,13 @@ const LoginForm = ({close}) => {
         }
     }
     const onSubmitLogin=(data)=>{
+        if (loginStatus===REQ_STATUS.LOADING)return;
         data.country=country;
         data.callback=onCallback;
         dispatch(reqLogin(data));
     }
     const onSubmitReg=(data)=>{
+        if (regStatus===REQ_STATUS.LOADING)return;
         data.country=country;
         data.callback=onCallback;
         dispatch(reqRegister(data));
@@ -180,10 +238,10 @@ const LoginForm = ({close}) => {
     const renderOTPLogin=()=>{
         return (
             <form autoComplete="off" className={styles.l_form} onSubmit={handleSubmit((data) => {
-                onSubmitLogin(data);
+                onSubmitOTPLogin(data);
             })}>
                 <Controller control={control}
-                            name="account"
+                            name="phone"
                             rules={{required: true}}
                             render={({field: {ref, onChange, value}, fieldState: {error}}) => {
                                 let message = error ? i18n.t("form_errors.phone_" + country) : "";
@@ -214,7 +272,20 @@ const LoginForm = ({close}) => {
                                                 onChange={e => onChange(e.target.value)}
                                                 placeholder={i18n.t('login.otp_ph')}
                                             />
-                                            <button type={"button"} className={styles.otp_btn}>OTP</button>
+                                            <button type={"button"}
+                                                    className={`${styles.otp_btn} ${otpCD > 0 ? styles.cd_timer : ''}`}
+                                                    onClick={onSendCode}>
+                                                {
+                                                    otpStatus === REQ_STATUS.LOADING ? (
+                                                        <CircularProgress color="success" size={22} thickness={5}/>) : (
+                                                        <>
+                                                            {
+                                                                otpCD <= 0 ? "OTP" : otpCD + "S"
+                                                            }
+                                                        </>
+                                                    )
+                                                }
+                                            </button>
                                         </div>
                                     </div>
                                 )
@@ -222,7 +293,7 @@ const LoginForm = ({close}) => {
                 />
                 <button className={"button-green"}>
                     {
-                        loginStatus === REQ_STATUS.LOADING ? renderLoading() : i18n.t("login.sign_in_btn")
+                        smsLoginStatus === REQ_STATUS.LOADING ? renderLoading() : i18n.t("login.sign_in_btn")
                     }
                 </button>
                 {renderGuestLogin()}

+ 3 - 0
src/widgets/LoginForm/styles.module.scss

@@ -114,6 +114,9 @@
   color: var(--primary-color);
   text-align: right;
   font-size: 1rem;
+  &.cd_timer{
+    color:var(--text);
+  }
 }
 .guest_btn{
   display: flex;

+ 23 - 0
src/widgets/UserSettings/index.jsx

@@ -17,6 +17,7 @@ import {ReactComponent as EmailIcon} from '@assets/singlesvg/email.svg';
 import {ReactComponent as InstallIcon} from "@assets/singlesvg/install.svg";
 import {ReactComponent as SoundOnIcon} from "@assets/singlesvg/sound-on.svg";
 import {ReactComponent as SoundOffIcon} from "@assets/singlesvg/sound-off.svg";
+import {ReactComponent as BindPhone} from "@assets/singlesvg/userupgrade.svg";
 import {CopyToClipboard} from 'react-copy-to-clipboard';
 import {toast} from "react-toastify";
 import ToggleButtonGroup from "@mui/material/ToggleButtonGroup";
@@ -319,10 +320,32 @@ const UserSettings=()=>{
             </div>
         )
     }
+    const renderBindPhone=()=>{
+        return (
+            <>
+                {
+                    isLogin?(
+                        <button className={styles.action_item} onClick={()=>{
+                            global.showBindPhone();
+                        }}>
+                            <div className={styles.action_left}>
+                                <BindPhone className={styles.icon}></BindPhone>
+                                <div>{i18n.t("settings.bindPhone")}</div>
+                            </div>
+                            <div className={styles.action_right}>
+                                <SvgIcon className={styles.icon} herf={"icon_Arrow"}></SvgIcon>
+                            </div>
+                        </button>
+                    ):(<></>)
+                }
+            </>
+        )
+    }
     const renderSetting=()=>{
         return (
             <div className={`${styles.wrap_item} ${styles.action_wrap}`}>
                 {renderItemSecurity()}
+                {renderBindPhone()}
                 {renderItemInstallBanner()}
                 {renderItemSound()}
                 {renderItemLang()}

+ 81 - 3
src/widgets/WBindPhone/index.jsx

@@ -8,11 +8,25 @@ import classNames from "classnames";
 import PasswordInput from "@components/UInput/PasswordInput";
 import {validateCountryPhone} from "@utils/helpers";
 import {REQ_STATUS} from "@api/config";
+import {useDispatch, useSelector} from "react-redux";
+import {getBindSMSStatus, getSendCodeStatus, getSendCodeTS} from "@features/mainSlice";
+import SmallLoading from "@components/SmallLoading";
+import {CircularProgress} from "@mui/material";
+import {toast} from "react-toastify";
+import {reqBindSMS, reqSendCode} from "@features/api";
 const i18n=global.i18n;
+const objInfo={
+    cdTimer:null
+}
 const WBindPhone=({})=>{
     const [isShow, setIsShow] = useState(false);
     const regCoins=5;
     const [country, setCountry] = useState("55");
+    const otpTimeStamp=useSelector(getSendCodeTS);
+    const [otpCD,setOtpCD]=useState(-1);
+    const otpStatus=useSelector(getSendCodeStatus);
+    const bindStatus=useSelector(getBindSMSStatus);
+    const dispatch=useDispatch();
     const {register, handleSubmit, formState: {errors}, control} = useForm({
         defaultValues: {
             phone: '',
@@ -31,8 +45,43 @@ const WBindPhone=({})=>{
     const validatePhone=(value)=>{
         return validateCountryPhone(country,value);
     }
+    const onBindCB=(code)=>{
+        if (code===200){
+            hide();
+        }
+    }
     const onSubmitBind=(data)=>{
-
+        if (bindStatus===REQ_STATUS.LOADING)return;
+        let params=data;
+        params.callback=onBindCB;
+        dispatch(reqBindSMS(params));
+    }
+    const onSendCode=(e)=>{
+        if (otpCD>0)return;
+        if (otpStatus===REQ_STATUS.LOADING)return;
+        let phone=control._formValues.phone;
+        if (phone==null||phone==="")return;
+        let validate=validatePhone(phone);
+        if (!validate){
+            control.setError("phone",true)
+            return;
+        }
+        let params={phone:phone};
+        dispatch(reqSendCode(params));
+    }
+    const getLeftCD=()=>{
+        let now=Date.now();
+        let delta=now-otpTimeStamp;
+        delta=Math.floor(delta/1000);
+        delta=60-delta;
+        if (delta<0)return -1;
+        else return delta;
+    }
+    const clearTimer=()=>{
+        if (objInfo.cdTimer!=null){
+            clearInterval(objInfo.cdTimer);
+            objInfo.cdTimer=null;
+        }
     }
     useEffect(()=>{
         const bindPhone={
@@ -40,7 +89,24 @@ const WBindPhone=({})=>{
             hide
         };
         global.bindPhone=bindPhone;
+        return ()=>{
+            clearTimer();
+        }
     },[])
+    useEffect(()=>{
+        let left=getLeftCD();
+        setOtpCD(left);
+        clearTimer();
+        if (left>0){
+            objInfo.cdTimer=setInterval(()=>{
+                left=getLeftCD();
+                setOtpCD(left);
+                if (left<0){
+                    clearTimer();
+                }
+            },1000)
+        }
+    },[otpTimeStamp])
     return (
        <Popup onClose={hide} open={isShow}>
            <div className={styles.container}>
@@ -113,7 +179,17 @@ const WBindPhone=({})=>{
                                                    onChange={e => onChange(e.target.value)}
                                                    placeholder={i18n.t('login.otp_ph')}
                                                />
-                                               <button type={"button"} className={styles.otp_btn}>OTP</button>
+                                               <button type={"button"} className={`${styles.otp_btn} ${otpCD>0?styles.cd_timer:''}`} onClick={onSendCode}>
+                                                   {
+                                                       otpStatus===REQ_STATUS.LOADING?(<CircularProgress color="success" size={22} thickness={5} />):(
+                                                           <>
+                                                               {
+                                                                   otpCD<=0?"OTP":otpCD+"S"
+                                                               }
+                                                           </>
+                                                       )
+                                                   }
+                                               </button>
                                            </div>
                                        </div>
                                    )
@@ -121,7 +197,9 @@ const WBindPhone=({})=>{
                    />
                    <button className={"button-green"}>
                        {
-                           i18n.t("bindPhone.btn_submit")
+                           bindStatus===REQ_STATUS.LOADING?(<SmallLoading fill={"#fff"}></SmallLoading>):(
+                               i18n.t("bindPhone.btn_submit")
+                           )
                        }
                    </button>
                    <span className={styles.tips}>{i18n.t("bindPhone.tips")}</span>

+ 3 - 0
src/widgets/WBindPhone/styles.module.scss

@@ -40,6 +40,9 @@
       color: var(--primary-color);
       text-align: right;
       font-size: 1rem;
+      &.cd_timer{
+        color:var(--text);
+      }
     }
     & .tips{
       display: flex;