zhejiushizhuce 2 роки тому
батько
коміт
aad1c0f307

+ 4 - 4
src/Global.js

@@ -1,6 +1,6 @@
 import i18n from './locales/index';
-import symbol from "@assets/symbol-defs.svg";
-import symbol_m from "@assets/symbol-mobile.svg";
+// import symbol from "@assets/symbol-defs.svg";
+// import symbol_m from "@assets/symbol-mobile.svg";
 global.i18n=i18n;
-global.symbol=symbol;
-global.symbol_m=symbol_m;
+// global.symbol=symbol;
+// global.symbol_m=symbol_m;

+ 9 - 0
src/api/config.js

@@ -8,6 +8,8 @@ export const REQ_STATUS={
 
 // export const BASE_API="https://api.24680.com/";
 export const BASE_API="https://api.ouro777.com/";
+
+export const APP_AVATAR="https://cdn.moeda777.com/24680/assets/avatar/1/";
 // export const APP_ROUTES="routes";
 //路由
 export const APP_ROUTES="game/routes";
@@ -21,4 +23,11 @@ export const APP_LOGIN="game/login";
 export const APP_REGISTER="game/register";
 //logout
 export const APP_LOGOUT="game/logout";
+//changePass
+export const APP_CHANGE_PASS="game/changePass";
+//changeInfo
+export const APP_CHANGE_INFO="game/changeInfo";
+//增加/game/changePhone /game/changeEmail
+export const APP_CHANGE_PHONE="game/changePhone";
+export const APP_CHANGE_EMAIL="game/changeEmail";
 

+ 18 - 2
src/api/request.js

@@ -2,14 +2,28 @@
 // Create axios instance
 import {BASE_API} from "./config";
 import axios from "axios";
-import {localStorageGet} from "@utils/helpers";
+import {getHttpMsg, localStorageGet} from "@utils/helpers";
 import {LOCAL_SIGN} from "@constants/localkey";
+import {toast} from "react-toastify";
 
 // axios.defaults.withCredentials = true
 const getSign=()=>{
     const sign=localStorageGet(LOCAL_SIGN,"");
     return sign;
 }
+const checkResponse=(data)=>{
+    if (data){
+        //登录失效
+        if (data.code===404){
+            let msg=getHttpMsg(data);
+            toast.error(msg,{
+                onClose: ()=>{
+                    window.location.replace(window.location.origin)
+                }
+            });
+        }
+    }
+}
 const service = axios.create({
     baseURL: BASE_API,
     withCredentials: true,
@@ -33,6 +47,8 @@ service.interceptors.request.use(
 service.interceptors.response.use(
     response => {
         // console.log(response);
+        // console.log(response.data)
+        checkResponse(response.data);
         return response.data;
     },
     error => {
@@ -60,7 +76,7 @@ const get=(params={})=>{
 const post=(params={})=>{
     let sign=getSign();
     if (sign!=null&&sign!=""){
-        params.sign=getSign();
+        if (params.data)params.data.sign=getSign();
     }
     return service({
         method: 'post',

+ 22 - 0
src/assets/singlesvg/email.svg

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
+<svg height="800px" width="800px" version="1.1" id="_x32_" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" 
+	 viewBox="0 0 512 512"  xml:space="preserve">
+<g>
+	<path class="st0" d="M510.678,112.275c-2.308-11.626-7.463-22.265-14.662-31.054c-1.518-1.915-3.104-3.63-4.823-5.345
+		c-12.755-12.818-30.657-20.814-50.214-20.814H71.021c-19.557,0-37.395,7.996-50.21,20.814c-1.715,1.715-3.301,3.43-4.823,5.345
+		C8.785,90.009,3.63,100.649,1.386,112.275C0.464,116.762,0,121.399,0,126.087V385.92c0,9.968,2.114,19.55,5.884,28.203
+		c3.497,8.26,8.653,15.734,14.926,22.001c1.59,1.586,3.169,3.044,4.892,4.494c12.286,10.175,28.145,16.32,45.319,16.32h369.958
+		c17.18,0,33.108-6.145,45.323-16.384c1.718-1.386,3.305-2.844,4.891-4.43c6.27-6.267,11.425-13.741,14.994-22.001v-0.064
+		c3.769-8.653,5.812-18.171,5.812-28.138V126.087C512,121.399,511.543,116.762,510.678,112.275z M46.509,101.571
+		c6.345-6.338,14.866-10.175,24.512-10.175h369.958c9.646,0,18.242,3.837,24.512,10.175c1.122,1.129,2.179,2.387,3.112,3.637
+		L274.696,274.203c-5.348,4.687-11.954,7.002-18.696,7.002c-6.674,0-13.276-2.315-18.695-7.002L43.472,105.136
+		C44.33,103.886,45.387,102.7,46.509,101.571z M36.334,385.92V142.735L176.658,265.15L36.405,387.435
+		C36.334,386.971,36.334,386.449,36.334,385.92z M440.979,420.597H71.021c-6.281,0-12.158-1.651-17.174-4.552l147.978-128.959
+		l13.815,12.018c11.561,10.046,26.028,15.134,40.36,15.134c14.406,0,28.872-5.088,40.432-15.134l13.808-12.018l147.92,128.959
+		C453.137,418.946,447.26,420.597,440.979,420.597z M475.666,385.92c0,0.529,0,1.051-0.068,1.515L335.346,265.221L475.666,142.8
+		V385.92z"/>
+</g>
+</svg>

+ 0 - 0
src/assets/gener/female.svg → src/assets/singlesvg/gener/female.svg


+ 0 - 0
src/assets/gener/gender.svg → src/assets/singlesvg/gener/gender.svg


+ 0 - 0
src/assets/gener/male.svg → src/assets/singlesvg/gener/male.svg


+ 19 - 0
src/assets/singlesvg/mobile.svg

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
+<svg fill="#000000" height="800px" width="800px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" 
+	 viewBox="0 0 512 512" xml:space="preserve">
+<g>
+	<g>
+		<path d="M358.4,0H153.6c-42.351,0-76.8,34.449-76.8,76.8v358.4c0,42.351,34.449,76.8,76.8,76.8h204.8
+			c42.351,0,76.8-34.449,76.8-76.8V76.8C435.2,34.449,400.751,0,358.4,0z M409.6,435.2c0,28.279-22.921,51.2-51.2,51.2H153.6
+			c-28.279,0-51.2-22.921-51.2-51.2v-25.6h307.2V435.2z M409.6,384H102.4V102.4h307.2V384z M102.4,76.8
+			c0-28.279,22.921-51.2,51.2-51.2h204.8c28.279,0,51.2,22.921,51.2,51.2H102.4z"/>
+	</g>
+</g>
+<g>
+	<g>
+		<path d="M294.4,435.2h-76.8c-7.074,0-12.8,5.726-12.8,12.8c0,7.074,5.726,12.8,12.8,12.8h76.8c7.074,0,12.8-5.726,12.8-12.8
+			C307.2,440.926,301.474,435.2,294.4,435.2z"/>
+	</g>
+</g>
+</svg>

+ 2 - 1
src/components/Logo/index.jsx

@@ -11,10 +11,11 @@ import logo_dark from '@assets/logo/logo.png';
 import logo_light from '@assets/logo/logo_w.png';
 import logo_small from '@assets/logo/logo_small3.png';
 import logo_small_black from '@assets/logo/logo_small3_black.png';
+import {APP_THEME} from "@constants/app";
 
 const Logo = ({size = 'md'}) => {
     const {theme,isMobile} = useThemeProvider();
-    const logourl=isMobile()?(theme=='dark'?logo_small:logo_small_black):(theme=='dark'?logo_dark:logo_light);
+    const logourl=isMobile()?(theme===APP_THEME.DARK?logo_small:logo_small_black):(theme===APP_THEME.DARK?logo_dark:logo_light);
     // const logourl=(theme=='dark'?logo_dark:logo_light);
     return (
         <NavLink className={`${styles.logo} ${styles[size]}`} to="/">

+ 4 - 2
src/components/SvgIcon/index.jsx

@@ -1,5 +1,7 @@
-const symboldef=global.symbol;
-const symbolmobie=global.symbol_m;
+import symbol from "@assets/symbol-defs.svg";
+import symbol_m from "@assets/symbol-mobile.svg";
+const symboldef=symbol;
+const symbolmobie=symbol_m;
 const SvgIcon=({herf,mobile=false,...props})=>{
     const symbol=mobile?symbolmobie:symboldef;
     const formatHerf=()=>{

+ 13 - 0
src/constants/app.js

@@ -0,0 +1,13 @@
+const APP_THEME={
+    DARK:"dark",
+    LIGHT:"light"
+}
+const APP_GENDER={
+    FEMALE:0,
+    MALE:1,
+    OTHER:2,
+}
+export {
+    APP_THEME,
+    APP_GENDER
+};

+ 2 - 0
src/constants/routes.js

@@ -13,6 +13,8 @@ const ROUTE_PATH={
     SIGN_IN:"#/signin",
     SIGN_UP:"#/signup",
     SETTINGS:"#/settings",
+    EDIT_PROFILE:"#/editprofile",
+    EDIT_PWD:"#/editpwd"
 }
 
 export {

+ 10 - 9
src/contexts/themeContext.js

@@ -1,6 +1,7 @@
 import {createContext, useContext, useEffect, useState} from 'react';
 import {isRtlLang} from 'rtl-detect';
 import {useWindowSize} from "react-use";
+import {APP_THEME} from "@constants/app";
 
 const ThemeContext = createContext(undefined);
 
@@ -19,7 +20,7 @@ export const ThemeProvider = ({children}) => {
     const isMobile=()=>{
         return width<768;
     }
-    const [theme, setTheme] = useState(persisted && persisted.theme ?  persisted.theme : ('dark'));
+    const [theme, setTheme] = useState(persisted && persisted.theme ?  persisted.theme : (APP_THEME.DARK));
     // const [fontScale, setFontScale] = useState(persisted.fontScale || 1);
     const [fontScale, setFontScale] = useState(isMobile()?0.88:1);
     const [direction, setDirection] = useState(persisted.direction || (isRtl ? 'rtl' : 'ltr'));
@@ -49,7 +50,7 @@ export const ThemeProvider = ({children}) => {
     }
 
     const toggleTheme = () => {
-        setTheme(theme === 'light' ? 'dark' : 'light');
+        setTheme(theme === APP_THEME.LIGHT ? APP_THEME.DARK : APP_THEME.LIGHT);
         // stopTransition();
     }
 
@@ -79,13 +80,13 @@ export const ThemeProvider = ({children}) => {
         page.setAttribute('dir', direction);
         savePreferences();
 
-        window
-            .matchMedia('(prefers-color-scheme: light)')
-            .addEventListener('change', event => {
-                event.matches ? setTheme('light') : setTheme('dark');
-                stopTransition();
-                savePreferences();
-            })
+        // window
+        //     .matchMedia('(prefers-color-scheme: light)')
+        //     .addEventListener('change', event => {
+        //         event.matches ? setTheme('light') : setTheme('dark');
+        //         stopTransition();
+        //         savePreferences();
+        //     })
         // eslint-disable-next-line react-hooks/exhaustive-deps
     }, [theme, fontScale, direction]);
 

+ 26 - 0
src/db/routes.js

@@ -152,4 +152,30 @@ export const routes=[
     //     lpath:"#/signin",//登录path
     //     subs:[]
     // },
+    // {
+    //     path: "#/editprofile",
+    //     type: 1,
+    //     side: 0,
+    //     block:1,//块排序
+    //     title: "Edit Profile",
+    //     icon:"",
+    //     fill:";;",//icon 颜色 默认 选中 本身颜色
+    //     component:"editProfile",
+    //     login:0,//0,1 检不检查登录
+    //     lpath:"#/signin",//登录path
+    //     subs:[]
+    // },
+    // {
+    //     path: "#/editpwd",
+    //     type: 1,
+    //     side: 0,
+    //     block:1,//块排序
+    //     title: "Edit Password",
+    //     icon:"",
+    //     fill:";;",//icon 颜色 默认 选中 本身颜色
+    //     component:"editPassword",
+    //     login:0,//0,1 检不检查登录
+    //     lpath:"#/signin",//登录path
+    //     subs:[]
+    // }
 ]

+ 58 - 0
src/features/api.js

@@ -1,5 +1,7 @@
 import {createAsyncThunk} from "@reduxjs/toolkit";
 import {
+    APP_CHANGE_EMAIL,
+    APP_CHANGE_INFO, APP_CHANGE_PASS, APP_CHANGE_PHONE,
     APP_GAME_LIST,
     APP_LOGIN,
     APP_LOGOUT,
@@ -117,4 +119,60 @@ export const reqLogout=createAsyncThunk(
         return response;
     },
 )
+//changeInfo
+export const reqChangeInfo=createAsyncThunk(
+    APP_CHANGE_INFO,
+    async (arg,thunkAPI)=>{
+        let data={...arg};
+        delete data.callback;
+        let params={
+            url:APP_CHANGE_INFO,
+            data:data
+        }
+        const response = await Http.post(params);
+        return response;
+    },
+)
+//changePass
+export const reqChangePass=createAsyncThunk(
+    APP_CHANGE_PASS,
+    async (arg,thunkAPI)=>{
+        let data={...arg};
+        delete data.callback;
+        let params={
+            url:APP_CHANGE_PASS,
+            data:data
+        }
+        const response = await Http.post(params);
+        return response;
+    },
+)
+//changePhone
+export const reqChangePhone=createAsyncThunk(
+    APP_CHANGE_PHONE,
+    async (arg,thunkAPI)=>{
+        let data={...arg};
+        delete data.callback;
+        let params={
+            url:APP_CHANGE_PHONE,
+            data:data
+        }
+        const response = await Http.post(params);
+        return response;
+    },
+)
+//changeEmail
+export const reqChangeEmail=createAsyncThunk(
+    APP_CHANGE_EMAIL,
+    async (arg,thunkAPI)=>{
+        let data={...arg};
+        delete data.callback;
+        let params={
+            url:APP_CHANGE_EMAIL,
+            data:data
+        }
+        const response = await Http.post(params);
+        return response;
+    },
+)
 //api

+ 95 - 16
src/features/mainSlice.js

@@ -8,9 +8,17 @@ import {
     ROUTE_TYPE_POP_OR_FULL, SIDE_TYPE_NOMAL
 } from "@constants/routes";
 import {REQ_STATUS} from "@api/config";
-import {buildReqReducer, reqGetRoutes, reqLogin, reqLogout, reqRegister} from "@features/api";
+import {
+    buildReqReducer, reqChangeEmail,
+    reqChangeInfo,
+    reqChangePass, reqChangePhone,
+    reqGetRoutes,
+    reqLogin,
+    reqLogout,
+    reqRegister
+} from "@features/api";
 import {toast} from 'react-toastify';
-import {localStorageGet, localStorageSet} from "@utils/helpers";
+import {getHttpMsg, localStorageGet, localStorageSet} from "@utils/helpers";
 import {LOCAL_IS_LOGIN, LOCAL_SIGN, LOCAL_USER_INFO} from "@constants/localkey";
 const i18n=global.i18n;
 const generateNavs=(routes)=>{
@@ -48,9 +56,9 @@ const generateRoutes=(routes,asyncRoutes=[])=>{
                     // if (route.path==="#/signin"||route.path==="#/signup"){
                     //     route.component="Login";
                     // }
-                    if (route.path==="#/settings"){
-                        route.component="settings";
-                    }
+                    // if (route.path==="#/settings"){
+                    //     route.component="settings";
+                    // }
                     allMap[route.path]=route;
                     let type=route.type;
                     if (type===ROUTE_TYPE_IN||type===ROUTE_TYPE_FULL)inRoutes.push(route);
@@ -151,16 +159,17 @@ const getLocalIsLogin=()=>{
     return islogin;
 }
 const getMsg=(payload)=>{
-    let result=payload.result;
-    if (result==null)result=payload.msg;
-    let msg="";
-    if (result!=null){
-        let key=result[0];
-        let def=result[1];
-        if (i18n.exists(key))msg=i18n.t(key);
-        else msg=def;
-    }
-    return msg;
+    // let result=payload.result;
+    // if (result==null)result=payload.msg;
+    // let msg="";
+    // if (result!=null){
+    //     let key=result[0];
+    //     let def=result[1];
+    //     if (i18n.exists(key))msg=i18n.t(key);
+    //     else msg=def;
+    // }
+    // return msg;
+    return getHttpMsg(payload);
 }
 const parseLogin=(state,action,arg)=>{
     let payload=action.payload;
@@ -218,6 +227,52 @@ const logoutStatus=(state,status,arg)=>{
     state.loginStatus.logout=status;
 }
 //login
+//modify
+const reducerChangePass=(state,action,arg)=>{
+    // let payload=action.payload;
+    // let code=payload.code;
+    // let msg=getMsg(payload);
+    // if (code==200){
+    //     toast.success(msg);
+    // }
+    // else
+    // {
+    //     toast.error(msg);
+    // }
+    parseLogin(state,action,arg);
+}
+const changePassStatus=(state,status,arg)=>{
+    state.modifyStatus.password=status;
+}
+const reducerChangeInfo=(state,action,arg)=>{
+    // let payload=action.payload;
+    // let code=payload.code;
+    // let msg=getMsg(payload);
+    // if (code==200){
+    //     toast.success(msg);
+    // }
+    // else
+    // {
+    //     toast.error(msg);
+    // }
+    parseLogin(state,action,arg);
+}
+const changeInfoStatus=(state,status,arg)=>{
+    state.modifyStatus.info=status;
+}
+const reducerChangePhone=(state,action,arg)=>{
+    parseLogin(state,action,arg);
+}
+const changePhoneStatus=(state,status,arg)=>{
+    state.modifyStatus.phone=status;
+}
+const reducerChangeEmail=(state,action,arg)=>{
+    parseLogin(state,action,arg);
+}
+const changeEmailStatus=(state,status,arg)=>{
+    state.modifyStatus.phone=status;
+}
+//modify
 export const Main=createSlice({
     name: 'main',
     initialState: {
@@ -233,7 +288,13 @@ export const Main=createSlice({
             logout:REQ_STATUS.NONE,
         },
         userInfo: {},
-        isLogin:0
+        isLogin:0,
+        modifyStatus:{
+            info:REQ_STATUS.NONE,
+            password:REQ_STATUS.NONE,
+            phone:REQ_STATUS.NONE,
+            email:REQ_STATUS.NONE
+        }
     },
     reducers: {
         logout:(state, action)=>{
@@ -250,6 +311,10 @@ export const Main=createSlice({
         buildReqReducer(builder,reqLogin,reducerLogin,loginStatus);
         buildReqReducer(builder,reqRegister,reducerReg,regStatus);
         buildReqReducer(builder,reqLogout,reducerLogout,logoutStatus);
+        buildReqReducer(builder,reqChangePass,reducerChangePass,changePassStatus);
+        buildReqReducer(builder,reqChangeInfo,reducerChangeInfo,changeInfoStatus);
+        buildReqReducer(builder,reqChangePhone,reducerChangePhone,changePhoneStatus);
+        buildReqReducer(builder,reqChangeEmail,reducerChangeEmail,changeEmailStatus);
     },
 })
 export const {logout} = Main.actions;
@@ -333,6 +398,20 @@ export const getRegStatus=(state)=>{
 export const getLogoutStatus=(state)=>{
     return state.main.loginStatus.logout;
 }
+//modify
+export const getChangeInfoStatus=(state)=>{
+    return state.main.modifyStatus.info;
+}
+export const getChangePassStatus=(state)=>{
+    return state.main.modifyStatus.password;
+}
+export const getChangePhoneStatus=(state)=>{
+    return state.main.modifyStatus.phone;
+}
+export const getChangeEmailStatus=(state)=>{
+    return state.main.modifyStatus.email;
+}
+
 export const isLogined=(state)=>{
     // console.log(state.main.isLogin)
     return state.main.isLogin;

+ 8 - 7
src/layout/Layout.jsx

@@ -55,13 +55,14 @@ const Layout=()=>{
           {
               hash!=''?(
                   <div className="app-hash">
-                      <Suspense>
-                          <CHashRouter routes={popRoutes} hlocation={{
-                              pathname: pathname,
-                              search: search,
-                              hash: hash
-                          }} basePath={pathname}></CHashRouter>
-                      </Suspense>
+                      {/*<Suspense>*/}
+                      {/*    */}
+                      {/*</Suspense>*/}
+                      <CHashRouter routes={popRoutes} hlocation={{
+                          pathname: pathname,
+                          search: search,
+                          hash: hash
+                      }} basePath={pathname}></CHashRouter>
                   </div>
               ):(<></>)
           }

+ 14 - 11
src/layout/Routes/index.jsx

@@ -1,6 +1,7 @@
 import AllComponents from '@pages/index';
 import {Route, Routes} from 'react-router-dom';
-import {lazy} from "react";
+import {lazy, Suspense} from "react";
+import SmallLoading from "@components/SmallLoading";
 const RouteWrapper = lazy(() => import('@layout/Routes/RouteWrapper'));
 
 const PageNotFound = lazy(() => import('@pages/PageNotFound'));
@@ -59,16 +60,18 @@ const CHashRouter=(props)=>{
                     if (item=="")return (<></>);
                     let hash="#"+item;
                     return (
-                        <Routes key={index} location={{pathname:currpath,hash:hash,search}}>
-                            {
-                                routes.map((route)=>{
-                                    if (route.path===hash){
-                                        return createRoute({...props,route,path:currpath});
-                                    }
-                                    else return (<></>);
-                                })
-                            }
-                        </Routes>
+                        <Suspense fallback={<SmallLoading></SmallLoading>}>
+                            <Routes key={index} location={{pathname:currpath,hash:hash,search}}>
+                                {
+                                    routes.map((route)=>{
+                                        if (route.path===hash){
+                                            return createRoute({...props,route,path:currpath});
+                                        }
+                                        else return (<></>);
+                                    })
+                                }
+                            </Routes>
+                        </Suspense>
                     )
                 })
             }

+ 3 - 2
src/layout/Sidebar/index.jsx

@@ -70,6 +70,7 @@ import {
 } from "@constants/routes";
 import SvgIcon from "@components/SvgIcon";
 import {reqLogout} from "@features/api";
+import {APP_THEME} from "@constants/app";
 const drawWidth = 250;
 const drawMobieWidth=250;
 const i18n=global.i18n;
@@ -430,11 +431,11 @@ const Sidebar = () => {
                         marginTop:'16px',
                     }} className={theme} exclusive value={theme} onChange={handleTheme}>
                         <div className="theme-bg"/>
-                        <StyledThemeTab value={"dark"}>
+                        <StyledThemeTab value={APP_THEME.DARK}>
                             <SvgIcon className={"icon"} herf={"icon_Dark"}/>
                             {i18n.t("theme.dark")}
                         </StyledThemeTab>
-                        <StyledThemeTab value={"light"}>
+                        <StyledThemeTab value={APP_THEME.LIGHT}>
                             <SvgIcon className={"icon"} herf={"icon_Light"}/>
                             {i18n.t("theme.light")}
                         </StyledThemeTab>

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

@@ -107,7 +107,36 @@
     },
     "language": "Language",
     "theme": "Theme",
-    "gender": "Gender"
+    "gender": "Gender",
+    "phone": "Phone",
+    "email": "Email",
+    "security": "Security"
   },
-  "copy_success": "Copy successful"
+  "copy_success": "Copy successful",
+  "user": {
+    "title_avatar": "Avatar",
+    "title_username": "Username",
+    "title_gender": "Gender",
+    "title_phone": "Phone",
+    "title_email": "Email",
+    "username_ph": " Enter Username",
+    "phone_ph": "Enter Phone Number",
+    "email_ph": "Enter Email",
+    "female": "Female",
+    "male": "Male",
+    "other": "Other",
+    "submit_btn": "Modify",
+    "password": {
+      "title": "Reset Password",
+      "oldpassword": "Login Password",
+      "newpassword": "New login password",
+      "renewpassword": "Confirm new login password",
+      "fail": "The original password is wrong, please enter it again.",
+      "modi_success": "Password reset completed!",
+      "notsame": "Passwords entered twice are inconsistent, please enter again!"
+    },
+    "info": {
+      "modi_success": "Modified information"
+    }
+  }
 }

+ 13 - 0
src/pages/EditPassword.jsx

@@ -0,0 +1,13 @@
+// components
+import WEditPassword from "@widgets/WEditPassword";
+import React from "react";
+
+const EditPassword = ({...props}) => {
+    return (
+        <>
+            <WEditPassword {...props}/>
+        </>
+    )
+}
+
+export default EditPassword

+ 12 - 0
src/pages/EditProfile.jsx

@@ -0,0 +1,12 @@
+// components
+import WEditProfile from "@widgets/WEditProfile";
+
+const EditProfile = ({...props}) => {
+    return (
+        <>
+            <WEditProfile {...props}/>
+        </>
+    )
+}
+
+export default EditProfile

+ 0 - 33
src/pages/PlayerProfile.jsx

@@ -1,33 +0,0 @@
-// components
-import PageHeader from '@layout/PageHeader';
-import AppGrid from '@layout/AppGrid';
-import PlayerProfileCard from '@widgets/PlayerProfileCard';
-import TrainingPaceChart from '@widgets/TrainingPaceChart';
-import GamesCalendar from '@widgets/GamesCalendar';
-import ShotsStats from '@widgets/ShotStats';
-import TrainingsPlanner from '@widgets/TrainingsPlanner';
-import LatestMessages from '@widgets/LatestMessages';
-import HotField from '@widgets/HotField';
-import ChampionsLeague from '@widgets/ChampionsLeague';
-
-const widgets = {
-    profile_card: <PlayerProfileCard />,
-    training_pace: <TrainingPaceChart />,
-    calendar: <GamesCalendar />,
-    shots: <ShotsStats />,
-    planner: <TrainingsPlanner />,
-    messages: <LatestMessages />,
-    field: <HotField />,
-    champions: <ChampionsLeague />
-}
-
-const Tickets = () => {
-    return (
-        <>
-            <PageHeader title="Player Profile" />
-            <AppGrid id="player_profile" widgets={widgets}/>
-        </>
-    )
-}
-
-export default Tickets

+ 4 - 0
src/pages/index.jsx

@@ -3,9 +3,13 @@ const ModulePage=lazy(()=>import('@pages/ModulePage'));
 const Home=lazy(()=>import('@pages/Home'));
 const Login=lazy(()=>import("@pages/Login"));
 const Settings=lazy(()=>import("@pages/Settings"));
+const EditProfile=lazy(()=>import("@pages/EditProfile"));
+const EditPassword=lazy(()=>import("@pages/EditPassword"));
 export default {
     ModulePage:ModulePage,
     Home:Home,
     login:Login,
     settings:Settings,
+    editProfile:EditProfile,
+    editPassword:EditPassword
 }

+ 1 - 0
src/styles/_elements.scss

@@ -676,6 +676,7 @@ a {
     background-color: var(--nav-item-bg);
     border-radius: var(--border-radius);
     opacity: 1;
+    //font-size: 0.875rem;
     &:focus-within{
         border: 1px solid var(--highlight);
     }

+ 4 - 0
src/styles/theme.js

@@ -107,5 +107,9 @@ export default createGlobalStyle`
         light: '#FFFFFF',
         dark: '#2f3036',
     })};
+    --card-line: ${theme('theme', {
+        light: 'rgba(0,0,0,.05)',
+        dark: 'rgba(255,255,255,.05)',
+    })};
   }
 `;

+ 12 - 0
src/utils/helpers.js

@@ -125,3 +125,15 @@ export const localStorageGet=(key,defaultValue="")=>{
 export const localStorageSet=(key,value)=>{
     localStorage.setItem(key,value);
 }
+export const getHttpMsg=(data)=>{
+    let result=data.result;
+    if (result==null)result=data.msg;
+    let msg="";
+    if (result!=null){
+        let key=result[0];
+        let def=result[1];
+        if (global.i18n.exists(key))msg=global.i18n.t(key);
+        else msg=def;
+    }
+    return msg;
+}

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

@@ -26,6 +26,7 @@ import SmallLoading from "@components/SmallLoading";
 import {REQ_STATUS} from "@api/config";
 import {reqLogin, reqRegister} from "@features/api";
 import {ROUTE_PATH} from "@constants/routes";
+import {APP_THEME} from "@constants/app";
 
 const i18n=global.i18n;
 const TYPE_LOGIN="login";
@@ -37,7 +38,7 @@ const LoginForm = ({close}) => {
     const [showType,setShowType]=useState(TYPE_LOGIN);
     const [open, setOpen] = useState(false);
     const [country, setCountry] = useState("55");
-    const logourl=(theme=='dark'?logo_dark:logo_light);
+    const logourl=(theme===APP_THEME.DARK?logo_dark:logo_light);
     const loginStatus=useSelector(getLoginStatus);
     const regStatus=useSelector(getRegStatus);
     const dispatch = useDispatch();
@@ -125,11 +126,11 @@ const LoginForm = ({close}) => {
                                         innerRef={ref}/>
                                 )}
                     />
-                    <div className={styles.l_forgot}>
-                        <button className={styles.forgot_btn}>
-                            {i18n.t("login.forgot")}
-                        </button>
-                    </div>
+                    {/*<div className={styles.l_forgot}>*/}
+                    {/*    <button className={styles.forgot_btn}>*/}
+                    {/*        {i18n.t("login.forgot")}*/}
+                    {/*    </button>*/}
+                    {/*</div>*/}
                     <button className={"button-green"}>
                         {
                             loginStatus===REQ_STATUS.LOADING?renderLoading():i18n.t("login.sign_in_btn")

+ 123 - 36
src/widgets/UserSettings/index.jsx

@@ -9,9 +9,11 @@ import LazyImage from "@components/LazyImage";
 import USD from "@assets/lvicon/USD.black.png";
 import {numFormatter} from "@utils/helpers";
 import SvgIcon from "@components/SvgIcon";
-import {ReactComponent as Male} from '@assets/gener/male.svg';
-import {ReactComponent as FeMale} from '@assets/gener/female.svg';
-import {ReactComponent as GenderIcon} from '@assets/gener/gender.svg';
+import {ReactComponent as Male} from '@assets/singlesvg/gener/male.svg';
+import {ReactComponent as FeMale} from '@assets/singlesvg/gener/female.svg';
+import {ReactComponent as GenderIcon} from '@assets/singlesvg/gener/gender.svg';
+import {ReactComponent as MobileIcon} from '@assets/singlesvg/mobile.svg';
+import {ReactComponent as EmailIcon} from '@assets/singlesvg/email.svg';
 import {CopyToClipboard} from 'react-copy-to-clipboard';
 import {toast} from "react-toastify";
 import ToggleButtonGroup from "@mui/material/ToggleButtonGroup";
@@ -21,6 +23,7 @@ import ToggleButton from "@mui/material/ToggleButton";
 import {reqLogout} from "@features/api";
 import {REQ_STATUS} from "@api/config";
 import SmallLoading from "@components/SmallLoading";
+import {APP_GENDER, APP_THEME} from "@constants/app";
 const i18n=global.i18n;
 const UserSettings=()=>{
     const isLogin=useSelector(isLogined);
@@ -33,7 +36,7 @@ const UserSettings=()=>{
         return (
             <div className={styles.wrap_item}>
                 <div className={styles.user_info}>
-                    <EmptyAvatar className={styles.avatar_empty}></EmptyAvatar>
+                    <EmptyAvatar className={styles.avatar_empty}/>
                     <div className={styles.empty_info}>
                         <span className={styles.welcome}>
                             {i18n.t("settings.user_info.welcome")}
@@ -58,35 +61,94 @@ const UserSettings=()=>{
             </div>
         )
     }
+    const getPhone=()=>{
+        let loc=user.RegisterLocation;
+        let phone=user.Phone;
+        if (phone.startsWith(loc)){
+            phone=phone.replace(loc,"");
+        }
+        return phone;
+    }
+    const getEmail=()=>{
+        let email=user.Email;
+        if (email==null||email==""){
+            return "--";
+        }
+        return email;
+    }
+    const renderPhone=()=>{
+        return (
+            <div className={styles.action_item}>
+                <div className={styles.action_left}>
+                    <MobileIcon className={styles.icon}></MobileIcon>
+                    <div>{i18n.t("settings.phone")}</div>
+                </div>
+                <div className={styles.action_right}>
+                    <div>{"+"+user.RegisterLocation}</div>
+                    <div>{getPhone()}</div>
+                </div>
+            </div>
+        )
+    }
+    const renderEmail=()=>{
+        return (
+            <div className={styles.action_item}>
+                <div className={styles.action_left}>
+                    <EmailIcon className={styles.icon}></EmailIcon>
+                    <div>{i18n.t("settings.email")}</div>
+                </div>
+                <div className={styles.action_right}>
+                    <div>{getEmail()}</div>
+                </div>
+            </div>
+        )
+    }
     const renderUser=()=>{
+        let gender=(<></>);
+        if (user.Gender===APP_GENDER.FEMALE){
+            gender=(<FeMale className={styles.female}/>);
+        }
+        else if (user.Gender===APP_GENDER.MALE){
+            gender=(<Male className={styles.male}/>);
+        }
         return (
             <div className={styles.wrap_item}>
-                <div className={styles.user_info}>
-                    <LazyImage wrapperClassName={styles.avatar} src={user.img} alt={"avatar"}></LazyImage>
-                    <div className={`${styles.user_info} ${styles.info}`}>
-                        <div className={styles.info_item}>
-                            <div className={styles.name}>{user.NickName}</div>
-                            {
-                                user.Gender===1?(<FeMale className={styles.female}></FeMale>):(<Male className={styles.male}></Male>)
-                            }
-                        </div>
-                        <CopyToClipboard text={user.UserID} onCopy={()=>{
-                            toast.success(i18n.t("copy_success"));
-                        }}>
+                <div className={styles.user_con}>
+                    <div className={styles.user_info}>
+                        <LazyImage wrapperClassName={styles.avatar} src={user.img} alt={"avatar"}></LazyImage>
+                        <div className={`${styles.user_info} ${styles.info}`}>
+                            <div className={styles.info_item}>
+                                <div className={styles.name}>{user.NickName}</div>
+                                {gender}
+                            </div>
+                            <CopyToClipboard text={user.UserID} onCopy={()=>{
+                                toast.success(i18n.t("copy_success"));
+                            }}>
+                                <div className={styles.info_item}>
+                                    <div className={styles.text}>{i18n.t("settings.user_info.id")}</div>
+                                    <div className={styles.text}>{user.UserID}</div>
+                                    <button>
+                                        <SvgIcon className={styles.icon_copy} herf={"icon_Copy"}></SvgIcon>
+                                    </button>
+                                </div>
+                            </CopyToClipboard>
                             <div className={styles.info_item}>
-                                <div className={styles.text}>{i18n.t("settings.user_info.id")}</div>
-                                <div className={styles.text}>{user.UserID}</div>
-                                <button>
-                                    <SvgIcon className={styles.icon_copy} herf={"icon_Copy"}></SvgIcon>
-                                </button>
+                                <img className={styles.icon_golds} src={USD}/>
+                                <div className={styles.golds}>{"$"+numFormatter(user.Score)}</div>
                             </div>
-                        </CopyToClipboard>
-                        <div className={styles.info_item}>
-                            <img className={styles.icon_golds} src={USD}/>
-                            <div className={styles.golds}>{"$"+numFormatter(user.Score)}</div>
                         </div>
                     </div>
+                    <div className={styles.user_edit_con}>
+                        <button className={styles.edit_btn} onClick={()=>{
+                            navigate(ROUTE_PATH.EDIT_PROFILE);
+                        }}>
+                            <SvgIcon className={styles.icon} herf={"icon_Edit"}></SvgIcon>
+                        </button>
+                    </div>
                 </div>
+                {/*<div className={styles.card_line}></div>*/}
+                {/*{renderPhone()}*/}
+                {/*{renderEmail()}*/}
             </div>
         )
     }
@@ -99,9 +161,30 @@ const UserSettings=()=>{
             </>
         )
     }
+    const renderItemSecurity=()=>{
+        return (
+            <>
+                {
+                    isLogin?(
+                        <button className={styles.action_item} onClick={()=>{
+                            navigate(ROUTE_PATH.EDIT_PWD);
+                        }}>
+                            <div className={styles.action_left}>
+                                <SvgIcon className={styles.icon} herf={"icon_Locked"}></SvgIcon>
+                                <div>{i18n.t("settings.security")}</div>
+                            </div>
+                            <div className={styles.action_right}>
+                                <SvgIcon className={styles.icon} herf={"icon_Arrow"}></SvgIcon>
+                            </div>
+                        </button>
+                    ):(<></>)
+                }
+            </>
+        )
+    }
     const renderItemLang=()=>{
         return (
-            <div className={styles.action_item}>
+            <button className={styles.action_item}>
                 <div className={styles.action_left}>
                     <SvgIcon className={styles.icon} herf={"icon_Language"}></SvgIcon>
                     <div>{i18n.t("settings.language")}</div>
@@ -110,12 +193,15 @@ const UserSettings=()=>{
                     <div>{i18n.getCurrLangName()}</div>
                     <SvgIcon className={styles.icon} herf={"icon_Arrow"}></SvgIcon>
                 </div>
-            </div>
+            </button>
         )
     }
+    const handleTheme=()=>{
+        toggleTheme();
+    }
     const renderItemTheme=()=>{
         return (
-            <div className={styles.action_item}>
+            <button className={styles.action_item}>
                 <div className={styles.action_left}>
                     <SvgIcon className={styles.icon} herf={"icon_Exchange"}></SvgIcon>
                     <div>{i18n.t("settings.theme")}</div>
@@ -130,9 +216,9 @@ const UserSettings=()=>{
                             alignItems:"center",
                             justifyContent:"space-between"
                         }} exclusive value={theme} onChange={()=>{
-                            toggleTheme();
+                            handleTheme();
                         }}>
-                            <div className={styles.theme_bg} style={{left: `${theme == "dark" ? '0%' : '50%'}`}}/>
+                            <div className={styles.theme_bg} style={{left: `${theme === APP_THEME.DARK ? '0%' : '50%'}`}}/>
                             <ToggleButton sx={{
                                 display:"flex",
                                 position: "relative",
@@ -145,8 +231,8 @@ const UserSettings=()=>{
                                 ".Mui-selected:hover":{
                                     backgroundColor: 'transparent !important;',
                                 }
-                            }} value={"dark"}>
-                                <SvgIcon className={`${styles.theme_icon} ${theme=="dark"?styles.active:''}`} herf={"icon_Dark"}/>
+                            }} value={APP_THEME.DARK}>
+                                <SvgIcon className={`${styles.theme_icon} ${theme===APP_THEME.DARK?styles.active:''}`} herf={"icon_Dark"}/>
                             </ToggleButton>
                             <ToggleButton sx={{
                                 display:"flex",
@@ -161,12 +247,12 @@ const UserSettings=()=>{
                                     backgroundColor: 'transparent !important;',
                                 }
                             }} value={"light"}>
-                                <SvgIcon className={`${styles.theme_icon} ${theme=="light"?styles.active:''}`} herf={"icon_Light"}/>
+                                <SvgIcon className={`${styles.theme_icon} ${theme===APP_THEME.LIGHT?styles.active:''}`} herf={"icon_Light"}/>
                             </ToggleButton>
                         </ToggleButtonGroup>
                     </div>
                 </div>
-            </div>
+            </button>
         )
     }
     const renderItemGender=()=>{
@@ -178,7 +264,7 @@ const UserSettings=()=>{
                 </div>
                 <div className={styles.action_right}>
                     {
-                        user.Gender===1?(<FeMale className={styles.gender}></FeMale>):(<Male className={styles.gender}></Male>)
+                        user.Gender===APP_GENDER.FEMALE?(<FeMale className={styles.gender}></FeMale>):(<Male className={styles.gender}></Male>)
                     }
                     <SvgIcon className={styles.icon} herf={"icon_Arrow"}></SvgIcon>
                 </div>
@@ -188,8 +274,9 @@ const UserSettings=()=>{
     const renderSetting=()=>{
         return (
             <div className={`${styles.wrap_item} ${styles.action_wrap}`}>
+                {renderItemSecurity()}
                 {renderItemLang()}
-                {renderItemGender()}
+                {/*{renderItemGender()}*/}
                 {renderItemTheme()}
             </div>
         )

+ 35 - 3
src/widgets/UserSettings/styles.module.scss

@@ -1,6 +1,6 @@
 .container{
   width: 100%;
-  height: 100%;
+  min-height: 100%;
   display: flex;
   flex-direction: column;
   position: relative;
@@ -26,6 +26,30 @@
   color: var(--text);
   height: 3rem;
 }
+.user_con{
+  display: flex;
+  justify-content: space-between;
+  width: 100%;
+}
+.user_edit_con{
+  display: flex;
+  flex-direction: column;
+  & .edit_btn{
+    //background: var(--nav-item-hover);
+    border-radius: var(--border-radius);
+    //background: var(--nav-item-bg);
+    //background-color: rgba(0, 0, 0, 0.15);
+    width: 2rem;
+    height: 2rem;
+    //opacity: 0.5;
+    & .icon{
+      font-size: .75rem;
+      width: 1.4em;
+      height: 1.4em;
+      fill: var(--text);
+    }
+  }
+}
 .user_info{
   display: flex;
   align-items: center;
@@ -142,12 +166,15 @@
     color: var(--header);
     gap: 0.525rem;
     & .icon{
-      width: 1.5rem;
-      height: 1.5rem;
+      width: 1.4rem;
+      height: 1.4rem;
       fill: var(--text);
       g{
         fill: var(--text);
       }
+      path{
+        fill: var(--text);
+      }
     }
   }
   & .action_right{
@@ -205,3 +232,8 @@
     fill: var(--header);
   }
 }
+.card_line{
+  width: 100%;
+  border-top: 1px solid var(--card-line);
+  margin: 0.75rem 0;
+}

+ 108 - 0
src/widgets/WEditPassword/index.jsx

@@ -0,0 +1,108 @@
+import styles from './styles.module.scss';
+import Spring from "@components/Spring";
+import React from "react";
+import logo_dark from "@assets/logo/logo.png";
+import logo_light from "@assets/logo/logo_w.png";
+import {useThemeProvider} from "@contexts/themeContext";
+import {Controller, useForm} from "react-hook-form";
+import PasswordInput from "@components/UInput/PasswordInput";
+import classNames from "classnames";
+import {REQ_STATUS} from "@api/config";
+import {useDispatch, useSelector} from "react-redux";
+import {getChangePassStatus} from "@features/mainSlice";
+import SmallLoading from "@components/SmallLoading";
+import {reqChangePass} from "@features/api";
+import {APP_THEME} from "@constants/app";
+const i18n=global.i18n;
+
+const WEditPassword=({close})=>{
+    const status=useSelector(getChangePassStatus);
+    const dispatch = useDispatch();
+    const {theme,isMobile} = useThemeProvider();
+    const logourl=(theme===APP_THEME.DARK?logo_dark:logo_light);
+    const {register, handleSubmit, formState: {errors}, control} = useForm({
+        defaultValues: {
+            oldpassword: '',
+            newpassword: '',
+            renewpassword:''
+        }
+    });
+    const renderLogo=()=>{
+        return (
+            <div className={styles.logo}>
+                <img className={styles.logo_img} src={logourl}/>
+            </div>
+        )
+    }
+    const onCallback=(code)=>{
+        if (code==200){
+            if (close)close();
+        }
+    }
+    const handleChange=(data)=>{
+        if (status===REQ_STATUS.LOADING)return;
+        data.callback=onCallback;
+        dispatch(reqChangePass(data));
+    }
+    const renderForm=()=>{
+        return (
+            <div className={styles.form_con}>
+                <div className={styles.form_title}>{i18n.t("user.password.title")}</div>
+                <form autoComplete="off" className={styles.form_content} onSubmit={handleSubmit((data) => {
+                    handleChange(data);
+                })}>
+                    <Controller control={control}
+                                name="oldpassword"
+                                rules={{required: true}}
+                                render={({field: {ref, onChange, value}, fieldState: {error}}) => (
+                                    <PasswordInput
+                                        className={classNames({'input-control--error': error})}
+                                        value={value}
+                                        onChange={e => onChange(e.target.value)}
+                                        placeholder={i18n.t('user.password.oldpassword')}
+                                        innerRef={ref}/>
+                                )}
+                    />
+                    <Controller control={control}
+                                name="newpassword"
+                                rules={{required: true}}
+                                render={({field: {ref, onChange, value}, fieldState: {error}}) => (
+                                    <PasswordInput
+                                        className={classNames({'input-control--error': error})}
+                                        value={value}
+                                        onChange={e => onChange(e.target.value)}
+                                        placeholder={i18n.t('user.password.newpassword')}
+                                        innerRef={ref}/>
+                                )}
+                    />
+                    <Controller control={control}
+                                name="renewpassword"
+                                rules={{required: true}}
+                                render={({field: {ref, onChange, value}, fieldState: {error}}) => (
+                                    <PasswordInput
+                                        className={classNames({'input-control--error': error})}
+                                        value={value}
+                                        onChange={e => onChange(e.target.value)}
+                                        placeholder={i18n.t('user.password.renewpassword')}
+                                        innerRef={ref}/>
+                                )}
+                    />
+                    <button className={"button-green"}>
+                        {
+                            status===REQ_STATUS.LOADING?(<SmallLoading fill={"#fff"}/>):i18n.t("user.submit_btn")
+                        }
+                    </button>
+                </form>
+            </div>
+        )
+    }
+    return (
+        <div className={styles.container}>
+            <Spring type="slideUp" className={styles.content}>
+                {renderLogo()}
+                {renderForm()}
+            </Spring>
+        </div>
+    )
+}
+export default WEditPassword;

+ 51 - 0
src/widgets/WEditPassword/styles.module.scss

@@ -0,0 +1,51 @@
+.container{
+  width: 100%;
+  min-height: 100%;
+  display: flex;
+  flex-direction: column;
+  position: relative;
+  //padding: 1rem .75rem;
+  background-color: var(--widget-bg);
+}
+.logo{
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 100%;
+  min-height: 10rem;
+  padding: 1rem;
+  & .logo_img{
+    width: 60%;
+    height: auto;
+  }
+}
+.content{
+  width: 100%;
+  display: flex;
+  flex-direction: column;
+}
+.form_con{
+  width: 100%;
+  height: auto;
+  padding: 2rem;
+  backdrop-filter: blur(7px);
+  display: flex;
+  flex-direction: column;
+}
+.form_title{
+  display: flex;
+  align-items: center;
+  height: 1.25rem;
+  line-height: 1.25rem;
+  color: var(--header);
+  font-size: 1rem;
+  margin: 0;
+  font-weight: 700;
+  text-transform: capitalize;
+}
+.form_content{
+  display: flex;
+  flex-direction: column;
+  margin-top: 1.5rem;
+  gap: 1rem;
+}

+ 289 - 0
src/widgets/WEditProfile/index.jsx

@@ -0,0 +1,289 @@
+import styles from './styles.module.scss';
+import Spring from "@components/Spring";
+import {APP_AVATAR, REQ_STATUS} from "@api/config";
+import LazyImage from "@components/LazyImage";
+import {useDispatch, useSelector} from "react-redux";
+import {getChangeEmailStatus, getChangeInfoStatus, getChangePhoneStatus, getUserInfo} from "@features/mainSlice";
+import React, {useState} from "react";
+import {Controller, useForm} from "react-hook-form";
+import classNames from "classnames";
+import PhoneInput from "@components/UInput/PhoneInput";
+import {ReactComponent as Male} from '@assets/singlesvg/gener/male.svg';
+import {ReactComponent as FeMale} from '@assets/singlesvg/gener/female.svg';
+import SmallLoading from "@components/SmallLoading";
+import {reqChangeEmail, reqChangeInfo, reqChangePhone} from "@features/api";
+import {APP_GENDER} from "@constants/app";
+const i18n=global.i18n;
+const getAvatarUrl=(id)=>{
+    return `${APP_AVATAR}avatar_${id}.png`;
+}
+const SelectAvatar=({innerRef, ...props})=>{
+    const [value, setValue] = useState(props.value);
+    const aNum=16;
+    const avatars = Array.from({ length:aNum }, (value, index) => index + 1);
+    return (
+        <div className={styles.select_avatar_con}
+            ref={innerRef}
+            {...props}
+        >
+            {
+                avatars.map((item,index)=>{
+                    return (
+                        <button type={"button"} key={index} className={`${styles.avatar_btn}`} onClick={()=>{
+                            setValue(item);
+                            if (props.onChange)props.onChange(item);
+                        }}>
+                            <LazyImage wrapperClassName={`${styles.avatar} ${item==value?styles.active:''}`} src={getAvatarUrl(item)} alt={"avatar_" + index}/>
+                        </button>
+                    )
+                })
+            }
+        </div>
+    )
+}
+const WEditProfile=({close})=>{
+    const user=useSelector(getUserInfo);
+    const status=useSelector(getChangeInfoStatus);
+    const status_ph=useSelector(getChangePhoneStatus);
+    const status_em=useSelector(getChangeEmailStatus);
+    const dispatch = useDispatch();
+    const [avatarID,setAvatarID]=useState(user.FaceID);
+    const [country, setCountry] = useState(user.RegisterLocation);
+    const getPhone=()=>{
+        let loc=user.RegisterLocation;
+        let phone=user.Phone;
+        if (phone.startsWith(loc)){
+            phone=phone.replace(loc,"");
+        }
+        return phone;
+    }
+    const getIsLock=()=>{
+        let isLock=status===REQ_STATUS.LOADING||status_ph===REQ_STATUS.LOADING||status_em===REQ_STATUS.LOADING;
+        return isLock;
+    }
+    const {register, handleSubmit, formState: {errors}, control} = useForm({
+        defaultValues: {
+            FaceID:user.FaceID,
+            NickName:user.NickName,
+            Gender:user.Gender,
+            Phone:getPhone(),
+            Email:user.Email
+        }
+    });
+    const renderAvatar=()=>{
+        return (
+            <div className={styles.avatar_con}>
+                <LazyImage wrapperClassName={styles.avatar} src={getAvatarUrl(avatarID)} alt={"avator"}/>
+            </div>
+        )
+    }
+    const renderSelectAvatar=()=>{
+        return (
+            <div className={styles.form_item}>
+                <div className={styles.title}>{i18n.t("user.title_avatar")}</div>
+                <Controller control={control}
+                            name="FaceID"
+                            render={({field: {ref, onChange, value}, fieldState: {error}}) => (
+                                <SelectAvatar
+                                    value={value}
+                                    onChange={(faceID)=>{
+                                        onChange(faceID);
+                                        setAvatarID(faceID);
+                                    }}
+                                    innerRef={ref}/>
+                            )}
+                />
+            </div>
+        )
+    }
+    const renderUsername=()=>{
+        return (
+            <div className={styles.form_item}>
+                <div className={styles.title}>{i18n.t("user.title_username")}</div>
+                <Controller control={control}
+                            name="NickName"
+                            rules={{required: true}}
+                            render={({field: {ref, onChange, value}, fieldState: {error}}) => (
+                                <div className={classNames("input-control",{'input-control--error': error})}>
+                                    <input
+                                           value={value}
+                                           type="tel"
+                                           ref={ref}
+                                           onChange={(e)=>{
+                                               onChange(e.target.value);
+                                           }}
+                                           placeholder={i18n.t('user.username_ph')}
+                                    />
+                                </div>
+
+                            )}
+                />
+            </div>
+        )
+    }
+    const renderPhone=()=>{
+        return (
+            <div className={styles.form_item}>
+                <div className={styles.title}>{i18n.t("user.title_phone")}</div>
+                <Controller control={control}
+                            name="Phone"
+                            rules={{required: true}}
+                            render={({field: {ref, onChange, value}, fieldState: {error}}) => (
+                                <PhoneInput
+                                    className={classNames({'input-control--error': error})}
+                                    value={value}
+                                    onChange={e => onChange(e.target.value)}
+                                    onChangeCountry={(country)=>{
+                                        setCountry(country);
+                                    }}
+                                    placeholder={i18n.t('user.phone_ph')}
+                                    innerRef={ref}/>
+
+                            )}
+                />
+            </div>
+        )
+    }
+    const renderEmail=()=>{
+        return (
+            <div className={styles.form_item}>
+                <div className={styles.title}>{i18n.t("user.title_email")}</div>
+                <Controller control={control}
+                            name="Email"
+                            // rules={{required: true}}
+                            render={({field: {ref, onChange, value}, fieldState: {error}}) => (
+                                <div className={classNames("input-control",{'input-control--error': error})}>
+                                    <input
+                                        value={value}
+                                        type="tel"
+                                        ref={ref}
+                                        onChange={(e)=>{
+                                            onChange(e.target.value);
+                                        }}
+                                        placeholder={i18n.t('user.email_ph')}
+                                    />
+                                </div>
+
+                            )}
+                />
+            </div>
+        )
+    }
+    const renderGender=()=>{
+        return (
+            <div className={styles.form_item}>
+                <div className={styles.title}>{i18n.t("user.title_gender")}</div>
+                <Controller control={control}
+                            name="Gender"
+                    // rules={{required: true}}
+                            render={({field: {ref, onChange, value}, fieldState: {error}}) => (
+                                <div className={styles.gender_con}>
+                                    <button type={"button"} className={`${styles.radio_btn} ${value===APP_GENDER.FEMALE?styles.active:''}`} onClick={()=>{
+                                        onChange(APP_GENDER.FEMALE);
+                                    }}>
+                                        <div className={styles.circle}>
+                                            <div className={styles.dot}/>
+                                        </div>
+                                        <div>{i18n.t("user.female")}</div>
+                                        <FeMale className={styles.icon_f}/>
+                                    </button>
+                                    <button type={"button"} className={`${styles.radio_btn} ${value===APP_GENDER.MALE?styles.active:''}`} onClick={()=>{
+                                        onChange(APP_GENDER.MALE)
+                                    }}>
+                                        <div className={styles.circle}>
+                                            <div className={styles.dot}/>
+                                        </div>
+                                        <div>{i18n.t("user.male")}</div>
+                                        <Male className={styles.icon_m}/>
+                                    </button>
+                                    <button type={"button"} className={`${styles.radio_btn} ${value===APP_GENDER.OTHER?styles.active:''}`} onClick={()=>{
+                                        onChange(APP_GENDER.OTHER)
+                                    }}>
+                                        <div className={styles.circle}>
+                                            <div className={styles.dot}/>
+                                        </div>
+                                        <div>{i18n.t("user.other")}</div>
+                                    </button>
+                                </div>
+                            )}
+                />
+            </div>
+        )
+    }
+    const renderSubmit=()=>{
+        let isLoading=getIsLock();
+        return (
+            <div className={styles.submit_btn_con}>
+                <button className={"button-green"}>
+                    {
+                        isLoading?(<SmallLoading fill={"#fff"}/>):i18n.t("user.submit_btn")
+                    }
+                </button>
+            </div>
+        )
+    }
+    const onCallback=(code)=>{
+        if (code==200){
+            if (close)close();
+        }
+    }
+    const handleChangeInfo=(data)=>{
+        data.Phone=country+data.Phone;
+        data.RegisterLocation=country;
+        let change={};
+        let change_ph={};
+        let change_em={};
+        let isSubmit=false;
+        let isSubmit_ph=false;
+        let isSubmit_em=false;
+        for (const key in data) {
+            let value=data[key];
+            if (value!==user[key]){
+                if (key==="Phone"){
+                    isSubmit_ph=true;
+                    change_ph[key]=value;
+                }
+                else if (key=="Email"){
+                    isSubmit_em=true;
+                    change_em[key]=value;
+                }
+                else
+                {
+                    isSubmit=true;
+                    change[key]=value;
+                }
+            }
+        }
+        // if (!isSubmit)return;
+        if (getIsLock())return;
+        change.callback=onCallback;
+        change_ph.callback=onCallback;
+        change_em.callback=onCallback;
+        if (isSubmit)dispatch(reqChangeInfo(change));
+        if (isSubmit_ph)dispatch(reqChangePhone(change_ph));
+        if (isSubmit_em)dispatch(reqChangeEmail(change_em));
+    }
+    const renderForm=()=>{
+        return (
+            <form autoComplete="off" className={styles.user_form} onSubmit={handleSubmit((data) => {
+                handleChangeInfo(data);
+            })}>
+                {renderUsername()}
+                {renderPhone()}
+                {renderEmail()}
+                {renderGender()}
+                {renderSelectAvatar()}
+                {renderSubmit()}
+            </form>
+        )
+    }
+    return (
+        <div className={styles.container}>
+            <Spring type="slideUp" className={styles.content}>
+                {renderAvatar()}
+                {renderForm()}
+            </Spring>
+        </div>
+    )
+}
+export default WEditProfile;

+ 136 - 0
src/widgets/WEditProfile/styles.module.scss

@@ -0,0 +1,136 @@
+.container{
+  width: 100%;
+  min-height: 100%;
+  display: flex;
+  flex-direction: column;
+  position: relative;
+  padding: 1rem .75rem;
+  background-color: var(--widget-bg);
+}
+.content{
+  width: 100%;
+  display: flex;
+  flex-direction: column;
+  //gap: 1rem;
+}
+.avatar_con{
+  display: flex;
+  width: 100%;
+  align-items: center;
+  justify-content: center;
+  padding: 0rem 3rem;
+  & .avatar{
+    width: 35%;
+    height: auto;
+    img{
+      width: 100%;
+      height: auto;
+      border-radius: 50%;
+      border: solid .25rem rgba(151, 151, 151, .15);
+    }
+  }
+}
+.user_form{
+  display: flex;
+  flex-direction: column;
+  gap: 1rem;
+  padding: 1rem;
+}
+.form_item{
+  display: flex;
+  flex-direction: column;
+  gap: 0.5rem;
+  //font-size: 1rem;
+  & .title{
+    font-size: 1rem;
+    color: var(--text);
+  }
+}
+.select_avatar_con{
+  display: flex;
+  position: relative;
+  overflow-x: auto;
+  overflow-y: hidden;
+  scroll-behavior: smooth;
+  //padding: 0.5rem 0.5rem;
+  gap: 0.71rem;
+  //background-color: var(--card-bg);
+  border-radius: var(--border-radius);
+  & .avatar_btn{
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    scroll-snap-align: start;
+    width: 3.5rem;
+    height: 3.5rem;
+    flex: none;
+    & .avatar{
+      img{
+        border-radius: 50%;
+        border: solid .15rem transparent;
+      }
+      &.active{
+        img{
+          border-radius: 50%;
+          border: solid .15rem var(--primary-color);
+        }
+      }
+    }
+  }
+}
+.gender_con{
+  display: flex;
+  align-items: center;
+  gap: 2rem;
+  min-height: 1.875rem;
+  & .icon_f{
+    width: 1.2rem;
+    height: 1.2rem;
+    g{
+      fill: #fa5fdb;
+    }
+  }
+  & .icon_m{
+    width: 1.2rem;
+    height: 1.2rem;
+    g{
+      fill: #05b0ff;
+    }
+  }
+  & .radio_btn{
+    display: flex;
+    align-items: center;
+    color: var(--header);
+    gap: 0.5rem;
+    &.active{
+      & .circle{
+        border: solid .13rem var(--primary-color);
+        opacity: 1;
+        & .dot{
+          background-color: var(--primary-color);
+        }
+      }
+    }
+    & .circle{
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      width: 1.2rem;
+      height: 1.2rem;
+      border-radius: 50%;
+      //border: solid .13rem var(--nav-item-hover);
+      border: solid .13rem var(--text);
+      opacity: 0.5;
+      & .dot{
+        width: 0.65rem;
+        height: 0.65rem;
+        border-radius: 50%;
+        //background-color: var(--nav-item-hover);
+        background-color:transparent;
+      }
+    }
+  }
+}
+.submit_btn_con{
+  margin-top: 3rem;
+}