Selaa lähdekoodia

Merge branch 'xudh' into sport

zhejiushizhuce 2 vuotta sitten
vanhempi
sitoutus
f7df248a3f
69 muutettua tiedostoa jossa 3221 lisäystä ja 437 poistoa
  1. 1 0
      package.json
  2. 67 18
      src/App.js
  3. 18 0
      src/Global.js
  4. 11 1
      src/api/config.js
  5. 48 2
      src/api/request.js
  6. 0 0
      src/assets/newmail.json
  7. BIN
      src/assets/share/share_11.png
  8. BIN
      src/assets/share/share_12.png
  9. BIN
      src/assets/share/share_13.png
  10. BIN
      src/assets/share/share_14.png
  11. BIN
      src/assets/share/share_16.png
  12. BIN
      src/assets/share/share_17.png
  13. BIN
      src/assets/share/share_3.png
  14. BIN
      src/assets/share/share_7.png
  15. BIN
      src/assets/share/share_8.png
  16. 29 0
      src/assets/singlesvg/userupgrade.svg
  17. 21 0
      src/assets/symbol-defs.svg
  18. 4 4
      src/components/DropCoins.jsx
  19. 58 11
      src/components/GameItem/index.jsx
  20. 3 1
      src/components/GameItem/styles.module.scss
  21. 66 11
      src/components/GameList/index.jsx
  22. 9 6
      src/components/GameList/styles.module.scss
  23. 16 2
      src/components/GameRoom/index.jsx
  24. 124 25
      src/components/GameSmallList/index.jsx
  25. 41 15
      src/components/GameSmallList/styles.module.scss
  26. 52 0
      src/components/MailNew.jsx
  27. 2 1
      src/constants/localkey.js
  28. 5 0
      src/contexts/themeContext.js
  29. 13 13
      src/db/routes.js
  30. 12 2
      src/features/actions/hallClient.js
  31. 114 10
      src/features/api.js
  32. 4 1
      src/features/colyseus/CSClient.js
  33. 129 7
      src/features/mainSlice.js
  34. 2 1
      src/features/paySlice.js
  35. 65 0
      src/hooks/useGameIFrame.js
  36. 1 1
      src/hooks/usePreviousRoute.js
  37. 3 2
      src/layout/BreadCrumb/index.jsx
  38. 1 0
      src/layout/BreadCrumb/styles.module.scss
  39. 16 3
      src/layout/Layout.jsx
  40. 4 1
      src/layout/Navbar/styles.module.scss
  41. 0 1
      src/layout/Routes/index.jsx
  42. 1 1
      src/layout/Sidebar/styles.js
  43. 67 14
      src/locales/languages/en_US.json
  44. 61 8
      src/locales/languages/es_MX.json
  45. 60 7
      src/locales/languages/pt_BR.json
  46. 181 128
      src/locales/languages/ru_RU.json
  47. 57 4
      src/locales/languages/zh_TW.json
  48. 8 3
      src/pages/Game.jsx
  49. 2 0
      src/styles/_global.scss
  50. 1 0
      src/styles/_vars.scss
  51. 154 0
      src/tracking/KwaiPixel.js
  52. 31 1
      src/tracking/TrackAll.js
  53. 287 0
      src/utils/UAUtils.js
  54. 223 64
      src/widgets/LoginForm/index.jsx
  55. 60 0
      src/widgets/LoginForm/styles.module.scss
  56. 43 2
      src/widgets/UserSettings/index.jsx
  57. 14 0
      src/widgets/UserSettings/styles.module.scss
  58. 212 0
      src/widgets/WBindPhone/index.jsx
  59. 55 0
      src/widgets/WBindPhone/styles.module.scss
  60. 3 1
      src/widgets/WCashWheel/WHallWheel/WHallWheel.jsx
  61. 5 0
      src/widgets/WCashWheel/WHallWheel/styles.module.scss
  62. 291 0
      src/widgets/WGame/WDGame/WDGame.jsx
  63. 184 0
      src/widgets/WGame/WDGame/styles.module.scss
  64. 40 55
      src/widgets/WGame/index.jsx
  65. 159 0
      src/widgets/WInstallBox/index.jsx
  66. 49 0
      src/widgets/WInstallBox/styles.module.scss
  67. 2 2
      src/widgets/WWithdraw/AccountEdit.jsx
  68. 1 0
      src/widgets/WWithdraw/BR/BRAccountEdit.jsx
  69. 31 8
      src/widgets/WWithdraw/BR/BRWithdraw.jsx

+ 1 - 0
package.json

@@ -85,6 +85,7 @@
     "styled-theming": "^2.2.0",
     "stylis-plugin-rtl": "^2.1.1",
     "swiper": "^11.1.4",
+    "ua-parser-js": "^1.0.38",
     "use-gauge": "^0.5.0",
     "use-mobile-detect-hook": "^1.0.5"
   },

+ 67 - 18
src/App.js

@@ -33,7 +33,7 @@ import useAuthRoute from '@hooks/useAuthRoute';
 // utils
 import {StyleSheetManager} from 'styled-components';
 import {ThemeProvider as MuiThemeProvider, createTheme} from '@mui/material/styles';
-import {getUrlParam, localStorageSet, preventDefault} from '@utils/helpers';
+import {getUrlParam, localStorageGet, localStorageSet, preventDefault} from '@utils/helpers';
 import rtlPlugin from 'stylis-plugin-rtl';
 import {CacheProvider} from '@emotion/react';
 import createCache from '@emotion/cache';
@@ -44,11 +44,11 @@ import Layout from "@layout/Layout";
 import {
     getBFP,
     getFetchStatus,
-    getHallServerUrl, getKickState, getPromoteInstall,
+    getHallServerUrl, getKickState, getOpenPay, getPromoteInstall,
     getUserID,
     getUserInfo,
     isLogined,
-    updateCurrGame
+    updateCurrGame, updateOpenPay
 } from '@features/mainSlice';
 import {useDispatch, useSelector} from "react-redux";
 import {REQ_STATUS} from "@api/config";
@@ -69,14 +69,15 @@ import {toast} from "react-toastify";
 import {useBlocker} from "react-router";
 import FontFaceObserver from 'fontfaceobserver';
 import {getTurnplate} from "@features/turnplateSlice";
-import {LOCAL_ACTIVITY_C, LOCAL_WHEEL_INVITE_CODE} from "@constants/localkey";
+import {LOCAL_ACTIVITY_C, LOCAL_VISTOR_ID, LOCAL_WHEEL_INVITE_CODE} from "@constants/localkey";
 import LoadingFull from "@components/Loading/LoadingFull";
 import {ClickAwayListener} from "@mui/base/ClickAwayListener";
 import useGlobalClick from "@hooks/useGlobalClick";
 import SoundHelp from "@utils/SoundManger";
-import usePreviousRoute from "@hooks/usePreviousRoute";
 import {ROUTE_PATH} from "@constants/routes";
 import {onChangeUser, onChangeUserID} from "@features/actions/mainAction";
+import {isInAppBrowser, isShowInstallBox, openUrlUseChrome} from "@utils/UAUtils";
+import WInstallBox from "@widgets/WInstallBox";
 const App = () => {
     const dispatch = useDispatch();
     const fetchStatus=useSelector(getFetchStatus);
@@ -84,7 +85,7 @@ const App = () => {
     const userID=useSelector(getUserID);
     const turnplate=useSelector(getTurnplate);
     const appRef = useRef(null);
-    const {theme, direction,setIsShowWheel} = useThemeProvider();
+    const {theme, direction,setIsShowWheel,preLocation,gameID} = useThemeProvider();
     const {pathname, search, hash} = useLocation();
     const gaKey = process.env.REACT_APP_PUBLIC_GA;
     gaKey && ReactGA.initialize(gaKey);
@@ -94,9 +95,10 @@ const App = () => {
     const plugins = direction === 'rtl' ? [rtlPlugin] : [];
     const hallServerUrl=useSelector(getHallServerUrl);
     useGlobalClick();
-    const {preLocation,gameID}=usePreviousRoute();
+    // const {preLocation,gameID}=usePreviousRoute();
     const serverKick=useSelector(getKickState);
     const promoteInstall=useSelector(getPromoteInstall);
+    const openPay=useSelector(getOpenPay);
     const muiTheme = createTheme({
         direction: direction,
     });
@@ -138,6 +140,11 @@ const App = () => {
         // else{
         //     localStorageSet(LOCAL_ACTIVITY_C,"");
         // }
+        let bfp=getUrlParam("bfp");
+        if (bfp!=null&&bfp!==""){
+            localStorageSet(LOCAL_VISTOR_ID,bfp);
+            global.BFP=bfp;
+        }
     }
     const reProgressierInstallBanner=()=>{
         if (window.NewProgressierInstallBanner!=null)return;
@@ -193,6 +200,7 @@ const App = () => {
     const appInitData=()=>{
         // scroll to top on route change
         checkInviteCode();
+        checkOpenInChrome();
         loadFont();
         //请求routes
         startReqRoutes();
@@ -206,9 +214,19 @@ const App = () => {
         if (userID>0){
             if (pathname==="/"){
                 if(hash==null||hash===""){
+                    let key="checkin_open_time";
+                    let now=Date.now();
+                    let lst=Number(localStorageGet(key,0));
+                    let delta=now-lst;
+                    if (delta<86400000){
+                        return false;
+                    }
                     dispatch(reqCheckinCheck((isCanCheckIn)=>{
                         if(hash==null||hash===""){
-                            if(isCanCheckIn===1)navigate(ROUTE_PATH.CHECK_IN,{replace:true});
+                            if(isCanCheckIn===1){
+                                navigate(ROUTE_PATH.CHECK_IN,{replace:true});
+                                localStorageSet(key,Date.now());
+                            }
                         }
                     }));
                     return true;
@@ -220,6 +238,27 @@ const App = () => {
     const onCloseHallWheel=()=>{
         checkOpenCheckin();
     }
+    const checkOpenInstallBox=()=>{
+        if (promoteInstall>0){
+            if (global.installed===1)return;
+            global.installed=1;
+            let time=promoteInstall*1000;
+            // time=1000;
+            setTimeout(async ()=>{
+                if (!isShowInstallBox())return;
+                global.showInstallBox();
+            },time);
+        }
+    }
+    const checkOpenInChrome=()=>{
+        let time=200;
+        setTimeout(async ()=>{
+            if (!isShowInstallBox())return;
+            if (isInAppBrowser()){
+                openUrlUseChrome();
+            }
+        },time);
+    }
     //app Effect
     //初始化
     useEffect(() => {
@@ -276,17 +315,15 @@ const App = () => {
     },[serverKick])
     //监听安装提示
     useEffect(()=>{
-        if (promoteInstall>0){
-            if (global.installed===1)return;
-            global.installed=1;
-            let time=promoteInstall*1000;
-            setTimeout(async ()=>{
-                if (window.progressier==null)return ;
-                if (window.progressier.detection.isStandalone())return;
-                window.progressier.flow.init("install",true)
-            },time);
-        }
+        checkOpenInstallBox();
     },[promoteInstall])
+    //监听打开pay
+    useEffect(()=>{
+        if (openPay===1){
+            navigate(ROUTE_PATH.PAY,{replace:true});
+            dispatch(updateOpenPay(0));
+        }
+    },[openPay])
     //app Effect
 
     const renderLayout=()=>{
@@ -298,6 +335,15 @@ const App = () => {
             </>
         )
     }
+    const renderPreload=()=>{
+        return (
+            <>
+                {
+                    <WInstallBox/>
+                }
+            </>
+        )
+    }
 
     return (
         <CacheProvider value={cacheRtl}>
@@ -312,6 +358,9 @@ const App = () => {
                                 renderLayout()
                             }
                         </StyleSheetManager>
+                        {
+                            renderPreload()
+                        }
                     </SidebarProvider>
                 </ThemeProvider>
             </MuiThemeProvider>

+ 18 - 0
src/Global.js

@@ -58,4 +58,22 @@ global.showDropCoins=()=>{
 global.hideDropCoins=()=>{
     if (global.dropCoins)global.dropCoins.hide();
 }
+global.showMailNew=()=>{
+    if (global.mailNew)global.mailNew.show();
+}
+global.hideMailNew=()=>{
+    if (global.mailNew)global.mailNew.hide();
+}
+global.showInstallBox=()=>{
+    if (global.installBox)global.installBox.show();
+}
+global.hideInstallBox=()=>{
+    if (global.installBox)global.installBox.hide();
+}
+global.showBindPhone=()=>{
+    if (global.bindPhone)global.bindPhone.show();
+}
+global.hideBindPhone=()=>{
+    if (global.bindPhone)global.bindPhone.hide();
+}
 //wd pass

+ 11 - 1
src/api/config.js

@@ -7,7 +7,7 @@ export const REQ_STATUS={
 }
 
 // export const BASE_API="https://api.24680.com/";
-export const BASE_API="https://api.ouro777.com/";
+export const BASE_API="https://br.24b.pro/";
 
 export const BASE_PREFIX="game/";
 
@@ -93,3 +93,13 @@ export const APP_CHECKIN_GET=BASE_PREFIX+"checkin/get";
 export const APP_FAVO_ADD=BASE_PREFIX+"favo/add";
 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";
+//apk
+export const APP_GAME_APK=BASE_PREFIX+"apk";

+ 48 - 2
src/api/request.js

@@ -1,11 +1,12 @@
 
 // Create axios instance
-import {BASE_API} from "./config";
+import {APP_GAME_APK, BASE_API} from "./config";
 import axios from "axios";
 import {getHttpMsg, localStorageGet} from "@utils/helpers";
 import {LOCAL_ACTIVITY_C, LOCAL_SIGN} from "@constants/localkey";
 import {toast} from "react-toastify";
 import LZString, {compress, compressToEncodedURIComponent, compressToUTF16, decompressFromBase64} from "lz-string";
+import {isStandalone, isTWA} from "@utils/UAUtils";
 
 // axios.defaults.withCredentials = true
 const getSign=()=>{
@@ -25,6 +26,12 @@ const getComParams=()=>{
     if (!!c)params.c=c;
     params._d=global.device;
     params._t=Date.now()+"_"+global.width+"_"+global.height;
+    if (isStandalone()||isTWA()){
+        params.pwa=1;
+    }
+    // if (window.navigator&&window.navigator.userAgent){
+    //     params.ua=window.navigator.userAgent;
+    // }
     return params;
 }
 const checkResponse=(data)=>{
@@ -38,6 +45,15 @@ const checkResponse=(data)=>{
                 }
             });
         }
+        else if (data.code===777){
+            let action={
+                type:"main/updateOpenPay",
+                payload:1,
+            }
+            setTimeout(()=>{
+                if (global.dispatch)global.dispatch(action);
+            },1000)
+        }
     }
 }
 const service = axios.create({
@@ -166,10 +182,40 @@ const adelete=(params={})=>{
         ...params
     });
 }
+const buildQuery=(param)=>{
+    if(param!=null){
+        if(typeof(param)=="string"){
+
+        }else{
+            var query=[];
+            for(var k in param){
+                let v=param[k];
+                if(typeof(v)=="object")v=JSON.stringify(v);
+                query.push(k+"="+escape(v));
+            }
+            param=query.join("&");
+        }
+    }
+
+    return param;
+}
+const getAPKUrl=()=>{
+    let params=getComParams();
+    let url=BASE_API+APP_GAME_APK;
+    if (!window.location.host.includes("localhost")){
+        url+="?lz="+LZString.compressToBase64(JSON.stringify(params));
+    }
+    else
+    {
+        url+="?"+buildQuery(params);
+    }
+    return url;
+}
 const Http={
     get,
     post,
     put,
-    delete:adelete
+    delete:adelete,
+    getAPKUrl
 }
 export default Http;

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 0 - 0
src/assets/newmail.json


BIN
src/assets/share/share_11.png


BIN
src/assets/share/share_12.png


BIN
src/assets/share/share_13.png


BIN
src/assets/share/share_14.png


BIN
src/assets/share/share_16.png


BIN
src/assets/share/share_17.png


BIN
src/assets/share/share_3.png


BIN
src/assets/share/share_7.png


BIN
src/assets/share/share_8.png


+ 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>

+ 21 - 0
src/assets/symbol-defs.svg

@@ -810,6 +810,9 @@
 <symbol id="icon_heart"  viewBox="0 0 1024 1024" >
     <path d="M332.584149,128 C387.910938,128 443.248113,145.17786 489.96482,179.924443 C504.079538,190.427592 523.179588,190.6581 537.47087,180.415525 C642.193981,105.400189 788.253795,115.753007 881.780683,211.243471 C982.058541,314.240478 985.922172,480.436779 893.350806,587.993837 L887.787748,594.485645 L887.787748,594.485645 L542.96512,947.416012 C528.205425,962.524311 505.201238,964.027023 488.800215,951.931454 L483.598136,947.395968 L142.393769,597.715263 L133.492876,587.993837 L133.492876,587.993837 C37.9614692,480.446801 40.9215095,312.42648 142.393769,208.517462 C194.905923,154.738933 263.745036,128 332.584149,128 Z M394.034943,244.918639 C389.66512,229.096154 373.293818,219.807692 357.471333,224.177515 C296.842635,240.911243 246.361866,280.25 215.327842,334.955621 C184.283463,389.661243 176.413641,453.178994 193.147369,513.807692 C196.781984,526.97929 208.742043,535.62574 221.779025,535.62574 C224.388493,535.62574 227.060091,535.284024 229.721333,534.548817 C245.543818,530.178994 254.83228,513.807692 250.462457,497.985207 C224.637014,404.427515 279.736126,307.307692 373.293818,281.482249 C389.116304,277.112426 398.404765,260.741124 394.034943,244.918639 Z"></path>
 </symbol>
+<symbol id="icon_Atmosfera"  viewBox="0 0 182 128">
+    <path d="M162,82.6l-7.6-16.7c-20.9-1.2-42-1.8-63.2-1.8s-42.6.6-63.5,1.8l-7.6,16.7c23.3-1.5,47-2.3,71.1-2.3,23.9,0,47.5.8,70.8,2.3ZM127.9,7.2l-3-6.6c-11.2-.4-22.4-.5-33.7-.5s-22.7.2-34,.5l-3,6.6c12.2-.4,24.6-.7,37-.7,12.3,0,24.5.3,36.7.7ZM148.2,52.2l-15-33.2c-13.9-.5-28-.8-42.1-.8s-28.3.3-42.4.8l-15,33.2c19-1,38.1-1.5,57.4-1.5,19.1,0,38.2.5,57,1.4ZM177.4,116.9c-16.8-1.3-33.8-2.2-50.9-2.7l4.4,9.9c17.2.6,34.3,1.6,51.1,3l-4.6-10.2ZM167.4,94.7c-25.1-1.7-50.5-2.6-76.2-2.6s-51.4.9-76.5,2.6l-4.6,10.1c26.5-2,53.6-3,81.1-3s54.4,1,80.8,3l-4.6-10.1ZM0,127.1c16.8-1.4,33.8-2.4,51-3l4.4-9.9c-17.1.6-34,1.5-50.8,2.8l-4.6,10.2Z"/>
+</symbol>
 <symbol id="icon_redheart"  viewBox="0 0 1024 1024" >
     <defs>
         <linearGradient x1="0%" y1="6.8877551%" x2="100%" y2="93.1122449%" id="linearGradient-1">
@@ -821,5 +824,23 @@
         <path d="M332.584149,128 C387.910938,128 443.248113,145.17786 489.96482,179.924443 C504.079538,190.427592 523.179588,190.6581 537.47087,180.415525 C642.193981,105.400189 788.253795,115.753007 881.780683,211.243471 C982.058541,314.240478 985.922172,480.436779 893.350806,587.993837 L887.787748,594.485645 L887.787748,594.485645 L542.96512,947.416012 C528.205425,962.524311 505.201238,964.027023 488.800215,951.931454 L483.598136,947.395968 L142.393769,597.715263 L133.492876,587.993837 L133.492876,587.993837 C37.9614692,480.446801 40.9215095,312.42648 142.393769,208.517462 C194.905923,154.738933 263.745036,128 332.584149,128 Z M394.034943,244.918639 C389.66512,229.096154 373.293818,219.807692 357.471333,224.177515 C296.842635,240.911243 246.361866,280.25 215.327842,334.955621 C184.283463,389.661243 176.413641,453.178994 193.147369,513.807692 C196.781984,526.97929 208.742043,535.62574 221.779025,535.62574 C224.388493,535.62574 227.060091,535.284024 229.721333,534.548817 C245.543818,530.178994 254.83228,513.807692 250.462457,497.985207 C224.637014,404.427515 279.736126,307.307692 373.293818,281.482249 C389.116304,277.112426 398.404765,260.741124 394.034943,244.918639 Z" id="形状" fill="url(#linearGradient-1)" fill-rule="nonzero"></path>
     </g>
 </symbol>
+
+<symbol id="icon_Aviatrix" viewBox="0 0 300 300">
+    <defs>
+        <style>
+            .cls-1, .cls-2 {
+            stroke-width: 0px;
+            }
+
+            .cls-2 {
+            fill-rule: evenodd;
+            }
+        </style>
+    </defs>
+    <path class="cls-2" d="M170.2,118.1c-.5-.3-1-.5-1.5-.7l4.7-12.6c10.1-25.4,24.6-56.3,41.2-75.2,7.9-8.8,17.9-15.5,29-19.5,3.4-.5,6.8-.8,10.2-.8h40.6c1,0,2,.2,2.9.8.5.4.9.8,1.3,1.3.2.3.4.6.5.9,0,0,0,.1.1.2,0,0,.1,0,.2.1,1.4,2.6.9,5.7-2.1,9.2l-41.5,47.2-67.7,77v-.4c-.1-3.9-1-7.7-2.5-11.2-1.8-4.2-4.6-8-8.1-11.1-2.2-2-4.7-3.7-7.3-5.1ZM199.3,69.3c1.2-4.9,2.9-9.6,4.8-14.3,4-9.1,9.3-17.6,15.9-25.1-4.4,8.7-8.2,17.7-11.3,26.9-1.2,3.6-2.1,7.2-2.9,10.9l18.6-20.5-20.2,28.4c-.5,3-1,6.1-1.4,9.2l17.1-19,4.8-5.5-3.9,5.5-18.7,25.9h-.1c0-.1-.8,7.6-.8,7.6,0,.3,0,.6,0,.9,0,.2,0,.4,0,.5l25.4-28.8-26.3,35.8c-.3,2.1-.6,4.2-1,6.3v.7c-.3.9-.5,1.8-.6,2.8l28.1-32.2-10.5,14.1-19.9,26.9c-1.7,5.5-4,10.7-6.9,15.7,1.4-5.2,2.6-10.5,3.4-15.9v-.8c.2-.5.3-1.1.4-1.6.5-3.4.9-6.8,1.2-10.2,0-1.4.2-2.8.3-4.1.3-3.5.5-6.9.7-10.5.3-5,.8-9.8,1.3-14.7.2-1.6.5-3.2.7-4.8h0c.6-3.4,1.2-6.7,2.1-10Z"/>
+    <path class="cls-2" d="M121.3,143.9l-25.5-51c-.4-1-.5-1.7-.5-2.1,0-1.7.6-3.3,1.8-4.7,1.4-1.6,3.1-2.4,5-2.4h32.6c3.3,0,5.7,1.6,7.1,4.7l16.5,31.5c14,3,24.4,15.4,24.4,30.2s-13.9,30.9-31,30.9-31-13.8-31-30.9.2-4.2.6-6.2ZM163.6,150.1c0,6.5-5.3,11.8-11.8,11.8s-11.8-5.3-11.8-11.8,5.3-11.8,11.8-11.8,11.8,5.3,11.8,11.8Z"/>
+    <path class="cls-2" d="M134.4,183.1c.5.3,1,.5,1.5.7l-4.7,12.6c-10.1,25.4-24.6,56.3-41.2,75.2-7.9,8.8-17.9,15.5-29,19.5-3.4.5-6.8.8-10.2.8H10.3c-1,0-2-.2-2.9-.8-.5-.4-.9-.8-1.3-1.3-.2-.3-.4-.6-.5-.9,0,0,0-.1-.1-.2,0,0-.1,0-.2-.1-1.4-2.6-.9-5.7,2.1-9.2l109.2-124.2v.4c.1,3.9,1,7.7,2.5,11.2,1.8,4.2,4.6,8,8.1,11.1,2.2,2,4.7,3.7,7.3,5.1ZM105.3,231.8c-1.2,4.9-2.9,9.6-4.8,14.3-4,9.1-9.3,17.6-15.9,25.1,4.4-8.7,8.2-17.7,11.3-26.9,1.2-3.6,2.1-7.2,2.9-10.9l-18.6,20.5,20.2-28.4c.5-3,1-6.1,1.4-9.2l-17.1,19-4.8,5.5,3.9-5.5,18.7-25.9h.1c0,.1.8-7.6.8-7.6,0-.3,0-.5,0-.8h0c0-.3,0-.5,0-.6l-25.4,28.8,26.3-35.8c.3-2.1.6-4.2,1-6.3.2-1.2.4-2.3.6-3.5l-28.1,32.2,10.5-14.1,19.9-26.9c1.7-5.5,4-10.7,6.9-15.7-1.5,5.2-2.6,10.5-3.4,15.9v.8c-.1,0-.1,0-.1,0,0,.5-.2,1.1-.2,1.6-.5,3.4-.9,6.8-1.2,10.2,0,1.4-.2,2.8-.3,4.1-.3,3.5-.5,6.9-.7,10.5-.3,5-.8,9.8-1.3,14.7-.2,1.6-.5,3.2-.7,4.8-.6,3.4-1.2,6.7-2.1,10Z"/>
+    <path class="cls-1" d="M183.4,169.4c-6.5,10.6-18.2,17.6-31.6,17.6s-6.1-.4-9-1.1l15.2,29.1c1.4,3.1,3.8,4.7,7.1,4.7h32.6c1.9,0,3.6-.8,5-2.4,1.2-1.4,1.8-3,1.8-4.7s-.2-1-.5-2.1l-20.6-41.2Z"/>
+</symbol>
 </defs>
 </svg>

+ 4 - 4
src/components/DropCoins.jsx

@@ -72,10 +72,10 @@ const DropCoins = ({...props}) => {
                     <Spring {...props} className="d-flex align-items-center justify-content-center flex-1 w-100 h-100 top_view">
                         <Lottie lottieRef={coinRef} loop={false} autoplay={false} className="top_view"  animationData={logo} speed={1.5}  />
                         {/*<Lottie loop={false}   animationData={flycoin} className="top_view" style={{marginLeft:"-15rem"}} />*/}
-                        <Lottie loop={false} lottieRef={coins[0]} autoplay={false} animationData={flycoin} className="top_view" style={{marginLeft:"-70%",marginTop:"-25rem",width:"25rem"}} />
-                        <Lottie loop={false} lottieRef={coins[1]} autoplay={false} animationData={flycoin} className="top_view" style={{marginLeft:"-20%",marginTop:"-35rem",width:"18rem"}} />
-                        <Lottie loop={false} lottieRef={coins[2]} autoplay={false} animationData={flycoin} className="top_view"  style={{marginRight:"-20%",marginTop:"-15rem",width:"20rem"}} />
-                        <Lottie loop={false} lottieRef={coins[3]} autoplay={false} animationData={flycoin} className="top_view"  style={{marginRight:"-60%",marginTop:"-20rem",width:"22rem"}}/>
+                        <Lottie loop={false} lottieRef={coins[0]} autoplay={false} animationData={flycoin} className="top_view" style={{marginLeft:"40%",marginTop:"-25rem",width:"25rem"}} />
+                        <Lottie loop={false} lottieRef={coins[1]} autoplay={false} animationData={flycoin} className="top_view" style={{marginRight:"30%",marginTop:"-35rem",width:"18rem"}} />
+                        <Lottie loop={false} lottieRef={coins[2]} autoplay={false} animationData={flycoin} className="top_view"  style={{marginLeft:"20%",marginTop:"-15rem",width:"20rem"}} />
+                        <Lottie loop={false} lottieRef={coins[3]} autoplay={false} animationData={flycoin} className="top_view"  style={{marginRight:"30%",marginTop:"-20rem",width:"22rem"}}/>
                     </Spring>
                 ):(<></>)
             }

+ 58 - 11
src/components/GameItem/index.jsx

@@ -5,18 +5,47 @@ import symbol from "@assets/symbol-defs.svg";
 import React from "react";
 import {ROUTE_PATH} from "@constants/routes";
 import {localStorageSet} from "@utils/helpers";
-import {LOCAL_GAME_URL} from "@constants/localkey";
+import {LOCAL_GAME_INFO, LOCAL_GAME_URL} from "@constants/localkey";
 import Imgix from "@components/Imgix";
 import LazyImgix from "@components/Imgix/LazyImgix";
 import {ReactComponent as UNLikeIcon} from "@assets/singlesvg/xiai_bai.svg";
 import {ReactComponent as LikeIcon} from "@assets/singlesvg/xiai_hong.svg";
 import {useDispatch, useSelector} from "react-redux";
-import {getGameLike, isLogined, updateGameLike} from "@features/mainSlice";
+import {getGameLike, isLogined, updateGameCardInfo, updateGameLike} from "@features/mainSlice";
 import {updatePageGameLike} from "@features/pageSlice";
 import SvgIcon from "@components/SvgIcon";
 import {reqUpdateFavoInfo} from "@features/api";
-
-const GameItem=({data,handleRoom,...props})=>{
+import styled from 'styled-components/macro';
+export const GameImgAspectRatio=384/516;
+const StyledGridItem = styled.div`
+    ${(props) => {
+        let whStyle=props.$whStyle;
+        let width='';
+        let aspectRatio='';
+        if (whStyle!=null){
+            width=`width:${whStyle.width} !important;`;
+            aspectRatio=`aspect-ratio:${whStyle.aspectRatio} !important;`;
+        }
+        return `
+            ${width}
+            ${aspectRatio}
+        `
+    }}
+`
+const StyledImgWrap=styled.button`
+    aspect-ratio: ${GameImgAspectRatio};
+    ${(props) => {
+        let whStyle=props.$whStyle;
+        let aspectRatio='';
+        if (whStyle!=null){
+            aspectRatio=`aspect-ratio:${whStyle.aspectRatio} !important;`;
+        }
+        return `
+            ${aspectRatio}
+        `
+    }}
+`
+const GameItem=({data,handleRoom,whStyle,...props})=>{
     const navigate=useNavigate();
     const like=useSelector(getGameLike(data.id));
     const isLogin=useSelector(isLogined);
@@ -44,10 +73,21 @@ const GameItem=({data,handleRoom,...props})=>{
         }
         return room;
     }
+    const saveGameInfo=(item)=>{
+        let info={
+            data:item
+        }
+        localStorageSet(LOCAL_GAME_INFO+item.id,JSON.stringify(info));
+    }
     const onEnterGame=()=>{
         let id=data.id;
         let url=data.link;
-        localStorageSet(LOCAL_GAME_URL+id,url);
+        // localStorageSet(LOCAL_GAME_URL+id,url);
+        // saveGameInfo(data);
+        dispatch(updateGameCardInfo({
+            data:data,
+            level:0
+        }))
         let path=ROUTE_PATH.GAME+"/"+id;
         global.authLockPath=path;
         navigate(path);
@@ -99,9 +139,16 @@ const GameItem=({data,handleRoom,...props})=>{
     //         </div>
     //     </div>
     // )
+    const getImgixW=()=>{
+        let baseAspectRatio=GameImgAspectRatio;
+        let whAspectRatio=baseAspectRatio;
+        if (whStyle!=null&&whStyle.aspectRatio!=null)whAspectRatio=Number(whStyle.aspectRatio);
+        let width=parseInt(384*(whAspectRatio/baseAspectRatio));
+        return width;
+    }
     return (
-        <div className={`${styles.grid_item}`} {...props}>
-            <button className={styles.game_img_wrap} onClick={(e)=>{
+        <StyledGridItem $whStyle={whStyle} className={`${styles.grid_item}`} {...props}>
+            <StyledImgWrap $whStyle={whStyle} className={styles.game_img_wrap} onClick={(e)=>{
                 // onEnterGame();
                 // if (handleRoom)handleRoom(e,data);
                 handleEnterRoom(e)
@@ -113,15 +160,15 @@ const GameItem=({data,handleRoom,...props})=>{
                 {/*    wrapperClassName={`lazy-image ${styles.game_img}`}*/}
                 {/*/>*/}
                 {/*<Imgix className={`lazyload ${styles.game_img}`} src={data.img} alt={"game-icon"} width={192}></Imgix>*/}
-                <LazyImgix className={`lazyload ${styles.game_img}`} src={data.img} alt={"game-icon"} width={384}></LazyImgix>
-            </button>
+                <LazyImgix className={`lazyload ${styles.game_img}`} src={data.img} alt={"game-icon"} width={getImgixW()}></LazyImgix>
+            </StyledImgWrap>
             <button className={styles.favorite_con} onClick={handleLike}>
                 {
                     // like?(<LikeIcon></LikeIcon>):(<UNLikeIcon></UNLikeIcon>)
                     <SvgIcon className={`${styles.icon} ${like?styles.icon_active:''}`} herf={like?"icon_Favorites1":"icon_Favorites"}></SvgIcon>
                 }
             </button>
-        </div>
+        </StyledGridItem>
     )
 }
-export default GameItem;
+export default GameItem;

+ 3 - 1
src/components/GameItem/styles.module.scss

@@ -14,8 +14,10 @@
     //display: block;
     position: relative;
     background-color: var(--widget-grid-bg);
-    border-radius: 0.8rem;
+    //border-radius: 0.8rem;
+    border-radius: 0.5rem;
     aspect-ratio:384/516;
+    overflow: hidden;
     & .game_img{
       position: relative;
       width: 100%;

+ 66 - 11
src/components/GameList/index.jsx

@@ -1,11 +1,11 @@
 import styles from "@components/GameList/styles.module.scss";
-import React, {useEffect, useState} from "react";
-import GameItem from "@components/GameItem";
+import React, {useEffect, useRef, useState} from "react";
+import GameItem, {GameImgAspectRatio, GameItemImgAspectRatio} from "@components/GameItem";
 import {REQ_STATUS} from "@api/config";
 import SmallLoading from "@components/SmallLoading";
 import {useThemeProvider} from "@contexts/themeContext";
 import GameRoom from "@components/GameRoom";
-import {total} from "react-big-calendar/lib/utils/dates";
+import {useWindowSize} from "react-use";
 const i18n =global.i18n;
 const objInfo={
     timer:null
@@ -76,7 +76,7 @@ const GameList=({data,status,reqMore,user})=>{
     //         },
     //     ]
     // }
-    const {isMobile} = useThemeProvider();
+    const {isMobile,fontScale} = useThemeProvider();
     const M_LIST_COL=3;
     const M_LIST_ROW=1;
     const [selected,setSelected]=useState(-1);
@@ -84,6 +84,9 @@ const GameList=({data,status,reqMore,user})=>{
     const [showPopRoom,setShowPopRoom]=useState(false);
     const roomHeight=5.2;
     const rowTop=roomHeight+0.75;
+    const gameListRef = useRef(null);
+    const [gridInfo,setGridInfo]=useState({});
+    const {width} = useWindowSize();
     const handleReset=(e)=>{
         setSelected(-1);
         setRoomRow({});
@@ -95,6 +98,39 @@ const GameList=({data,status,reqMore,user})=>{
             resetTimer();
         }
     },[])
+    useEffect(()=>{
+        initGridsInfo();
+    },[gameListRef,width])
+    const getDesktopCol=()=>{
+        if (width>=1264&&width<=1564)return 6;
+        else if (width>=963&&width<=1263)return 5;
+        else if (width>=622&&width<=962)return 4;
+        else if (width<=621)return 3;
+        return 7;
+    }
+    const initGridsInfo=()=>{
+        let parent=gameListRef.current;
+        // if (parent!=null){
+            let totalCol=3;
+            if (!isMobile())totalCol=getDesktopCol();
+            let gridBaseRatio=GameImgAspectRatio;
+            let totalWidth=isMobile()?100*totalCol:160*totalCol;
+            if (parent!=null)totalWidth=parent.clientWidth;
+            let rowGap = parent!=null?window.getComputedStyle(parent).getPropertyValue('grid-row-gap'):"0px";
+            let gap=Number(rowGap.replace("px",""));
+            if (gap===0||isNaN(gap))gap=0.75*16*fontScale;
+            let gridsData={};
+            for (let i=1;i<=6;i++){
+                let gridNum=i;
+                let gridWidth=(totalWidth-(totalCol-1)*gap)/totalCol;
+                let itemWidth=gridWidth*gridNum+(gridNum-1)*gap;
+                let width=itemWidth+"px";
+                let aspectRatio=gridBaseRatio*(itemWidth/gridWidth);
+                gridsData[i]={width,aspectRatio};
+            }
+            setGridInfo(gridsData);
+        // }
+    }
     const resetTimer=()=>{
         if (objInfo.timer!=null){
             clearTimeout(objInfo.timer);
@@ -154,11 +190,15 @@ const GameList=({data,status,reqMore,user})=>{
         if (list==null)list=[];
         return (
             <>
-                <div id={"gameList"} className={styles.game_list}>
+                <div ref={gameListRef} id={"gameList"} className={styles.game_list}>
                     {
                         list.map((item, index) => {
+                            let gridNum=item.gridNum!=null?item.gridNum:1;
+                            // gridNum=Math.floor(Math.random()*3)+1;
+                            let whStyle=gridInfo[gridNum];
+                            if (whStyle==null)return (<></>);
                             return (
-                                <GameItem key={index} data={item} handleRoom={handlePopSelected}></GameItem>
+                                <GameItem key={index} data={item} handleRoom={handlePopSelected} whStyle={whStyle}></GameItem>
                             )
                         })
                     }
@@ -170,9 +210,21 @@ const GameList=({data,status,reqMore,user})=>{
     const formatMList = (list) => {
         let mList = [];
         if (list) {
-            for (let i = 0; i < list.length; i += M_LIST_COL) {
-                let rowArr = list.slice(i, i + M_LIST_COL);
-                mList.push(rowArr);
+            // for (let i = 0; i < list.length; i += M_LIST_COL) {
+            //     let rowArr = list.slice(i, i + M_LIST_COL);
+            //     mList.push(rowArr);
+            // }
+            let count=0;
+            for (let i = 0; i < list.length; i++) {
+                let item = list[i];
+                let gridNum=item.gridNum!=null?item.gridNum:1;
+                let row=Math.floor(count/M_LIST_COL);
+                let rowData=mList[row];
+                if (rowData==null)rowData=[];
+                let nitem={...item,gridNum:gridNum}
+                rowData.push(nitem);
+                mList[row]=rowData;
+                count+=gridNum;
             }
         }
         return mList;
@@ -195,7 +247,7 @@ const GameList=({data,status,reqMore,user})=>{
         list=formatMList(list);
         let len=list.length;
         return (
-            <div id={"gameList"} className={styles.game_list_mobile}>
+            <div ref={gameListRef} id={"gameList"} className={styles.game_list_mobile}>
                 {
                     list.map((rowArr,row)=>{
                         let rowStyle={};
@@ -225,10 +277,13 @@ const GameList=({data,status,reqMore,user})=>{
                                                     itemStyle.transform="scale(1.05)";
                                                 }
                                             }
+                                            let gridNum=item.gridNum!=null?item.gridNum:1;
+                                            // gridNum=Math.floor(Math.random()*3)+1;
+                                            let whStyle=gridInfo[gridNum];
                                             return (
                                                 <GameItem key={col} data={item} handleRoom={(e,data)=>{
                                                     handleSelect(e,data,row,len)
-                                                }} row={row}  style={itemStyle}></GameItem>
+                                                }} row={row} whStyle={whStyle}  style={itemStyle}></GameItem>
                                             )
                                         })
                                     }

+ 9 - 6
src/components/GameList/styles.module.scss

@@ -2,12 +2,15 @@
 .list_container {
   //margin-top: 2.75rem;
   & .game_list {
-    display: grid;
-    grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
-    gap: 1.25rem .75rem;
-    @media screen and (max-width: $width-mobile-max){
-      grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
-    }
+    display: flex;
+    flex-wrap: wrap;
+    //display: grid;
+    //grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
+    //grid-template-columns: repeat(7,1fr);
+    gap:.75rem;
+    //@media screen and (max-width: $width-mobile-max){
+    //  grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
+    //}
   }
   & .game_list_mobile{
     display: flex;

+ 16 - 2
src/components/GameRoom/index.jsx

@@ -8,12 +8,14 @@ import React, {cloneElement, useEffect, useRef, useState} from "react";
 import TransitionGroup from "react-transition-group/TransitionGroup";
 import {useNavigate} from "react-router-dom";
 import {formatCash, localStorageSet} from "@utils/helpers";
-import {LOCAL_GAME_URL} from "@constants/localkey";
+import {LOCAL_GAME_INFO, LOCAL_GAME_URL} from "@constants/localkey";
 import {ROUTE_PATH} from "@constants/routes";
 import styled from 'styled-components/macro';
 import Button from "@mui/material/Button";
 import Popup from "@components/Popup";
 import {useSpringRef} from "@react-spring/core";
+import {updateGameCardInfo} from "@features/mainSlice";
+import {useDispatch} from "react-redux";
 const gameBgs=[null,bg_1,bg_2,bg_3,bg_2];
 const fontColors=["","#bc9350","#208e3c","#bc9350","#208e3c"];
 const i18n=global.i18n;
@@ -43,6 +45,7 @@ const GameRoom = ({open,data,height="5rem",mobile=false,user,onClose,...props})
     })
     const conRef=useRef();
     // const [cstyle,setCstyle]=useState(null)
+    const dispatch=useDispatch();
     useEffect(()=>{
        return ()=>{
 
@@ -59,6 +62,12 @@ const GameRoom = ({open,data,height="5rem",mobile=false,user,onClose,...props})
         }
         global.msgBoxShow(data)
     }
+    const saveGameInfo=(item)=>{
+        let info={
+            data:item
+        }
+        localStorageSet(LOCAL_GAME_INFO+item.id,JSON.stringify(info));
+    }
     const handleGame=(item)=>{
         if (checkLack(item)){
             lackCash();
@@ -68,7 +77,12 @@ const GameRoom = ({open,data,height="5rem",mobile=false,user,onClose,...props})
         let url=data.link;
         if (url.indexOf("?"))url+=("&level="+item.type);
         else url+=("?level="+item.type);
-        localStorageSet(LOCAL_GAME_URL+id,url);
+        localStorageSet(LOCAL_GAME_URL+id,item.type);
+        // saveGameInfo(data);
+        dispatch(updateGameCardInfo({
+            data:data,
+            level:item.type
+        }))
         let path=ROUTE_PATH.GAME+"/"+id;
         global.authLockPath=path;
         navigate(path);

+ 124 - 25
src/components/GameSmallList/index.jsx

@@ -4,7 +4,7 @@ import {NavLink} from "react-router-dom";
 import symbol from "@assets/symbol-defs.svg";
 import {LazyLoadImage} from "react-lazy-load-image-component";
 import {useThemeProvider} from "@contexts/themeContext";
-import GameItem from "@components/GameItem";
+import GameItem, {GameImgAspectRatio} from "@components/GameItem";
 import SvgIcon from "@components/SvgIcon";
 import styled from 'styled-components/macro';
 import GameRoom from "@components/GameRoom";
@@ -101,6 +101,8 @@ const GameSmallList=({data,user})=>{
     // }
     let list=data.data;
     if (list==null)list=[];
+    const gameListRef = useRef(null);
+    const [gridInfo,setGridInfo]=useState({});
     const handleScroll=()=>{
         const gridList=gridListRef.current;
         if (gridList!=null){
@@ -124,6 +126,7 @@ const GameSmallList=({data,user})=>{
             let gap=0;
             if (gridList.children&&gridList.children.length>0){
                 let first=gridList.children[0];
+                if (first&&first.children.length>0)first=first.children[0];
                 let cB=rect.top+first.clientHeight;
                 let cH = document.documentElement.clientHeight;
                 if (cH-cB>90){
@@ -185,6 +188,55 @@ const GameSmallList=({data,user})=>{
         setRoomRow({});
         setShowPopRoom(false);
     }
+    const getDesktopCol=()=>{
+        if (width>=1264&&width<=1564)return 6;
+        else if (width>=963&&width<=1263)return 5;
+        else if (width>=622&&width<=962)return 4;
+        else if (width<=621)return 3;
+        return 7;
+    }
+    const initGridsInfo=()=>{
+        let parent=isMobile()?gridListMRef.current:gridListRef.current;
+        // if (parent!=null){
+        let totalCol=3;
+        if (!isMobile())totalCol=getDesktopCol();
+        let gridBaseRatio=GameImgAspectRatio;
+        let totalWidth=isMobile()?100*totalCol:160*totalCol;
+        if (parent!=null)totalWidth=parent.clientWidth;
+        let rowGap =parent!=null?window.getComputedStyle(parent).getPropertyValue('grid-row-gap'):"0px";
+        let gap=Number(rowGap.replace("px",""));
+        if (gap===0||isNaN(gap))gap=0.75*16*fontScale;
+        let gridsData={};
+        for (let i=1;i<=6;i++){
+            let gridNum=i;
+            let gridWidth=(totalWidth-(totalCol-1)*gap)/totalCol;
+            let itemWidth=gridWidth*gridNum+(gridNum-1)*gap;
+            let width=itemWidth+"px";
+            let aspectRatio=gridBaseRatio*(itemWidth/gridWidth);
+            gridsData[i]={width,aspectRatio};
+        }
+        setGridInfo(gridsData);
+        // }
+    }
+    const formatMList = (list)=>{
+        let mList = [];
+        if (list!=null){
+            if (list.length<=3)mList.push(list);
+            else
+            {
+                for (let i = 0; i < list.length; i++) {
+                    let row=i%2;
+                    let rowList=mList[row];
+                    if (rowList==null){
+                        rowList=[];
+                        mList[row]=rowList;
+                    }
+                    rowList.push(list[i]);
+                }
+            }
+        }
+        return mList;
+    }
     useEffect(() => {
         const gridList=gridListRef.current;
         if (gridList) {
@@ -200,6 +252,9 @@ const GameSmallList=({data,user})=>{
             resetTimer();
         }
     }, []); // 空数组表示只在组件挂载和卸载时执行
+    useEffect(()=>{
+        initGridsInfo();
+    },[gridListRef,gridListMRef,width])
     const onChagePage=(change=1)=>{
         const gridList=gridListRef.current;
         if (gridList){
@@ -253,8 +308,12 @@ const GameSmallList=({data,user})=>{
                 <div className={styles.grid_list} ref={gridListRef}>
                     {
                         list.map((item, index) => {
+                            let gridNum=item.gridNum!=null?item.gridNum:1;
+                            // gridNum=Math.floor(Math.random()*3)+1;
+                            let whStyle=gridInfo[gridNum];
+                            if (whStyle==null)return (<></>);
                             return (
-                                <GameItem key={index} data={item} handleRoom={handlePopSelected}></GameItem>
+                                <GameItem whStyle={whStyle} key={index} data={item} handleRoom={handlePopSelected}></GameItem>
                             )
                         })
                     }
@@ -281,35 +340,75 @@ const GameSmallList=({data,user})=>{
             }
             else
             {
-                if (len<5){
+                if (len<=3){
                     gridStyle.paddingBottom=`${roomHeight+rowGap}rem`;
                 }
-                else gridStyle.rowGap=`${rowTop}rem`;
+                else gridStyle.gap=`${rowTop}rem`;
             }
         }
+        let rowlist=formatMList(list);
         return (
             <>
-                <div className={`${styles.grid_list_m} ${len<5?styles.grid_list_m_onerow:''}`} ref={gridListMRef} style={gridStyle}>
-                    {
-                        list.map((item, index) => {
-                            let itemStyle={}
-                            if (selected!==-1){
-                                let row=index%2;
-                                if (item.id!==selected){
-                                    itemStyle.filter="brightness(60%)";
-                                }
-                                else
-                                {
-                                    itemStyle.transform="scale(1.02)";
-                                }
-                            }
-                            return (
-                                <GameItem key={index} data={item} handleRoom={(e,data)=>{
-                                    handleSelect(e,data);
-                                }} style={itemStyle}></GameItem>
-                            )
-                        })
-                    }
+                <div className={`${styles.grid_list_m}`} ref={gridListMRef}>
+                    <div className={styles.grid_row_con} style={gridStyle}>
+                        {
+                            rowlist.map((ritem,rindex)=>{
+                                if (ritem==null)return (<></>);
+                                return (
+                                    <div key={rindex} className={styles.grid_row}>
+                                        {
+                                            ritem.map((item,index)=>{
+                                                let itemStyle={}
+                                                if (selected!==-1){
+                                                    let row=index%2;
+                                                    if (item.id!==selected){
+                                                        itemStyle.filter="brightness(60%)";
+                                                    }
+                                                    else
+                                                    {
+                                                        itemStyle.transform="scale(1.02)";
+                                                    }
+                                                }
+                                                let gridNum=item.gridNum!=null?item.gridNum:1;
+                                                // gridNum=Math.floor(Math.random()*3)+1;
+                                                let whStyle=gridInfo[gridNum];
+                                                if (whStyle==null)return (<></>);
+                                                return (
+                                                    <GameItem whStyle={whStyle} key={index} data={item} handleRoom={(e,data)=>{
+                                                        handleSelect(e,data);
+                                                    }} style={itemStyle}></GameItem>
+                                                )
+                                            })
+                                        }
+                                    </div>
+                                )
+                            })
+                        }
+                    </div>
+                    {/*{*/}
+                    {/*    list.map((item, index) => {*/}
+                    {/*        let itemStyle={}*/}
+                    {/*        if (selected!==-1){*/}
+                    {/*            let row=index%2;*/}
+                    {/*            if (item.id!==selected){*/}
+                    {/*                itemStyle.filter="brightness(60%)";*/}
+                    {/*            }*/}
+                    {/*            else*/}
+                    {/*            {*/}
+                    {/*                itemStyle.transform="scale(1.02)";*/}
+                    {/*            }*/}
+                    {/*        }*/}
+                    {/*        let gridNum=item.gridNum!=null?item.gridNum:1;*/}
+                    {/*        gridNum=Math.floor(Math.random()*3)+1;*/}
+                    {/*        let whStyle=gridInfo[gridNum];*/}
+                    {/*        if (whStyle==null)return (<></>);*/}
+                    {/*        return (*/}
+                    {/*            <GameItem whStyle={whStyle} key={index} data={item} handleRoom={(e,data)=>{*/}
+                    {/*                handleSelect(e,data);*/}
+                    {/*            }} style={itemStyle}></GameItem>*/}
+                    {/*        )*/}
+                    {/*    })*/}
+                    {/*}*/}
                 </div>
                 <StyledRoomCon top={roomTop}>
                     {renderRoom()}

+ 41 - 15
src/components/GameSmallList/styles.module.scss

@@ -71,7 +71,8 @@
     display: grid;
     grid-template-rows: repeat(var(--grid-rows), 1fr);
     grid-auto-flow: column;
-    grid-auto-columns: calc(100% / var(--grid-num) - (var(--grid-num) - 1)* var(--grid-gap) / var(--grid-num));
+    //grid-auto-columns: calc(100% / var(--grid-num) - (var(--grid-num) - 1)* var(--grid-gap) / var(--grid-num));
+    grid-template-columns: repeat(var(--grid-num),1fr);
     gap: var(--grid-gap);
     position: relative;
     scroll-snap-type: x mandatory;
@@ -81,24 +82,49 @@
     padding-top: .75rem;
   }
   & .grid_list_m{
-    --grid-gap: .75rem;
-    row-gap: 1.25rem;
-    display: grid;
-    grid-template-rows: repeat(2, 1fr);
-    grid-auto-flow: column;
-    //grid-auto-columns: calc(100% / var(--grid-num) - (var(--grid-num) - 1)* var(--grid-gap) / var(--grid-num));
-    grid-auto-columns: calc(100% / 3 - (2)* var(--grid-gap) / 3);
-    &_onerow{
-      grid-template-rows: repeat(1, 1fr);
-    }
-    gap: var(--grid-gap);
+    //--grid-gap: .75rem;
+    //row-gap: 1.25rem;
+    //display: grid;
+    //grid-template-rows: repeat(2, 1fr);
+    //grid-auto-flow: column;
+    ////grid-auto-columns: calc(100% / var(--grid-num) - (var(--grid-num) - 1)* var(--grid-gap) / var(--grid-num));
+    ////grid-auto-columns: calc(100% / 3 - (2)* var(--grid-gap) / 3);
+    //grid-template-columns:repeat(3, 1fr);
+    //&_onerow{
+    //  grid-template-rows: repeat(1, 1fr);
+    //}
+    //gap: var(--grid-gap);
+    //position: relative;
+    //scroll-snap-type: x mandatory;
+    //scroll-behavior: smooth;
+    //overflow-x: auto;
+    //overflow-y: hidden;
+    //transition: all var(--transition);
+    ////z-index: 2;
+    //padding-top: .75rem;
+    //display: flex;
+    //flex-direction: row;
+    width: 100%;
+    height:auto;
     position: relative;
     scroll-snap-type: x mandatory;
     scroll-behavior: smooth;
     overflow-x: auto;
     overflow-y: hidden;
-    transition: all var(--transition);
-    //z-index: 2;
-    padding-top: .75rem;
+    gap: .75rem;
+    & .grid_row_con{
+      display: flex;
+      flex-direction: column;
+      gap: .75rem;
+      transition: all var(--transition);
+      padding-top: .75rem;
+      & .grid_row{
+        display: grid;
+        gap:.75rem;
+        grid-auto-flow: column;
+        grid-template-rows: repeat(1, 1fr);
+        grid-template-columns:repeat(3, 1fr);
+      }
+    }
   }
 }

+ 52 - 0
src/components/MailNew.jsx

@@ -0,0 +1,52 @@
+import {useEffect, useState} from "react";
+import Spring from "@components/Spring";
+import Lottie from "lottie-react";
+import mailnew from "@assets/newmail.json";
+const objInfo={
+    timerIds:[]
+}
+const MailNew = ({...props}) => {
+    const [isShow, setIsShow] = useState(false);
+    const show=()=>{
+        setIsShow(true);
+    }
+    const hide=()=>{
+        setIsShow(false);
+    }
+    const resetTimer=()=>{
+        if (objInfo.timerIds){
+            for (let i = 0; i < objInfo.timerIds; i++) {
+                clearTimeout(objInfo.timerIds[i]);
+            }
+            objInfo.timerIds=[];
+        }
+    }
+    useEffect(()=>{
+        const mailNew={
+            show,
+            hide
+        };
+        global.mailNew=mailNew;
+    },[])
+    useEffect(() => {
+        resetTimer();
+        if (isShow){
+            let timer=setTimeout(()=>{
+                setIsShow(false);
+            },4000);
+            objInfo.timerIds.push(timer);
+        }
+    },[isShow])
+    return (
+        <>
+            {
+                isShow?(
+                    <Spring {...props} className="d-flex align-items-center justify-content-center flex-1 w-100 h-100 top_view">
+                        <Lottie loop={false} autoplay={true} className="top_view"  animationData={mailnew}  />
+                    </Spring>
+                ):(<></>)
+            }
+        </>
+    )
+}
+export default MailNew;

+ 2 - 1
src/constants/localkey.js

@@ -5,6 +5,7 @@ const LOCAL_USER_INFO="USER_INFO";
 const LOCAL_IS_LOGIN="IS_LOGIN";
 const LOCAL_SIGN="G_SIGN";
 const LOCAL_GAME_URL="LOCAL_GAME_URL_";
+const LOCAL_GAME_INFO="LOCAL_GAME_INFO_";
 const LOCAL_CUSTOM_LOCK="LOCAL_CUSTOM_LOCK";
 const LOCAL_WHEEL_RETAIN="LOCAL_WHEEL_RETAIN";
 const LOCAL_WHEEL_INVITE_CODE="LOCAL_WHEEL_INVITE_CODE";
@@ -13,4 +14,4 @@ const LOCAL_WD_PASS="LOCAL_WD_PASS";
 const LOCAL_WD_PASS_SET_TIME="LOCAL_WD_PASS_SET_TIME";
 const LOCAL_FAVO_IDS="LOCAL_FAVO_IDS";
 const LOCAL_FAVO_DATA="LOCAL_FAVO_DATA";
-export {LOCAL_VISTOR_ID,LOCAL_USER_INFO,LOCAL_IS_LOGIN,LOCAL_SIGN,LOCAL_GAME_URL,LOCAL_CUSTOM_LOCK,LOCAL_WHEEL_RETAIN,LOCAL_WHEEL_INVITE_CODE,LOCAL_WD_PASS,LOCAL_WD_PASS_SET_TIME,LOCAL_ACTIVITY_C,LOCAL_FAVO_IDS,LOCAL_FAVO_DATA};
+export {LOCAL_VISTOR_ID,LOCAL_USER_INFO,LOCAL_IS_LOGIN,LOCAL_SIGN,LOCAL_GAME_URL,LOCAL_CUSTOM_LOCK,LOCAL_WHEEL_RETAIN,LOCAL_WHEEL_INVITE_CODE,LOCAL_WD_PASS,LOCAL_WD_PASS_SET_TIME,LOCAL_ACTIVITY_C,LOCAL_FAVO_IDS,LOCAL_FAVO_DATA,LOCAL_GAME_INFO};

+ 5 - 0
src/contexts/themeContext.js

@@ -6,6 +6,7 @@ import {ROUTE_PATH} from "@constants/routes";
 import {useSelector} from "react-redux";
 import {getUserInfo, isLogined} from "@features/mainSlice";
 import SoundHelp from "@utils/SoundManger";
+import usePreviousRoute from "@hooks/usePreviousRoute";
 
 const ThemeContext = createContext(undefined);
 
@@ -65,6 +66,7 @@ export const ThemeProvider = ({children}) => {
     const [isOpenSub, setIsOpenSub]=useState(false);
     const isLogin=useSelector(isLogined);
     const user=useSelector(getUserInfo);
+    const {preLocation,gameID}=usePreviousRoute();
     const initWindowSize=()=>{
         if (isMobile())global.device="m";
         else global.device="d";
@@ -206,6 +208,9 @@ export const ThemeProvider = ({children}) => {
                 //sound
                 soundOpen,
                 toggleSoundOpen,
+                //preroute hook
+                preLocation,
+                gameID
             }}>
             {children}
         </ThemeContext.Provider>

+ 13 - 13
src/db/routes.js

@@ -295,19 +295,19 @@ export const routes=[
         lpath:"#/signin",//登录path
         subs:[]
     },
-    {
-        path: "#/message",
-        type: 1,
-        side: 0,
-        block:1,//块排序
-        title: "links.route_title.msg",
-        icon:"",
-        fill:";;",//icon 颜色 默认 选中 本身颜色
-        component:"mail",
-        login:1,//0,1 检不检查登录
-        lpath:"#/signin",//登录path
-        subs:[]
-    },
+    // {
+    //     path: "#/message",
+    //     type: 1,
+    //     side: 0,
+    //     block:1,//块排序
+    //     title: "links.route_title.msg",
+    //     icon:"",
+    //     fill:";;",//icon 颜色 默认 选中 本身颜色
+    //     component:"mail",
+    //     login:1,//0,1 检不检查登录
+    //     lpath:"#/signin",//登录path
+    //     subs:[]
+    // },
     {
         path: "#/messageDetail",
         type: 1,

+ 12 - 2
src/features/actions/hallClient.js

@@ -1,6 +1,6 @@
 import {APP_SERVER} from "@constants/app";
 import {connectServer, disconnectServer, onDisconnect, sendToServer} from "@features/socketSlice";
-import {reqGameInfo} from "@features/api";
+import {reqGameInfo, reqMailCheck} from "@features/api";
 import {updatePayFinish} from "@features/paySlice";
 import {trackEvent} from "../../tracking/TrackAll";
 import {toast} from "react-toastify";
@@ -15,10 +15,12 @@ const __playground_message_types=(message,dispatch)=>{
 
 }
 const handlePayFinish=(message,dispatch)=>{
-    dispatch(reqGameInfo());
     dispatch(updatePayFinish(message));
     let {data}=message;
     if (data&&data.PayNum>=200)global.showDropCoins();
+    setTimeout(()=>{
+        dispatch(reqGameInfo());
+    },1000)
 }
 const handleKick=(message,dispatch)=>{
     let onSure=()=>{
@@ -72,6 +74,14 @@ const roger=(message,dispatch)=>{
                             trackEvent(data.event,data.params)
                             break;
                         }
+                        case "refresh_mail":{
+                            //有新的邮件刷新邮件
+                            //播放动画newmail.json
+                            global.showMailNew();
+                            //刷新mail 红点
+                            dispatch(reqMailCheck());
+                            break;
+                        }
                     }
 
 

+ 114 - 10
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_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";
@@ -30,6 +60,7 @@ import {formatCash, localStorageGet, sleep} from "@utils/helpers";
 import {LOCAL_ACTIVITY_C, LOCAL_CUSTOM_LOCK, LOCAL_WHEEL_INVITE_CODE} from "@constants/localkey";
 import {toast} from "react-toastify";
 import {preDelMail} from "@features/mailSlice";
+import {getInAppBrowser} from "@utils/UAUtils";
 const i18n=global.i18n;
 export const buildReqReducer=(builder,thunk,callback,statusCB=null)=>{
     if (builder==null||thunk==null)return;
@@ -56,7 +87,20 @@ export const buildReqReducer=(builder,thunk,callback,statusCB=null)=>{
 export const reqGetRoutes = createAsyncThunk(
     APP_ROUTES,
     async (thunkAPI) => {
-        const response = await Http.get({url:APP_ROUTES})
+        let params={
+            url:APP_ROUTES,
+            params:{
+
+            }
+        }
+        if (window.navigator&&window.navigator.userAgent){
+            params.params.ua=window.navigator.userAgent;
+        }
+        let ia=getInAppBrowser();
+        if (ia!=null&&ia!==""){
+            params.params.ia=ia;
+        }
+        const response = await Http.get(params)
         return response;
     },
 )
@@ -725,4 +769,64 @@ export const reqFavoGet=createAsyncThunk(
         return response;
     }
 )
+//game card info
+export const reqGameCardInfo=createAsyncThunk(
+    APP_GAME_CARD_INFO,
+    async (arg,thunkAPI)=>{
+        let params={
+            url:APP_GAME_CARD_INFO,
+            params:{
+                id:arg
+            }
+        }
+        const response=await Http.get(params);
+        return response;
+    }
+)
+//otp
+export const reqSendCode=createAsyncThunk(
+    APP_SEND_CODE,
+    async (arg,thunkAPI)=>{
+        let params={
+            url:APP_SEND_CODE,
+            params:{
+                Phone:arg.phone
+            }
+        }
+        const response=await Http.get(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

+ 4 - 1
src/features/colyseus/CSClient.js

@@ -77,7 +77,10 @@ class CSClient {
                 self.pingTimeout--;
                 return;
             }
-            console.log("Room:"+self.roomName+" onMessage:"+" type:"+type+" message:"+JSON.stringify(message))
+            let host=window.location.host;
+            if (host.startsWith("localhost")){
+                console.log("Room:"+self.roomName+" onMessage:"+" type:"+type+" message:"+JSON.stringify(message))
+            }
             if (self.messageCB)self.messageCB(type,message);
         })
         room.onError((code, message) => {

+ 129 - 7
src/features/mainSlice.js

@@ -2,6 +2,7 @@ import { initTrackAll ,trackEvent} from "../tracking/TrackAll"
 import {createAsyncThunk, createSlice} from "@reduxjs/toolkit";
 import {routes} from "@db/routes";
 import {
+    ROUTE_PATH,
     ROUTE_TYPE_CUSTOM,
     ROUTE_TYPE_FULL,
     ROUTE_TYPE_IN,
@@ -11,19 +12,19 @@ import {
 } from "@constants/routes";
 import {REQ_STATUS} from "@api/config";
 import {
-    buildReqReducer, reqChangeEmail,
+    buildReqReducer, reqBindSMS, reqChangeEmail,
     reqChangeInfo,
-    reqChangePass, reqChangePhone, reqCustomLeaveMsg, reqCustomService, reqFavoGet, reqGameInfo,
+    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";
 import {
     LOCAL_CUSTOM_LOCK,
-    LOCAL_FAVO_IDS,
+    LOCAL_FAVO_IDS, LOCAL_GAME_URL,
     LOCAL_IS_LOGIN,
     LOCAL_SIGN,
     LOCAL_USER_INFO,
@@ -175,11 +176,15 @@ const reducerRoutes=(state,action)=>{
                 userInfo=user;
                 //请求pay
                 // global.dispatch(reqPayConfig(user));
+                let sign=user.sign;
+                if (sign!=null)localStorageSet(LOCAL_SIGN,sign);
             }
         }
         let conf=json.conf;
         let hallServerUrl='';
         let promoteInstall=0;
+        let guest=0;
+        let upgradeBonus=0;
         if (conf){
             if (!!conf.hall)hallServerUrl=conf.hall;
             if (!!conf.DOLLAR)global.DOLLAR=conf.DOLLAR;
@@ -191,10 +196,13 @@ const reducerRoutes=(state,action)=>{
             }
             //promoteInstall
             if (!!conf.promoteInstall)promoteInstall=conf.promoteInstall;
+            //guest
+            if (!!conf.guest)guest=conf.guest;
+            if (!!conf.upgradeBonus)upgradeBonus=conf.upgradeBonus;
         }
         //favorites
         // let favorites=getFavorites(json.favorites);
-        Object.assign(state, {routes:state.routes,asyncRoutes:data,styles:styles,blocks:blocks,...generData,isLogin:isLogin,userInfo:userInfo,hallServerUrl:hallServerUrl,promoteInstall:promoteInstall});
+        Object.assign(state, {routes:state.routes,asyncRoutes:data,styles:styles,blocks:blocks,...generData,isLogin:isLogin,userInfo:userInfo,hallServerUrl:hallServerUrl,promoteInstall:promoteInstall,guest:guest,upgradeBonus:upgradeBonus});
     }
 }
 const routesStatus=(state,status,arg)=>{
@@ -390,6 +398,53 @@ const reducerFavoGet=(state,action,arg)=>{
         state.favorites=favorites;
     }
 }
+const reducerGameCardInfo=(state,action,arg)=>{
+    let payload=action.payload;
+    if (payload&&payload.code===200){
+        let data=payload.data;
+        if (data!=null){
+            let id=Number(data.id);
+            let level_info=data.level_info;
+            let level=level_info!=null?1:0;
+            let local=parseInt(localStorageGet(LOCAL_GAME_URL+id,0));
+            if (local>0)level=local;
+            state.gameCardInfo[id]={...data,level:level}
+        }
+    }
+}
+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',
@@ -404,6 +459,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,
@@ -416,12 +474,29 @@ export const Main=createSlice({
         gameInfoStatus:REQ_STATUS.NONE,
         customLeaveMsgStatus:REQ_STATUS.NONE,
         customServiceStatus:REQ_STATUS.NONE,
+        //客服
         customerInfo:[],
+        //大厅服务器
         hallServerUrl:'',
+        //收藏列表
         favorites:[],
+        //当前游戏id
         gameID:0,
+        //服务器踢
         serverKick:0,
-        promoteInstall:0
+        //安装提示
+        promoteInstall:0,
+        //服务器返回777打开充值
+        openPay:0,
+        gameCardInfoStatus:REQ_STATUS.NONE,
+        //游戏卡信息
+        gameCardInfo:{},
+        //otp 时间戳
+        otpTimeStamp:0,
+        //是否是guest登录
+        guest:0,
+        //绑定奖励
+        upgradeBonus:0
     },
     reducers: {
         logout:(state, action)=>{
@@ -474,6 +549,24 @@ export const Main=createSlice({
         },
         updateServerKick:(state,action)=>{
             state.serverKick=action.payload;
+        },
+        updateOpenPay:(state,action)=>{
+            state.openPay=action.payload;
+        },
+        updateGameRoute:(state,action)=> {
+            let route = state.allMap[ROUTE_PATH.GAME + "/*"];
+            if (route != null) {
+                let type = action.payload;
+                route.type = type;
+            }
+        },
+        updateGameCardInfo:(state,action)=>{
+            let {data,level}=action.payload;
+            if (data!=null){
+                if (level==null)level=0;
+                let id=data.id;
+                state.gameCardInfo[id]={...data,level};
+            }
         }
     },
     extraReducers: (builder) => {
@@ -492,9 +585,13 @@ export const Main=createSlice({
         buildReqReducer(builder,reqCustomLeaveMsg,reducerCustomLeaveMsg,customLeaveMsgStatus);
         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} = Main.actions;
+export const {logout,updateUserInfo,updateUserScore,updateAddScore,updateGameLike,updateCurrGame,updateServerKick,updateOpenPay,updateGameRoute,updateGameCardInfo} = Main.actions;
 //数据获取
 export const getFetchStatus=(state)=>{
     return state.main.status;
@@ -592,6 +689,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;
@@ -663,6 +772,19 @@ export const getKickState=(state)=>{
 export const getPromoteInstall=(state)=>{
     return state.main.promoteInstall;
 }
+export const getOpenPay=(state)=>{
+    return state.main.openPay;
+}
+export const getGameCardInfo=(id)=>(state)=>{
+    id=Number(id);
+    return state.main.gameCardInfo[id];
+}
+export const getIsGuest=(state)=>{
+    return state.main.guest;
+}
+export const getUpgradeBonus=(state)=>{
+    return state.main.upgradeBonus;
+}
 //数据获取
 export default Main.reducer;
 

+ 2 - 1
src/features/paySlice.js

@@ -206,7 +206,8 @@ const reducerWithdrawPayout=(state,action,arg)=>{
     let payload=action.payload;
     let msg=getHttpMsg(payload);
     if (payload&&payload.code===200){
-        toast.success(msg);
+        // toast.success(msg);
+        toast.success(i18n.t("withdraw.tip.success"));
     }
     else
     {

+ 65 - 0
src/hooks/useGameIFrame.js

@@ -0,0 +1,65 @@
+import {useEffect, useRef, useState} from "react";
+import {ROUTE_PATH} from "@constants/routes";
+import {openFullScreen} from "@utils/helpers";
+import {useNavigate} from "react-router-dom";
+
+const useGameIFrame = () => {
+    const iFrameRef = useRef(null);
+    const [isGameLoad,setIsGameLoad]=useState(false);
+    const navigate = useNavigate();
+    const onBack=()=>{
+        // dispatch(reqGameInfo());
+        navigate(ROUTE_PATH.BACK);
+    }
+    const receiveMessage=(event)=>{
+        let data=event.data;
+        // console.log(data);
+        if (data&&data.cmd!=null){
+            // toast.success(JSON.stringify(data))
+            if (data.cmd==="back"){
+                onBack();
+            }
+            else if (data.cmd==="pay"){
+                navigate(ROUTE_PATH.PAY);
+            }
+            else if (data.cmd==="withdraw"){
+                navigate(ROUTE_PATH.SAQUE);
+            }
+            else if (data.cmd==="fullScreen"){
+                openFullScreen();
+            }
+            else if (data.cmd==="closeLoading"){
+                setIsGameLoad(true);
+            }
+        }
+    }
+    const handleLoadGame=()=>{
+        setIsGameLoad(true);
+    }
+    const initGameEvent=()=>{
+        const gameFrame = iFrameRef.current;
+        if(gameFrame!=null){
+            gameFrame.addEventListener('load', handleLoadGame);
+        }
+        window.addEventListener("message", receiveMessage, false);
+    }
+    const removeGameEvent=()=>{
+        const gameFrame = iFrameRef.current;
+        if(gameFrame!=null){
+            gameFrame.removeEventListener('load', handleLoadGame);
+        }
+        window.removeEventListener("message", receiveMessage);
+    }
+    useEffect(() => {
+        initGameEvent();
+        return () => {
+            removeGameEvent();
+        };
+    }, [iFrameRef]);
+    return {
+        iFrameRef,
+        isGameLoad,
+        setIsGameLoad
+    };
+}
+export default useGameIFrame;

+ 1 - 1
src/hooks/usePreviousRoute.js

@@ -26,7 +26,7 @@ const usePreviousRoute = () => {
             setPreLocation({...currentLocation})
             setCurrentLocation({...location});
             parseGameID();
-        }, [location]);
+        }, [location.pathname]);
     return {
         preLocation,
         gameID

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

@@ -7,10 +7,11 @@ import symbol from '@assets/symbol-defs.svg';
 import {useThemeProvider} from "@contexts/themeContext";
 import SvgIcon from "@components/SvgIcon";
 const i18n=global.i18n;
-const BreadCrumb=()=>{
+const BreadCrumb=({customChain=null})=>{
     const {isMobile} = useThemeProvider();
     const {pathname} = useLocation();
-    const chain=useSelector(getRouteChain(pathname));
+    const routeChain=useSelector(getRouteChain(pathname));
+    const chain=customChain==null?routeChain:customChain;
     const currRoute=useSelector(getRoute(pathname));
 
     useEffect(() => {

+ 1 - 0
src/layout/BreadCrumb/styles.module.scss

@@ -20,6 +20,7 @@
     & .nav_active{
       color: var(--header);
       cursor: default;
+      pointer-events: none;
     }
     & .icon{
       width: .75rem;

+ 16 - 3
src/layout/Layout.jsx

@@ -6,8 +6,8 @@ import AppContainer from "@layout/AppContainer";
 import React, {cloneElement, createRef, lazy, Suspense, useEffect, useRef, useState} from "react";
 import LoadingScreen from "@components/LoadingScreen";
 import {Route, Routes, useLocation, useNavigate} from "react-router-dom";
-import {useSelector} from "react-redux";
-import {isFullRoute, isLogined, routesIn, routesPop} from "@features/mainSlice";
+import {useDispatch, useSelector} from "react-redux";
+import {isFullRoute, isLogined, routesIn, routesPop, updateGameRoute} from "@features/mainSlice";
 import {preventDefault} from "@utils/helpers";
 import {useThemeProvider} from "@contexts/themeContext";
 import CRoute from "@layout/Routes";
@@ -25,10 +25,13 @@ import useAuthLockRoute from "@hooks/useAuthLockRoute";
 import WHallWheel from "@widgets/WCashWheel/WHallWheel/WHallWheel";
 import LoadingIn from "@components/Loading/LoadingIn";
 import WDPassBox from "@widgets/WWithdraw/PassBox/WDPassBox";
-import {ROUTE_PATH} from "@constants/routes";
+import {ROUTE_PATH, ROUTE_TYPE_FULL, ROUTE_TYPE_IN} from "@constants/routes";
 import DropCoins from "@components/DropCoins";
 import {toast} from "react-toastify";
 import useEdgeGesture from "@hooks/useEdgeGesture";
+import MailNew from "@components/MailNew";
+import WInstallBox from "@widgets/WInstallBox";
+import WBindPhone from "@widgets/WBindPhone";
 const PageNotFound = lazy(() => import('@pages/PageNotFound'));
 
 const Layout=({...props})=>{
@@ -43,6 +46,8 @@ const Layout=({...props})=>{
     const navigate=useNavigate();
     const {getSidedGesture}=useEdgeGesture();
     const isGesture=getSidedGesture();
+    const mobile=isMobile();
+    const dispatch = useDispatch();
     // const {setLockPath}=useAuthLockRoute();
     useEffect(() => {
         // scroll to top on route change
@@ -52,6 +57,11 @@ const Layout=({...props})=>{
     useEffect(()=>{
         global.msgBox=msgBoxRef.current;
     },[msgBoxRef])
+    useEffect(()=>{
+        let routeType=ROUTE_TYPE_FULL;
+        if (!mobile)routeType=ROUTE_TYPE_IN;
+        dispatch(updateGameRoute(routeType));
+    },[mobile])
     const renderIn=()=>{
         let isShowSub=false;
         if (isMobile()&&isOpenSub)isShowSub=true;
@@ -112,6 +122,8 @@ const Layout=({...props})=>{
                     setIsShowWheel(false);
                     if (props&&props.onCloseHallWheel)props.onCloseHallWheel()
                 }}></WHallWheel>
+                {/*<WInstallBox/>*/}
+                <WBindPhone/>
             </>
         )
     }
@@ -119,6 +131,7 @@ const Layout=({...props})=>{
         return (
             <>
                 <DropCoins/>
+                <MailNew/>
             </>
         )
     }

+ 4 - 1
src/layout/Navbar/styles.module.scss

@@ -34,8 +34,10 @@
   padding-left:85px;
   box-shadow:var(--widget-shadow);
   pointer-events: auto;
+  transition: padding .3s ease;
   &.nav_open{
-    padding-left:270px;
+    //padding-left:270px;
+    padding-left:16.9rem;
   }
   &_mobile{
     //position:fixed;
@@ -82,6 +84,7 @@
   &.nav_open{
     padding-left:16.9rem;
   }
+  transition: padding .3s ease;
 }
 .btn_sign_in{
   //padding: 12px 5px !important;

+ 0 - 1
src/layout/Routes/index.jsx

@@ -7,7 +7,6 @@ import CSSTransition from "react-transition-group/CSSTransition";
 import SubScreen from "@components/SubScreen";
 import RouteContainer from "@layout/Routes/RouteContainer";
 import {Navigate} from "react-router";
-import usePreviousRoute from "@hooks/usePreviousRoute";
 import {ROUTE_PATH, ROUTE_TYPE_POP_OR_FULL} from "@constants/routes";
 import {useThemeProvider} from "@contexts/themeContext";
 import useAuthLockRoute from "@hooks/useAuthLockRoute";

+ 1 - 1
src/layout/Sidebar/styles.js

@@ -688,7 +688,7 @@ const StyledBlock=styled.nav`
     let display=column>1?"grid":"flex";
     let grid_template_columns=column>1&&open?`grid-template-columns: repeat(${column}, minmax(0, 1fr));`:'';
     let flex_direction=column>1?'':'flex-direction: column;';
-    let padding=props.open?'padding: 0px 1rem;':'padding: 0px 0.43rem;';
+    let padding=props.open?'padding: 0px 0.75rem;':'padding: 0px 0.43rem;';
     return`
       display: ${display};
       ${grid_template_columns}

+ 67 - 14
src/locales/languages/en_US.json

@@ -62,8 +62,8 @@
     "dark": "Dark",
     "light": "Light"
   },
-  "sign_in": "Sign in",
-  "sign_up": "Sign up",
+  "sign_in": "Login",
+  "sign_up": "Register",
   "footer": {
     "game_res": "Gaming Responsibilities",
     "privacy_policy": "Privacy Policy",
@@ -90,16 +90,16 @@
     "cancel": "Cancel"
   },
   "login": {
-    "login_title": "Sign In",
+    "login_title": "Login",
     "phone_ph": "Phone Number",
     "login_pwd": "Login Password",
     "forgot": "Forgot your password?",
-    "sign_in_btn": "Sign In",
+    "sign_in_btn": "Login",
     "reg_new_des": "New to 24680?",
-    "reg_new_btn": "Create account",
-    "reg_title": "Sign Up",
-    "re_pwd": "Confirm login password",
-    "sign_up_btn": "Sign Up",
+    "reg_new_btn": "Register",
+    "reg_title": "Register",
+    "re_pwd": "Confirm Login Password",
+    "sign_up_btn": "Register",
     "login_des": "Already have an account?",
     "account": {
       "empty": "Please enter username!"
@@ -108,7 +108,14 @@
       "empty": "Please enter password!"
     },
     "success": "Login successful, welcome back!",
-    "notfound": "Your account was not found, please register or try again!"
+    "notfound": "Your account was not found, please register or try again!",
+    "login_type": {
+      "password": "Password",
+      "otp": "SMS"
+    },
+    "otp_ph": "Verification Code",
+    "guest_login": "Guest Login",
+    "btn_otp": "Send Code"
   },
   "logout": {
     "success": "You have exited successfully."
@@ -133,7 +140,7 @@
   "settings": {
     "user_info": {
       "welcome": "Welcome to 24680.com",
-      "tips": "Please Sign up or Log in first",
+      "tips": "Please Register or Login first",
       "name": "Name:",
       "id": "ID:"
     },
@@ -144,7 +151,9 @@
     "email": "Email",
     "security": "Security",
     "install_banner": "Show Install Notice",
-    "sound": "Sound"
+    "sound": "Sound",
+    "bindPhone": "Account Upgrade",
+    "bonus": "Get Bonus +{0}"
   },
   "copy_success": "Copy successful",
   "user": {
@@ -190,7 +199,7 @@
       "create_date": "Creation date",
       "time": "Time",
       "order": "Request number",
-      "content": "Pay the rope",
+      "content": "Pay the code",
       "value": "Value",
       "copyButton": "Copy pix code",
       "lookButton": "Already paid",
@@ -228,7 +237,13 @@
     "back": "Back",
     "msg_title": "Leave the game",
     "msg_content": "Are you sure you want to leave?",
-    "cancel": "Cancel"
+    "cancel": "Cancel",
+    "brand_by": "By",
+    "tips": {
+      "movie_mode": "Movie Mode",
+      "full_screen": "Full Screen"
+    },
+    "share_title": "Share This Game"
   },
   "msgbox": {
     "title": "Prompt",
@@ -339,6 +354,20 @@
       "processing": "Processing",
       "detail_title": "Record Details"
     },
+    "tip": {
+      "success": "The submission is successful and the system is under review",
+      "total_not_enough": "The withdrawable amount is not enough",
+      "under_can_score": "The single withdrawal quantity cannot be less than {0}",
+      "over_can_score": "The single withdrawal quantity cannot be higher than {0}",
+      "exceed_count_limit": "Only {0} withdrawal requests can be initiated per day",
+      "user_score_not_enough": "Your Chip is not enough",
+      "user_in_game": "Sorry, You're in game for now",
+      "need_recharge": "Your withdrawal request amount is too much, you must top up before you can continue to withdraw",
+      "btn_to_recharge": "To recharge",
+      "unbound_account": "You have not bound a bank account and cannot withdraw",
+      "account_abnormal": "The system has detected an abnormal operation in your account, and your withdrawal has been prohibited. Please contact customer service",
+      "score_null": "You didn't fill in the withdrawal amount"
+    },
     "help": {
       "title": "Withdrawal Rules",
       "rule_1": "1. The minimum/maximum amount which you can withdraw every time is {0}/ {1};",
@@ -577,6 +606,17 @@
     "reconnect": "Reconnect",
     "disconnected": "The network is disconnected. Do you want to reconnect?"
   },
+  "installBox": {
+    "tips": "We would love to add 24680.com to your home screen for the latest free bets and bonus updates.",
+    "btn_cancel": "Cancel",
+    "btn_install": "Install"
+  },
+  "bindPhone": {
+    "title_link_phone": "Account Upgrade",
+    "phone_award": "Award:",
+    "btn_submit": "SUBMIT",
+    "tips": "Link your Mobile and get {0} chips reward"
+  },
   "web": {
     "activity": {
       "redpack_not_available": "There is no RedPack",
@@ -637,7 +677,14 @@
       "score_fail": "Amount must be an integer value.",
       "params_error": "params error",
       "pix_type_missing": "The PIX type is missing",
-      "cpf_number_used": "The CPF number has been used multiple times and cannot be saved"
+      "cpf_number_used": "The CPF number has been used multiple times and cannot be saved",
+      "no_withdraw_value": "Insufficient withdrawal amount",
+      "too_low": "Lower than the minimum withdrawal amount",
+      "too_high": "Higher than the maximum withdrawal amount",
+      "day_max": "Exceeded the daily withdrawal limit",
+      "no_enouth_score": "Not enough money on hand",
+      "in_game": "Opened in the game",
+      "no_deposit": "You need deposit to withdraw"
     },
     "payment": {
       "paytype_error": "PayType ERROR",
@@ -651,6 +698,12 @@
       "recharge_required": "Recharge is required to daily bonus",
       "unknown_error": "Unknown error",
       "exception_occurred": "An error occurred while processing your request"
+    },
+    "verify": {
+      "num_empty": "Phone number is empty",
+      "code_too_long": "Phone code is too long",
+      "already_bound": "The phone number is already bound",
+      "code_incorrect_or_expired": "The code is incorrect or has expired"
     }
   }
 }

+ 61 - 8
src/locales/languages/es_MX.json

@@ -59,7 +59,7 @@
     "dark": "Oscuro",
     "light": "Claro"
   },
-  "sign_in": "Iniciar sesión",
+  "sign_in": "Acceso",
   "sign_up": "Registrarse",
   "footer": {
     "game_res": "Responsabilidades del Juego",
@@ -89,13 +89,13 @@
   "login": {
     "login_title": "Iniciar Sesión",
     "phone_ph": "Número de Teléfono",
-    "login_pwd": "Contraseña de Inicio de Sesión",
+    "login_pwd": "Contraseña",
     "forgot": "¿Olvidaste tu contraseña?",
     "sign_in_btn": "Iniciar Sesión",
     "reg_new_des": "¿Nuevo en 24680?",
     "reg_new_btn": "Crear cuenta",
     "reg_title": "Registrarse",
-    "re_pwd": "Confirmar contraseña de inicio de sesión",
+    "re_pwd": "Confirmar Contraseña",
     "sign_up_btn": "Registrarse",
     "login_des": "¿Ya tienes una cuenta?",
     "account": {
@@ -105,7 +105,14 @@
       "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": "SMS"
+    },
+    "otp_ph": "Código de verificación",
+    "guest_login": "Acceso de usuario invitado",
+    "btn_otp": "Verificar"
   },
   "logout": {
     "success": "Has salido exitosamente."
@@ -141,7 +148,9 @@
     "email": "Correo Electrónico",
     "security": "Seguridad",
     "install_banner": "Mostrar Aviso de Instalación",
-    "sound": "Sonido"
+    "sound": "Sonido",
+    "bindPhone": "Actualización de cuenta",
+    "bonus": "Obtenga un bono de +{0}"
   },
   "copy_success": "Copia exitosa",
   "user": {
@@ -187,7 +196,7 @@
       "create_date": "Fecha de creación",
       "time": "Hora",
       "order": "Número de solicitud",
-      "content": "Pagar la cuerda",
+      "content": "Pagar el codigo",
       "value": "Valor",
       "copyButton": "Copiar código pix",
       "lookButton": "Ya pagado",
@@ -225,7 +234,13 @@
     "back": "Atrás",
     "msg_title": "Salir del juego",
     "msg_content": "¿Estás seguro de que deseas salir?",
-    "cancel": "Cancelar"
+    "cancel": "Cancelar",
+    "brand_by": "Por",
+    "tips": {
+      "movie_mode": "Modo película",
+      "full_screen": "Pantalla completa"
+    },
+    "share_title": "Comparte este juego"
   },
   "msgbox": {
     "title": "Mensaje",
@@ -333,6 +348,20 @@
       "processing": "Procesando",
       "detail_title": "Detalles del Registro"
     },
+    "tip": {
+      "success": "Enviado con éxito, se está revisando",
+      "total_not_enough": "Cantidad a retirar insuficiente",
+      "under_can_score": "Cantidad de retirada mínima {0}",
+      "over_can_score": "Cantidad de retirada máxima {0}",
+      "exceed_count_limit": "Solo puedes solicitar {0} veces al día",
+      "user_score_not_enough": "Fichas insuficientes",
+      "user_in_game": "Lo siento, te encuentras en una partida",
+      "need_recharge": "La cantidad de retirada es demasiado grande, debes recargar primero",
+      "btn_to_recharge": "Recargar",
+      "unbound_account": "No hay cuenta bancaria vinculada, no puedes retirar",
+      "account_abnormal": "El sistema detectó anomalías en tu cuenta, por lo que se te ha prohibido la retirada, contacta con atención al cliente",
+      "score_null": "No introdujiste la cantidad a retirar"
+    },
     "help": {
       "title": "Reglas de Retiro",
       "rule_1": "1. El monto mínimo/máximo que puedes retirar cada vez es {0}/{1};",
@@ -566,6 +595,17 @@
     "reconnect": "Reconectar",
     "disconnected": "La red se ha desconectado. ¿Quieres volver a conectarte?"
   },
+  "installBox": {
+    "tips": "Nos encantaría añadir 24680.com a tu pantalla de inicio para las últimas apuestas gratuitas y actualizaciones de bonos.",
+    "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 {0} fichas"
+  },
   "web": {
     "activity": {
       "redpack_not_available": "No hay RedPack",
@@ -626,7 +666,14 @@
       "score_fail": "La cantidad debe ser un valor entero.",
       "params_error": "error de parámetros",
       "pix_type_missing": "Falta el tipo de PIX",
-      "cpf_number_used": "El número de CPF ha sido utilizado varias veces y no se puede guardar"
+      "cpf_number_used": "El número de CPF ha sido utilizado varias veces y no se puede guardar",
+      "no_withdraw_value": "Monto de retiro insuficiente",
+      "too_low": "Inferior al monto mínimo de retiro",
+      "too_high": "Superior al monto máximo de retiro",
+      "day_max": "Límite diario de retiro excedido",
+      "no_enouth_score": "No hay suficiente dinero disponible",
+      "in_game": "Abierto en el juego",
+      "no_deposit": "Necesitas depositar para retirar"
     },
     "payment": {
       "paytype_error": "Error de tipo de pago",
@@ -640,6 +687,12 @@
       "recharge_required": "Se requiere recarga para obtener el bono diario",
       "unknown_error": "Error desconocido",
       "exception_occurred": "Ocurrió un error al procesar su solicitud"
+    },
+    "verify": {
+      "num_empty": "El número de teléfono está vacío",
+      "code_too_long": "El código de teléfono es demasiado largo",
+      "already_bound": "El número de teléfono ya está vinculado",
+      "code_incorrect_or_expired": "El código es incorrecto o ha expirado"
     }
   }
 }

+ 60 - 7
src/locales/languages/pt_BR.json

@@ -89,13 +89,13 @@
   "login": {
     "login_title": "Entrar",
     "phone_ph": "Número de Telefone",
-    "login_pwd": "Senha de Login",
+    "login_pwd": "Senha",
     "forgot": "Esqueceu sua senha?",
     "sign_in_btn": "Entrar",
     "reg_new_des": "Novo no 24680?",
     "reg_new_btn": "Criar Conta",
     "reg_title": "Registrar",
-    "re_pwd": "Confirmar senha de login",
+    "re_pwd": "Confirmar Senha",
     "sign_up_btn": "Registrar",
     "login_des": "Já tem uma conta?",
     "account": {
@@ -105,7 +105,14 @@
       "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": "SMS"
+    },
+    "otp_ph": "Código de verificação",
+    "guest_login": "Login como convidado",
+    "btn_otp": "Verificar"
   },
   "logout": {
     "success": "Você saiu com sucesso."
@@ -141,7 +148,9 @@
     "email": "E-mail",
     "security": "Segurança",
     "install_banner": "Mostrar Aviso de Instalação",
-    "sound": "Som"
+    "sound": "Som",
+    "bindPhone": "Atualização de conta",
+    "bonus": "Ganhe Bônus +{0}"
   },
   "copy_success": "Cópia bem-sucedida",
   "user": {
@@ -225,7 +234,13 @@
     "back": "Voltar",
     "msg_title": "Sair do jogo",
     "msg_content": "Você tem certeza que deseja sair?",
-    "cancel": "Cancelar"
+    "cancel": "Cancelar",
+    "brand_by": "Por",
+    "tips": {
+      "movie_mode": "Modo Filme",
+      "full_screen": "Tela cheia"
+    },
+    "share_title": "Compartilhe este jogo"
   },
   "msgbox": {
     "title": "Prompt",
@@ -247,7 +262,7 @@
       "btn_account": "+ Conta bancária",
       "btn_account_change": "Mudar de conta",
       "btn_withdraw": "Saque",
-      "tip": "Dicas: O tempo previsto de recebimento é de 12 horas, consulte o banco.\nO valor mínimo/máximo que você pode sacar toda vez é {1}/{2}",
+      "tip": "Dicas: O tempo previsto de recebimento é de 12 horas, consulte o banco.\nO valor mínimo/máximo que você pode sacar toda vez é {0}/{1}",
       "unbound_account": "Você não vinculou uma conta bancária e não pode retirar",
       "account_abnormal": "O sistema detectou uma operação anormal em sua conta, e sua retirada foi proibida. Entre em contato com o atendimento ao cliente",
       "score_null": "Você não preencheu o valor de retirada"
@@ -333,6 +348,20 @@
       "processing": "Processando",
       "detail_title": "Detalhes do Registro"
     },
+    "tip": {
+      "success": "O envio foi bem-sucedido e o sistema está em revisão",
+      "total_not_enough": "O valor a ser sacado não é suficiente",
+      "under_can_score": "A quantidade de retirada única não pode ser inferior a {0}",
+      "over_can_score": "A quantidade de retirada única não pode ser superior a {0}",
+      "exceed_count_limit": "apenas {0} solicitações de saque podem ser iniciadas por dia",
+      "user_score_not_enough": "Seu Ficha não é suficiente",
+      "user_in_game": "Desculpe,você está no jogo por enquanto",
+      "need_recharge": "O valor da sua solicitação de saque é muito alto, você deve completar antes de continuar a sacar",
+      "btn_to_recharge": "Para recarregar",
+      "unbound_account": "Você não vinculou uma conta bancária e não pode sacar",
+      "account_abnormal": "O sistema detectou uma operação anormal em sua conta,e sua retirada foi banida. Entre em contato com o atendimento ao cliente",
+      "score_null": "Você não preencheu o valor da retirada"
+    },
     "help": {
       "title": "Regras de Retirada",
       "rule_1": "1. O valor mínimo/máximo que você pode retirar a cada vez é {0}/ {1};",
@@ -566,6 +595,17 @@
     "reconnect": "Reconectar",
     "disconnected": "A rede foi desconectada. Deseja reconectar?"
   },
+  "installBox": {
+    "tips": "Adoraríamos adicionar 24680.com à sua tela inicial para as últimas apostas gratuitas e atualizações de bônus.",
+    "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 {0} fichas de recompensa"
+  },
   "web": {
     "activity": {
       "redpack_not_available": "Não há RedPack",
@@ -626,7 +666,14 @@
       "score_fail": "O valor deve ser um valor inteiro.",
       "params_error": "erro nos parâmetros",
       "pix_type_missing": "O tipo de PIX está ausente",
-      "cpf_number_used": "O número do CPF foi usado várias vezes e não pode ser salvo"
+      "cpf_number_used": "O número do CPF foi usado várias vezes e não pode ser salvo",
+      "no_withdraw_value": "Montante de retirada insuficiente",
+      "too_low": "Abaixo do valor mínimo de retirada",
+      "too_high": "Acima do valor máximo de retirada",
+      "day_max": "Limite diário de retirada excedido",
+      "no_enouth_score": "Dinheiro insuficiente em mãos",
+      "in_game": "Aberto no jogo",
+      "no_deposit": "Você precisa depositar para retirar"
     },
     "payment": {
       "paytype_error": "Erro de tipo de pagamento",
@@ -640,6 +687,12 @@
       "recharge_required": "É necessário recarregar para receber o bônus diário",
       "unknown_error": "Erro desconhecido",
       "exception_occurred": "Ocorreu um erro ao processar sua solicitação"
+    },
+    "verify": {
+      "num_empty": "Número de telefone está vazio",
+      "code_too_long": "O código do telefone é muito longo",
+      "already_bound": "O número de telefone já está vinculado",
+      "code_incorrect_or_expired": "O código está incorreto ou expirou"
     }
   }
 }

+ 181 - 128
src/locales/languages/ru_RU.json

@@ -2,54 +2,54 @@
   "links": {
     "home": "Главная",
     "menu_title": {
-      "agent": "Агент",
-      "cashwheel": "Колесо Денег",
-      "rain": "Денежный Дождь"
+      "agent": "Реферальная программа",
+      "cashwheel": "Колесо фортуны",
+      "rain": "Денежный бонус"
     },
     "route_title": {
-      "activity": "Все мероприятия",
-      "cs": "Обслуживание клиентов",
-      "withdraw_record": "Запись вывода",
+      "activity": "Все активности",
+      "cs": "Клиентские сервисы",
+      "withdraw_record": "История вывода",
       "withdraw_pass": "Пароль для вывода",
       "account_info": "Информация об аккаунте",
-      "message_detail": "Детали сообщения",
+      "message_detail": "Детали",
       "security": "Безопасность",
       "game": "Игра",
       "payorder": "Платежное поручение",
       "msg": "Сообщение",
-      "agent_policy": "Агентская политика",
-      "cashwheel": "Получите 100 R$ бесплатно",
-      "check_in": "БОНУС В ДЕНЬ"
+      "agent_policy": "Политика реферальной программы",
+      "cashwheel": "Получите 100 R$ БЕСПЛАТНО",
+      "check_in": "ЕЖЕДНЕВНЫЙ БОНУС"
     },
     "casino": "Казино",
     "slots": "Слоты",
-    "hot_games": "Горячие игры",
-    "live_casino": "Живое Казино",
-    "favorites": "Избранное",
-    "recent": "Недавнее",
-    "new_release": "Новые Релизы",
+    "hot_games": "Наиболее популярные",
+    "live_casino": "Лайв казино",
+    "favorites": "Избранные",
+    "recent": "Недавние",
+    "new_release": "Новинки",
     "bingo": "Бинго",
     "blackjack": "Блэкджек",
     "table_games": "Настольные игры",
     "sports": "Спорт",
     "racing": "Гонки",
-    "fighting": "Борьба",
+    "fighting": "Бои",
     "live_events": "Прямые трансляции",
     "basketball": "Баскетбол",
-    "fifa": "ФИФА",
+    "fifa": "Футбол",
     "bonus": "БОНУС",
-    "live_support": "Поддержка в реальном времени",
+    "live_support": "Служба поддержки",
     "quest": "КВЕСТ",
     "spin": "СПИН",
-    "log_out": "Выйти",
-    "check_in": "РЕГИСТРИРОВАТЬСЯ"
+    "log_out": "Выйти из аккаунта",
+    "check_in": "ЕЖЕДНЕВНЫЙ БОНУС"
   },
   "tab": {
     "withdraw": "Вывод",
     "deposit": "Депозит",
-    "promotion": "Акция",
-    "message": "Сообщение",
-    "service": "Сервис"
+    "promotion": "Акции",
+    "message": "Сообщения",
+    "service": "Обслуживание"
   },
   "refer": {
     "title": "Карнавал в Рио 2024",
@@ -62,13 +62,13 @@
   "sign_in": "Войти",
   "sign_up": "Регистрация",
   "footer": {
-    "game_res": "Ответственность за игру",
+    "game_res": "Безопасность и условия работы сайта",
     "privacy_policy": "Политика конфиденциальности",
-    "terms_of_service": "Условия обслуживания",
+    "terms_of_service": "Пользовательское соглашение",
     "kyc_policy": "Политика AML & KYC"
   },
   "color_card": {
-    "go_to": "Перейти к"
+    "go_to": "Перейти"
   },
   "win_list": {
     "big_wins": "Недавние крупные выигрыши",
@@ -79,7 +79,7 @@
     "view_all": "Посмотреть все"
   },
   "game_list": {
-    "show_more": "Загрузить больше",
+    "show_more": "Показать еще",
     "no_more": "Больше нет"
   },
   "game_search": {
@@ -92,7 +92,7 @@
     "login_pwd": "Пароль",
     "forgot": "Забыли пароль?",
     "sign_in_btn": "Войти",
-    "reg_new_des": "Новый на 24680?",
+    "reg_new_des": "Новый пользователь?",
     "reg_new_btn": "Создать аккаунт",
     "reg_title": "Регистрация",
     "re_pwd": "Подтвердите пароль",
@@ -104,11 +104,18 @@
     "password": {
       "empty": "Пожалуйста, введите пароль!"
     },
-    "success": "Успешный вход, добро пожаловать обратно!",
-    "notfound": "Ваш аккаунт не найден, пожалуйста, зарегистрируйтесь или попробуйте снова!"
+    "success": "Успешный вход, добро пожаловать!",
+    "notfound": "Ваш аккаунт не найден, пожалуйста, зарегистрируйтесь или попробуйте снова!",
+    "login_type": {
+      "password": "Пароль",
+      "otp": "SMS"
+    },
+    "otp_ph": "Проверочный код",
+    "guest_login": "Гостевой вход",
+    "btn_otp": "Отправить"
   },
   "logout": {
-    "success": "Вы успешно вышли."
+    "success": "Вход успешно выполнен."
   },
   "reg": {
     "account": {
@@ -141,9 +148,11 @@
     "email": "Электронная почта",
     "security": "Безопасность",
     "install_banner": "Показать уведомление об установке",
+    "bindPhone": "Обновление учетной записи",
+    "bonus": "Получите бонус +{0}",
     "sound": "Звук"
   },
-  "copy_success": "Копирование успешно",
+  "copy_success": "Успешно скопировано",
   "user": {
     "title_avatar": "Аватар",
     "title_username": "Имя пользователя",
@@ -159,10 +168,10 @@
     "submit_btn": "Изменить",
     "password": {
       "title": "Сброс пароля",
-      "oldpassword": "Текущий пароль",
+      "oldpassword": "Старый пароль",
       "newpassword": "Новый пароль",
       "renewpassword": "Подтвердите новый пароль",
-      "fail": "Неправильный текущий пароль, пожалуйста, введите снова.",
+      "fail": "Неверный старый пароль, пожалуйста, введите снова.",
       "modi_success": "Пароль успешно сброшен!",
       "notsame": "Пароли не совпадают, пожалуйста, введите снова!"
     },
@@ -187,14 +196,14 @@
       "create_date": "Дата создания",
       "time": "Время",
       "order": "Номер заказа",
-      "content": "Оплатить веревку",
+      "content": "Оплатите по коду",
       "value": "Значение",
       "copyButton": "Скопировать код pix",
       "lookButton": "Уже оплачен",
-      "tips1": "Копирование успешно, пожалуйста, откройте pix",
-      "tips2": "Спасибо за ваш заказ",
+      "tips1": "Успешно скопировано, пожалуйста, перейдите в Pix",
+      "tips2": "Спасибо за ваш запрос",
       "okey": "ОК",
-      "success": "Успешное пополнение"
+      "success": "Баланс успешно пополнен"
     },
     "promotions": {
       "tips": "Участвовать в акциях",
@@ -225,6 +234,12 @@
     "back": "Назад",
     "msg_title": "Покинуть игру",
     "msg_content": "Вы уверены, что хотите выйти?",
+    "brand_by": "Источник",
+    "tips": {
+      "movie_mode": "Режим фильма",
+      "full_screen": "Полноэкранный"
+    },
+    "share_title": "Поделиться этой игрой",
     "cancel": "Отмена"
   },
   "msgbox": {
@@ -234,28 +249,28 @@
   },
   "withdraw": {
     "main": {
-      "total_balance": "Мои общие фишки",
-      "withdrawable": "Доступные фишки",
-      "editBox": "Фишки",
+      "total_balance": "Мой баланс",
+      "withdrawable": "Доступно для вывода",
+      "editBox": "Вывести",
       "editPlaceholder": "Введите сумму для вывода",
       "btn_reset": "Удалить",
-      "btn_all": "ВСЕ",
+      "btn_all": "Вывести все",
       "bank_info_isNull": "Нет привязанных банковских данных",
       "bank_No_isNull": "Нет банковского счета",
       "via": "Вывод через",
-      "account": "Мой аккаунт",
+      "account": "Мой счет",
       "btn_account": "+ Банковский счет",
       "btn_account_change": "Изменить счет",
       "btn_withdraw": "Вывести",
-      "tip": "Советы: Ожидаемое время получения до 12 часов, обратитесь к банку. Минимальная/максимальная сумма, которую вы можете вывести каждый раз, составляет {0}/{1}",
-      "unbound_account": "Вы не привязали банковский счет и не можете вывести",
-      "account_abnormal": "Система обнаружила аномальную операцию на вашем аккаунте, и ваш вывод был запрещен. Пожалуйста, свяжитесь с службой поддержки",
+      "tip": "Советы: Ожидаемое время получения вывода составляет до 12 часов в зависимости от условий банка. Минимальная/максимальная сумма, которую вы можете вывести каждый раз, составляет {0}/{1}",
+      "unbound_account": "Вы не привязали банковский счет и не можете осуществить вывод средств",
+      "account_abnormal": "Система обнаружила подозрительную операцию на вашем аккаунте, и ваш вывод был остановлен. Пожалуйста, свяжитесь со службой поддержки",
       "score_null": "Вы не указали сумму вывода"
     },
     "account": {
       "title": "Информация об аккаунте",
       "btn_save": "Сохранить и активировать",
-      "help": "Убедитесь, что номер вашего аккаунта правильный, мы не несем ответственности за ошибки, вызванные неправильным вводом",
+      "help": "Убедитесь, что данные вашего аккаунта введены правильно. Мы не несем ответственности за последствия, в случае ввода некорректных данных",
       "userName": "Имя",
       "name_holder": "Введите имя",
       "phone": "Телефон[+55]",
@@ -278,10 +293,10 @@
         "select_null": "пожалуйста, выберите PIX DICT",
         "account_error": "Неправильный формат банковской карты",
         "account_null": "Банковская карта не заполнена",
-        "name_error": "Неправильная информация о имени пользователя",
+        "name_error": "Неправильно указано имя пользователя",
         "name_null": "Имя пользователя не заполнено",
-        "bank_error": "Неправильная информация о банке",
-        "bank_null": "Название банка не заполнено",
+        "bank_error": "Неправильно указана информация о банке",
+        "bank_null": "Наименование банка не заполнено",
         "phone_error": "Неправильный формат номера телефона",
         "phone_null": "Номер телефона не заполнен",
         "IFSC_error": "Неправильный код IFSC",
@@ -297,13 +312,13 @@
         "success": "Банковский счет PIX сохранен и активирован",
         "success2": "Банковский счет TED сохранен и активирован",
         "error": "Не удалось сохранить информацию о банковском счете",
-        "bind_too_more": "Применение не удалось\nВаш аккаунт может использоваться несколькими пользователями, проверьте безопасность вашего банковского аккаунта.",
+        "bind_too_more": "Применение не удалось\\Ваш аккаунт может использоваться несколькими пользователями, проверьте безопасность вашего банковского аккаунта.",
         "is_null": "не заполнен",
         "is_error": "неправильный формат"
       }
     },
     "pass": {
-      "prompt": "Для безопасности ваших средств, пожалуйста, сначала установите пароль для фондов",
+      "prompt": "Пожалуйста, сначала установите пароль для безопасности ваших средств",
       "btn_confirm": "Подтвердить",
       "edit_title": "Установить пароль для вывода",
       "old_pass": "Старый пароль для вывода",
@@ -312,7 +327,7 @@
       "btn_submit": "Вывести сейчас",
       "tips": {
         "success": "Успешно настроено",
-        "des": "Новый пароль установлен правильно, запомните свой пароль",
+        "des": "Новый пароль сохранен, запомните его",
         "btn_back": "Вернуться на главную страницу",
         "btn_withdraw": "Вывести сейчас",
         "err_not_same": "Пароли не совпадают, пожалуйста, введите снова."
@@ -333,62 +348,76 @@
       "processing": "Обработка",
       "detail_title": "Детали записи"
     },
+    "tip": {
+      "success": "Запрос был успешно принят. Система находится в процессе обработки",
+      "total_not_enough": "Недостаточная сумма для вывода",
+      "under_can_score": "Сумма вывода не может быть меньше {0}",
+      "over_can_score": "Сумма вывода не может быть больше {0}",
+      "exceed_count_limit": "Вы можете оформить {0} запросов на вывод в день",
+      "user_score_not_enough": "Недостаточный баланс",
+      "user_in_game": "Извините, вы находитесь в игре",
+      "need_recharge": "Сумма вашего запроса на вывод слишком велика, вы должны пополнить баланс, прежде чем продолжить вывод",
+      "btn_to_recharge": "Пополнить баланс",
+      "unbound_account": "Вы не привязали банковский счет для вывода",
+      "account_abnormal": "Система обнаружила подозрительную активность на вашем аккаунте и остановила вывод. Пожалуйста, свяжитесь со службой поддержки",
+      "score_null": "Вы не заполнили сумму для вывода"
+    },
     "help": {
       "title": "Правила вывода",
       "rule_1": "1. Минимальная/максимальная сумма, которую вы можете вывести каждый раз, составляет {0}/{1};",
       "rule_2": "2. Вы можете вывести сумму в зависимости от общего количества проигранных и выигранных средств;",
-      "rule_3": "3. Максимальное количество заявок на вывод в день: {0} раз;",
-      "rule_4": "4. Мы обрабатываем запросы на вывод с 9:00 до 18:00, с понедельника по пятницу, кроме праздничных дней;",
-      "rule_5": "5. Вы, вероятно, получите сумму в течение 2-12 часов;",
-      "rule_6": "6. Несостоявшаяся сумма вывода будет возвращена напрямую, вы можете проверить причину в почтовом ящике;",
-      "rule_tips": "Примечание: {0}% от суммы вывода будет удержано в качестве комиссии платежного шлюза;"
+      "rule_3": "3. Максимальное количество заявок на вывод в день: {0} ;",
+      "rule_4": "4. Мы обрабатываем запросы на вывод средств с 9:00 до 18:00, с понедельника по пятницу, кроме праздничных дней;",
+      "rule_5": "5. Время получения вывода составляет от 2 до 12 часов;",
+      "rule_6": "6. Сумма отмененного вывода будет возвращена на баланс аккаунта, сообщение с причиной отмены будет отправлено на почтовый ящик;",
+      "rule_tips": "Примечание: {0}% от суммы вывода будет удержано в качестве комиссии платежной системы;"
     }
   },
   "customer": {
     "TitleLabel": "Служба поддержки",
-    "leavemsg_notice": "Опишите ваши вопросы и комментарии. (до 300 символов)",
-    "TipLabel": "Если у вас возникли проблемы в игре, пожалуйста, свяжитесь со службой поддержки ниже.",
-    "Input_tip_Label": "Или вы можете оставить нам сообщение.",
+    "leavemsg_notice": "Опишите ваш вопрос (до 300 символов)",
+    "TipLabel": "Если у вас возникли проблемы в игре, пожалуйста, свяжитесь со службой поддержки любым из предложенных ниже способов",
+    "Input_tip_Label": "Или оставьте сообщение",
     "Btn_send": "Отправить",
     "Item_customer_Label": "Связаться напрямую",
-    "input_holder": "Пожалуйста, опишите ваши вопросы и комментарии. (до 300 символов)",
+    "input_holder": "Пожалуйста, опишите ваш вопрос (до 300 символов)",
     "TopTip_succeed": "Сообщение успешно отправлено!",
     "TopTip_fail": "Не удалось отправить сообщение!",
-    "TopTip_chatIsNull": "Содержание не может быть пустым!",
-    "TopTip_timerFail": "Слишком много сообщений, попробуйте позже!",
-    "WhatsApp_tip": "Если у вас есть вопросы, вы можете нажать на WhatsApp для консультации",
+    "TopTip_chatIsNull": "Необходимо добавить описание!",
+    "TopTip_timerFail": "Слишком много сообщений, попробуйте отправить сообщение позже или воспользуйтесь другим каналом для связи!",
+    "WhatsApp_tip": "Если у вас есть вопросы, вы можете нажать на кнопку WhatsApp для консультации",
     "WhatsApp_btn_WhastApp": "Перейти в WhatsApp",
     "copy": "Копировать",
     "btn_telegram": "Перейти в Telegram",
-    "whats_label": "Или скопируйте номер в свой WhatsApp",
+    "whats_label": "Или скопируйте номер телефона в WhatsApp",
     "email_label": "Вы также можете связаться с нами по электронной почте",
     "write_now": "Написать сейчас"
   },
   "form_errors": {
-    "phone_55": "Введите правильный 11-значный номер телефона.",
+    "phone_55": "Введите корректный 11-значный номер телефона.",
     "pass": "Пароль должен содержать не менее 6 символов"
   },
   "turnplate": {
     "expires_in": "Истекает через",
-    "invite_tips": "Пригласите друзей для помощи с выводами",
+    "invite_tips": "Приглашайте друзей и получайте средства на свой баланс",
     "cash_btn": "ВЫВЕСТИ",
     "need_tip": "Вам еще нужно",
-    "need_tip_1": "для вывода",
+    "need_tip_1": "для оформления вывода",
     "record": {
       "tab_0": "Отчет",
-      "tab_1": "Моя ссылка"
+      "tab_1": "Ссылка для регистрации друга"
     },
     "rule": {
       "title": "Правила",
-      "rule_1": "1. Вы можете начать вывод денег, когда накопленная сумма достигнет 100BRL (максимальная сумма вознаграждения составляет 1.000BRL, нет ограничений по количеству раз).",
-      "rule_2": "2. Приглашение друзей для регистрации на 24680.com поможет вам быстро вывести деньги.",
-      "rule_3": "3. Каждый пользователь получает один бесплатный спин в день.",
-      "rule_4": "4. Активность каждого пользователя действительна в течение 3 дней и может участвовать в активности несколько раз.",
-      "rule_5": "5. Если вы получите дополнительный бонус, вы можете играть бесплатно и выиграть больше реальных денег.",
+      "rule_1": "1. Для вывода средств необходимо, чтобы баланс составлял не менее 100BRL (максимальная сумма бонуса с одного Спина может составить до 1.000BRL).",
+      "rule_2": "2. Регистрируйте друзей по вашей ссылке на 24680.com, чтобы получить бонус и быстрее вывести средства",
+      "rule_3": "3. Каждый пользователь получает один бесплатный Спин в день.",
+      "rule_4": "4. Если вы получите бонусное задание, то оно будет активно в течение 3 дней. Каждый пользователь может принимать участие несколько раз",
+      "rule_5": "5. Если вы получите бонусные R$, вы сможете играть бесплатно и выиграть больше реальных денег.",
       "rule_6": "6. Если вывод не будет сделан в течение 3 дней, средства будут потеряны."
     },
     "info_box": {
-      "title": "Скоро вы сможете вывести",
+      "title": "Вывод скоро будет доступен",
       "tips": "Общая сумма к оплате",
       "step_title": "Способы оплаты",
       "step_0": "Запрос на оплату отправлен",
@@ -415,7 +444,7 @@
     "pop_info": {
       "title": "Получите R${0} бесплатно",
       "title_1": "БЫСТРЫЙ ВЫВОД",
-      "tips": "Поздравляем с удачей, вы выиграли",
+      "tips": "Поздравляем, вы выиграли!",
       "btn_invite": "Получите больше для вывода"
     },
     "retain_pop": {
@@ -434,16 +463,16 @@
       "titleTheme": "Тема:",
       "titleTime": "Время:"
     },
-    "tips":"Упс, в данный момент нет новых писем!!",
-    "TopTip_qxlqyxjl": "Пожалуйста, сначала получите награду за письмо",
-    "TopTip_jscsyydyj": "Все прочитанные письма с полученными наградами за вложения будут удалены"
+    "tips":"Упс, в данный момент нет новых писем!",
+    "TopTip_qxlqyxjl": "Пожалуйста, сначала заберите награду из письма",
+    "TopTip_jscsyydyj": "Все прочитанные письма с полученными наградами будут удалены"
   },
   "security": {
     "title": "Настройки безопасности",
     "login_pass_title": "Изменить пароль для входа",
-    "login_pass_des": "Регулярно меняйте пароль для входа, чтобы сохранить его уникальным и безопасным.",
+    "login_pass_des": "Регулярно меняйте пароль для входа, чтобы повысить уровень безопасности аккаунта.",
     "withdraw_pass_title": "Изменить пароль для вывода",
-    "withdraw_pass_des": "Регулярно меняйте пароль для вывода, чтобы сохранить его уникальным и безопасным."
+    "withdraw_pass_des": "Регулярно меняйте пароль для вывода, чтобы повысить уровень безопасности аккаунта."
   },
   "gameRoom": {
     "slect_title": "Выбор комнаты",
@@ -457,7 +486,7 @@
   },
   "moneyRain": {
     "rule": {
-      "title": "Деньги как дождь",
+      "title": "Деньги льются рекой",
       "reward_tip_1": "за раз",
       "reward_tip_2": "Максимальная выплата"
     },
@@ -465,63 +494,63 @@
       "title": "{0}X в день"
     },
     "des": {
-      "tips_0": "Каждое сессия дождя из денег раздается бесплатно с {0}",
-      "tips_1": "Максимальная сумма денег: Каждая сессия дождя из денег раздается бесплатно с",
-      "tips_2": "Пополняемые участники могут получить бесплатно",
-      "tips_3": "Полученные деньги можно использовать для игры или напрямую вывести."
+      "tips_0": "Каждую сессию разыгрывается денежный фонд в размере  {0}",
+      "tips_1": "Каждый участник может забрать часть фонда абсолютно бесплатно",
+      "tips_2": "Для того, чтобы принять участние, необходимо вносить депозиты на счёт.",
+      "tips_3": "Выигрыш можно расходовать в играх или вывести."
     },
     "redPack": {
-      "title": "Дождь из денег",
-      "tips_0": "Пополняемые участники могут получить бесплатно.",
-      "tips_1": "Максимальная сумма выпадения денег: {0}",
+      "title": "Деньги льются рекой",
+      "tips_0": "Участники, вносившие депозит, могут принять участие и имеют шанс забрать бесплатный кэш",
+      "tips_1": "Максимальная сумма выигрыша: {0}",
       "btn": "ЗАБРАТЬ"
     },
     "redOpen": {
       "time_tips": "График сбора:",
       "max_drop": "Максимальная выплата ",
-      "already_collect": "Уже собрано"
+      "already_collect": "Уже разыграно"
     }
   },
   "agentPolicy": {
-    "step_des_1": "Участники, зарегистрировавшиеся по вашей реферальной ссылке, станут вашими прямыми агентами.",
-    "step_des_2": "Участники, зарегистрировавшиеся по реферальной ссылке любого подчиненного, станут членами вашей команды, и весь доход от игры будет учитываться в вашей производительности.",
-    "step_des_3": "Согласно таблице комиссий, ваш доход рассчитывается вычитанием комиссии прямых агентов. Чем выше уровень, тем выше процент комиссии.",
+    "step_des_1": "Участники, зарегистрировавшиеся по вашей реферальной ссылке, станут вашими прямыми рефералами.",
+    "step_des_2": "Участники, зарегистрировавшиеся по реферальной ссылке любого из ваших рефералов, станут вашими рефералами второго и третьего уровней и будут также учитываться в ваших реферальных начислениях.",
+    "step_des_3": "Согласно таблице комиссий, ваш доход рассчитывается, исходя из общей суммы депозитов ваших рефералов всех уровней за вычетом дохода ваших рефералов второго и третьего уровней. Чем больше общая сумма депозитов ваших рефералов, тем выше процент комиссии.",
     "example": "Пример",
     "team_info": {
-      "title": "Общий объем команды как производительность A",
-      "des_1": "Сегодняшняя производительность от A\n200,000 Уровень 12",
-      "des_2": "Сегодняшняя производительность от B\n50,000 Уровень 10",
-      "des_3": "Сегодняшняя производительность от C\n20,000 Уровень 9",
-      "title_1": "Сегодняшний доход от A = 10,700"
+      "title": "Формула расчета дохода А:",
+      "des_1": "Общая сумма депозитов рефералов A\n200,000 (Уровень 12)",
+      "des_2": "Общая сумма депозитов рефералов B\n50,000 (Уровень 10)",
+      "des_3": "Общая сумма депозитов рефералов C\n20,000 (Уровень 9)",
+      "title_1": "Текущий доход A = 10,700"
     },
     "levels": {
       "title": "Таблица комиссий",
       "label_1": "Уровень",
-      "label_2": "Производительность",
+      "label_2": "Сумма депозитов",
       "label_3": "Комиссия"
     }
   },
   "agent": {
-    "no_data": "Пока нет записей",
+    "no_data": "Записи отсутствуют",
     "get_success": "Успешно получено",
-    "get_error": "Не удалось собрать",
+    "get_error": "Получение не удалось",
     "base": {
       "title_link": "Ссылка на справочник",
       "btn_copy": "Копировать",
       "value": "Значение",
-      "title_policy": "Политика агента",
+      "title_policy": "Политика реферальной программы",
       "details": "Подробности"
     },
     "today": {
       "label_level": "Уровень",
-      "label_team_size": "Размер команды",
-      "label_direct_agent": "Прямой агент",
+      "label_team_size": "Количество рефералов",
+      "label_direct_agent": "Прямые рефералы",
       "label_revenue": "Доход за сегодня",
       "label_commission": "Комиссия за сегодня"
     },
     "subs": {
-      "title": "Производительность прямого агента сегодня",
-      "label_name": "Имя агента",
+      "title": "Депозиты прямых рефералов",
+      "label_name": "Имя реферала",
       "label_revenue": "Доход",
       "label_commision": "Комиссия"
     },
@@ -540,7 +569,7 @@
     }
   },
   "CheckIn": {
-    "only_btn": "только нужен $",
+    "only_btn": "Только $",
     "take": "Взять",
     "getMore": "Получить больше фишек",
     "nota": "Вы не",
@@ -553,18 +582,29 @@
     "day6": "ДЕНЬ 6",
     "day7": "ДЕНЬ 7",
     "req_info_error": "Не удалось получить информацию о сервисе",
-    "isChecked": "Вы уже запросили свой бонус сегодня",
+    "isChecked": "Сегодняшний бонус уже получен",
     "isEnd": "Активность завершена",
     "req_data_error": "Ошибка чтения данных",
     "must_recharge": "Вы должны пополнить счет, прежде чем продолжить получение бонусов",
     "btn_to_recharge": "Для пополнения",
     "last_get": "Следующий бонус в:",
-    "get_success": "Получено успешно"
+    "get_success": "Успешно получено"
   },
   "hall": {
-    "offline": "Вы уже были онлайн в другом месте и были вынуждены отключиться здесь.",
+    "offline": "Сеанс завершен, так как вы уже были авторизованы на другом устройстве.",
     "reconnect": "переподключиться",
-    "disconnected": "Сеть отключена. Хотите восстановить соединение?"
+    "disconnected": "Соединение прервано. Хотите восстановить?"
+  },
+  "installBox": {
+    "tips": "Мы бы хотели добавить 24680.com на ваш рабочий стол для последних бесплатных ставок и обновлений бонусов.",
+    "btn_cancel": "Отмена",
+    "btn_install": "Установить"
+  },
+  "bindPhone": {
+    "title_link_phone": "Повышение учетной записи",
+    "phone_award": "Награда:",
+    "btn_submit": "ПОДТВЕРДИТЬ",
+    "tips": "Свяжите свой мобильный телефон и получите награду - {0} фишек"
   },
   "web": {
     "activity": {
@@ -575,39 +615,39 @@
       "wrong_act": "Неверная строка действия."
     },
     "agent": {
-      "fail_wrong_id": "Неправильный ID записей",
+      "fail_wrong_id": "Неправильный ID записи",
       "fail_wrong_status": "Уже получено"
     },
     "turnplate": {
-      "toofast": "Запросы слишком часты",
+      "toofast": "Количество запросов превышено",
       "invalid": "Неверное состояние",
-      "expired": "Активность истекла",
+      "expired": "Время активности истекло",
       "notimes": "Нет времени",
-      "to_many_request": "Слишком много запросов",
-      "already_help": "Уже помог"
+      "to_many_request": "Количество запросов превышено",
+      "already_help": "Уже помогло"
     },
     "user": {
-      "password_fail": "Оригинальный пароль неверен, пожалуйста, введите его снова.",
-      "paypass_fail": "Оригинальный пароль неверен, пожалуйста, введите его снова.",
+      "password_fail": "Некорректный пароль, пожалуйста, введите его снова.",
+      "paypass_fail": "Некорректный пароль, пожалуйста, введите его снова.",
       "phone_fail": "Пожалуйста, проверьте правильность введенного номера телефона.",
       "email_fail": "Пожалуйста, проверьте правильность введенного адреса электронной почты."
     },
     "modiUserInfo": {
       "fail_NickName": "Неверное имя пользователя.",
       "fail_FaceID": "Неверный FaceID.",
-      "fail_Gender": "Пол должен быть женским, мужским или неизвестным.",
-      "fail_DefaultLanguage": "Язык по умолчанию должен быть допустимой строкой.",
+      "fail_Gender": "Пол должен быть женским, мужским или другим.",
+      "fail_DefaultLanguage": "Не выбран язык по-умолчанию.",
       "fail_ThemeColor": "Цвет темы должен быть темным или светлым!"
     },
     "reg": {
-      "password_notsame": "Пароли, введенные дважды, не совпадают, пожалуйста, введите снова!",
-      "paypass_notsame": "Пароли, введенные дважды, не совпадают, пожалуйста, введите снова!",
+      "password_notsame": "Введенные пароли не совпадают, пожалуйста, введите снова!",
+      "paypass_notsame": "Введенные пароли не совпадают, пожалуйста, введите снова!",
       "account_empty": "Пожалуйста, введите имя пользователя!",
       "fail_email_exist": "Электронная почта уже существует, пожалуйста, измените её и попробуйте зарегистрироваться снова!",
       "fail_phone_exist": "Номер телефона уже существует, пожалуйста, измените его и попробуйте зарегистрироваться снова!",
       "fail_suspend": "Регистрация приостановлена",
       "fail_stoplogin": "Вход приостановлен",
-      "fail_account_exist": "Аккаунт уже существует, измените имя аккаунта и попробуйте снова!",
+      "fail_account_exist": "Данный аккаунт уже существует, измените имя аккаунта и попробуйте снова!",
       "fail_ipmax": "На один IP нельзя регистрировать несколько аккаунтов"
     },
     "login": {
@@ -616,8 +656,8 @@
       "notfound": "Ваш аккаунт не найден, пожалуйста, зарегистрируйтесь или попробуйте снова!"
     },
     "mail": {
-      "wrong_id": "Вам нужно выбрать письмо для удаления!",
-      "mail_has_money": "В письме все еще есть деньги!",
+      "wrong_id": "Выбирите письмо, которое хотите удалить!",
+      "mail_has_money": "Получите средства из письма перед удалением!",
       "mail_not_existed": "Письмо не найдено!",
       "mail_gift_taken": "Вложение получено"
     },
@@ -626,7 +666,14 @@
       "score_fail": "Сумма должна быть целым числом.",
       "params_error": "ошибка параметров",
       "pix_type_missing": "Отсутствует тип PIX",
-      "cpf_number_used": "Номер CPF использовался несколько раз и не может быть сохранен"
+      "cpf_number_used": "Номер CPF использовался несколько раз и не может быть сохранен",
+      "no_withdraw_value": "Недостаточная сумма для вывода",
+      "too_low": "Ниже минимальной суммы вывода",
+      "too_high": "Превышает максимальную сумму вывода",
+      "day_max": "Превышен дневной лимит на вывод",
+      "no_enouth_score": "Недостаточно денег на руках",
+      "in_game": "Открыто в игре",
+      "no_deposit": "Для вывода необходимо внести депозит"
     },
     "payment": {
       "paytype_error": "Ошибка типа платежа",
@@ -640,6 +687,12 @@
       "recharge_required": "Для получения ежедневного бонуса требуется пополнение",
       "unknown_error": "Неизвестная ошибка",
       "exception_occurred": "Произошла ошибка при обработке вашего запроса"
+    },
+    "verify": {
+      "num_empty": "Номер телефона пуст",
+      "code_too_long": "Код телефона слишком длинный",
+      "already_bound": "Номер телефона уже привязан",
+      "code_incorrect_or_expired": "Код неверен или истек"
     }
   }
-}
+}

+ 57 - 4
src/locales/languages/zh_TW.json

@@ -105,7 +105,14 @@
       "empty": "請輸入密碼!"
     },
     "success": "登入成功,歡迎回來!",
-    "notfound": "未找到您的帳戶,請註冊或重試!"
+    "notfound": "未找到您的帳戶,請註冊或重試!",
+    "login_type": {
+      "password": "密碼",
+      "otp": "驗證碼"
+    },
+    "otp_ph": "驗證碼",
+    "guest_login": "訪客登入",
+    "btn_otp": "驗證碼"
   },
   "logout": {
     "success": "您已成功退出。"
@@ -141,7 +148,9 @@
     "email": "電子郵件",
     "security": "安全",
     "install_banner": "顯示安裝通知",
-    "sound": "聲音"
+    "sound": "聲音",
+    "bindPhone": "帳號升級",
+    "bonus": "獲得獎金+{0}"
   },
   "copy_success": "複製成功",
   "user": {
@@ -225,7 +234,13 @@
     "back": "返回",
     "msg_title": "退出遊戲",
     "msg_content": "您確定要退出嗎?",
-    "cancel": "取消"
+    "cancel": "取消",
+    "brand_by": "來源",
+    "tips": {
+      "movie_mode": "電影模式",
+      "full_screen": "全螢幕"
+    },
+    "share_title": "分享這個遊戲"
   },
   "msgbox": {
     "title": "提示",
@@ -333,6 +348,20 @@
       "processing": "處理中",
       "detail_title": "記錄詳情"
     },
+    "tip": {
+      "success": "提交成功,系统正在审核中",
+      "total_not_enough": "可提取金额不足",
+      "under_can_score": "单次提取数量不能少于 {0}",
+      "over_can_score": "单次提取数量不能超过 {0}",
+      "exceed_count_limit": "每天只能发起 {0} 次提取请求",
+      "user_score_not_enough": "您的筹码不足",
+      "user_in_game": "抱歉,您目前正在游戏中",
+      "need_recharge": "您的提取请求金额过高,请充值后再继续提取",
+      "btn_to_recharge": "去充值",
+      "unbound_account": "您未绑定银行账户,无法提取",
+      "account_abnormal": "系统检测到您的账户有异常操作,已禁止提取,请联系客服处理",
+      "score_null": "您未填写提取金额"
+    },
     "help": {
       "title": "提現規則",
       "rule_1": "1. 每次提現的最低/最高金額為 {0}/{1};",
@@ -566,6 +595,17 @@
     "reconnect": "重新連接",
     "disconnected": "網路已斷開連接,要重新連接嗎?"
   },
+  "installBox": {
+    "tips": "我們希望將 24680.com 添加到您的主屏幕,以獲取最新的免費投注和獎金更新。",
+    "btn_cancel": "取消",
+    "btn_install": "安裝"
+  },
+  "bindPhone": {
+    "title_link_phone": "帳戶升級",
+    "phone_award": "獎勵:",
+    "btn_submit": "提交",
+    "tips": "連接您的手機並獲得 {0} 積分獎勵"
+  },
   "web": {
     "activity": {
       "redpack_not_available": "沒有紅包",
@@ -626,7 +666,14 @@
       "score_fail": "金額必須是整數值。",
       "params_error": "參數錯誤",
       "pix_type_missing": "缺少PIX類型",
-      "cpf_number_used": "CPF號碼已多次使用,無法保存"
+      "cpf_number_used": "CPF號碼已多次使用,無法保存",
+      "no_withdraw_value": "提現金額不足",
+      "too_low": "低於最低提現金額",
+      "too_high": "高於最高提現金額",
+      "day_max": "超過每日提現限額",
+      "no_enouth_score": "手頭資金不足",
+      "in_game": "在遊戲中打開",
+      "no_deposit": "提現需要先充值"
     },
     "payment": {
       "paytype_error": "支付類型錯誤",
@@ -640,6 +687,12 @@
       "recharge_required": "每日獎金需要充值",
       "unknown_error": "未知錯誤",
       "exception_occurred": "處理您的請求時發生錯誤"
+    },
+    "verify": {
+      "num_empty": "電話號碼為空",
+      "code_too_long": "電話代碼過長",
+      "already_bound": "電話號碼已綁定",
+      "code_incorrect_or_expired": "代碼不正確或已過期"
     }
   }
 }

+ 8 - 3
src/pages/Game.jsx

@@ -1,10 +1,15 @@
 import WGame from "@widgets/WGame";
+import {useThemeProvider} from "@contexts/themeContext";
+import WDGame from "@widgets/WGame/WDGame/WDGame";
 
 const Game=({...props})=>{
+    const {isMobile} = useThemeProvider();
     return (
-        <WGame {...props}>
-
-        </WGame>
+        <>
+            {
+                isMobile()?(<WGame {...props}/>):(<WDGame {...props}/>)
+            }
+        </>
     )
 }
 export default Game;

+ 2 - 0
src/styles/_global.scss

@@ -132,10 +132,12 @@ input, input::placeholder {
 .sidebar-open{
     //margin-left: 250px;
     margin-left: var(--side-open);
+    transition: margin .3s ease;
 }
 .sidebar-close{
     //margin-left: 65px;
     margin-left: var(--side-close);
+    transition: margin .3s ease;
 }
 .sidebar-nav{
     //padding: 20px 10px 80px;

+ 1 - 0
src/styles/_vars.scss

@@ -69,6 +69,7 @@ $width-desktop-big-max:1564px;
     --zindex-drawer:1200;
     --zindex-popup:1300;
     --zindex-effect:1400;
+    --zindex-install:1500;
     --coin-yellow:#edc349;
     //--progressierBg
     --progressierBg:var(--widget) !important;

+ 154 - 0
src/tracking/KwaiPixel.js

@@ -0,0 +1,154 @@
+import React from "react";
+
+let kwaiPixel_ID = null;
+export const kwai_setPixelID = (pixelID, ex_id) => {
+    if (!pixelID) return;
+    kwaiPixel_ID = pixelID;
+    (function (e, t) {
+        if (typeof exports === "object" && typeof module === "object") {
+            module.exports = t();
+        } else if (typeof exports === "object") {
+            exports.install = t();
+        } else {
+            e.install = t();
+        }
+    }(window, function () {
+        return function (e) {
+            var t = {};
+
+            function n(o) {
+                if (t[o]) return t[o].exports;
+                var r = t[o] = { i: o, l: false, exports: {} };
+                e[o].call(r.exports, r, r.exports, n);
+                r.l = true;
+                return r.exports;
+            }
+
+            n.m = e;
+            n.c = t;
+            n.d = function (e, t, o) {
+                if (!n.o(e, t)) {
+                    Object.defineProperty(e, t, { enumerable: true, get: o });
+                }
+            };
+            n.r = function (e) {
+                if (typeof Symbol !== "undefined" && Symbol.toStringTag) {
+                    Object.defineProperty(e, Symbol.toStringTag, { value: "Module" });
+                }
+                Object.defineProperty(e, "__esModule", { value: true });
+            };
+            n.t = function (e, t) {
+                if (t & 1) e = n(e);
+                if (t & 8) return e;
+                if (t & 4 && typeof e === "object" && e && e.__esModule) return e;
+                var o = Object.create(null);
+                n.r(o);
+                Object.defineProperty(o, "default", { enumerable: true, value: e });
+                if (t & 2 && typeof e !== "string") {
+                    for (var r in e) {
+                        n.d(o, r, function (t) { return e[t]; }.bind(null, r));
+                    }
+                }
+                return o;
+            };
+            n.n = function (e) {
+                var t = e && e.__esModule ? function () { return e.default; } : function () { return e; };
+                n.d(t, "a", t);
+                return t;
+            };
+            n.o = function (e, t) {
+                return Object.prototype.hasOwnProperty.call(e, t);
+            };
+            n.p = "";
+            return n(n.s = 0);
+        }([function (e, t, n) {
+            "use strict";
+            var o = this && this.__spreadArray || function (e, t, n) {
+                if (n || arguments.length === 2) {
+                    for (var o, r = 0, i = t.length; r < i; r++) {
+                        if (!o && r in t || o) {
+                            o || (o = Array.prototype.slice.call(t, 0, r));
+                            o[r] = t[r];
+                        }
+                    }
+                }
+                return e.concat(o || Array.prototype.slice.call(t));
+            };
+            Object.defineProperty(t, "__esModule", { value: true });
+            var r = function (e, t, n) {
+                var o, i = e.createElement("script");
+                i.type = "text/javascript";
+                i.async = true;
+                i.src = t;
+                if (n) {
+                    i.onerror = function () {
+                        r(e, n);
+                    };
+                }
+                var a = e.getElementsByTagName("script")[0];
+                var parentNode = a.parentNode;
+                if (parentNode) {
+                    parentNode.insertBefore(i, a);
+                }
+            };
+            (function (e, t, n) {
+                e.KwaiAnalyticsObject = n;
+                var i = e[n] = e[n] || [];
+                i.methods = ["page", "track", "identify", "instances", "debug", "on", "off", "once", "ready", "alias", "group", "enableCookie", "disableCookie"];
+                var a = function (e, t) {
+                    e[t] = function () {
+                        var n = [];
+                        for (var r = 0; r < arguments.length; r++) {
+                            n[r] = arguments[r];
+                        }
+                        var i = o([t], n, true);
+                        e.push(i);
+                    };
+                };
+                i.methods.forEach(function (e) {
+                    a(i, e);
+                });
+                i.instance = function (e) {
+                    var t, n = (i._i && i._i[e]) || [];
+                    i.methods.forEach(function (e) {
+                        a(n, e);
+                    });
+                    return n;
+                };
+                i.load = function (e, o) {
+                    var a = "https://s1.kwai.net/kos/s101/nlav11187/pixel/events.js";
+                    i._i = i._i || {};
+                    i._i[e] = [];
+                    i._i[e]._u = a;
+                    i._t = i._t || {};
+                    i._t[e] = +new Date();
+                    i._o = i._o || {};
+                    i._o[e] = o || {};
+                    var c = "?sdkid=".concat(e, "&lib=").concat(n);
+                    r(t, a + c, "https://s16-11187.ap4r.com/kos/s101/nlav11187/pixel/events.js" + c);
+                };
+            }(window, document, "kwaiq"));
+        }]); // Add semicolon here
+    })); // Add semicolon here
+
+    window.kwaiq.load(pixelID);
+    window.kwaiq.page();
+};
+
+// fbPixel.js
+export const kwai_trackPageView = () => {
+    if (window.kwaiq) {
+        window.kwaiq.page();
+    }
+};
+
+/**
+ * fbq('trackCustom', "firstpayD0", {external_id: data.udid})
+ * @param eventName
+ * @param params
+ */
+export const kwai_trackEvent = (eventName, params) => {
+    if (window.kwaiq) {
+        window.kwaiq.instance(kwaiPixel_ID).track(eventName, params);
+    }
+};

+ 31 - 1
src/tracking/TrackAll.js

@@ -1,12 +1,20 @@
 import {fb_trackEvent,fb_trackPageView,fb_setPixelID} from "./FacebookPixel";
+import {kwai_setPixelID,kwai_trackEvent} from "./KwaiPixel";
 
 export const initTrackAll = (pf_type,pf_id,ex_id) => {
     if(pf_type==="fb"){
         fb_setPixelID(pf_id,ex_id)
+    }else if(pf_type==="kw"){
+        kwai_setPixelID(pf_id,ex_id)
     }
     window.addEventListener('installready', function(){
         //Show your own installation UI
-        trackEvent("InstallPWA")
+        if(pf_type==="fb"){
+            trackEvent("InstallPWA")
+        }else if(pf_type==="kw"){
+            trackEvent("download")
+        }
+
     });
 }
 
@@ -47,4 +55,26 @@ export const trackEvent = (eventName, params) => {
             fb_trackEvent(eventName, params);
         }
     }
+    if (window.kwaiq&&global.pf&&global.pf.type==='kw') {
+        if(eventName==="pay"){
+            let data=params;
+            let addParams={
+                value: data.golds,
+                currency: 'BRL',
+                mapping_id: data.udid
+            };
+            if(data.first) {
+                if (!!data.isd0) {
+                    kwai_trackEvent('firstDeposit', addParams)
+                    kwai_trackEvent('addToWishlist', addParams);
+                }
+                kwai_trackEvent('addToCart', addParams);
+            }
+
+            kwai_trackEvent('purchase', addParams);
+        }else{
+            if(eventName==='CompleteRegistration')eventName='completeRegistration';
+            kwai_trackEvent(eventName, params);
+        }
+    }
 };

+ 287 - 0
src/utils/UAUtils.js

@@ -0,0 +1,287 @@
+import { UAParser } from 'ua-parser-js';
+// Constants for browsers
+const isAndroid=()=>{
+    try {
+        return window.progressier.detection.isAndroid();
+    } catch (t) {
+        return false
+    }
+}
+const isIOS=()=>{
+    try {
+        return window.progressier.detection.isIOS();
+    } catch (t) {
+        return false
+    }
+}
+const isMacOs=()=>{
+    try {
+        return window.progressier.detection.isMacOs();
+    } catch (t) {
+        return false
+    }
+}
+const isWindows=()=>{
+    try {
+        return window.progressier.detection.isWindows();
+    } catch (e) {
+        return false
+    }
+}
+const isSafari=()=>{
+    try {
+        return window.progressier.detection.isSafari();
+    } catch (e) {
+        return false
+    }
+}
+const isFirefox=()=>{
+    try {
+        return window.progressier.detection.isFirefox();
+    } catch (n) {
+        return false
+    }
+}
+const isSamsungInternet=()=>{
+    try {
+        return window.progressier.detection.isSamsungInternet();
+    } catch (e) {
+        return false
+    }
+}
+const isChromeEdgeOnMacOs=()=>{
+    try {
+        return window.progressier.detection.isChromeEdgeOnMacOs();
+    } catch (e) {
+        return false
+    }
+}
+const isArc=()=>{
+    try {
+        return window.progressier.detection.isArc();
+    } catch (e) {
+        return false
+    }
+}
+const isEdge=()=>{
+    try {
+        return window.progressier.detection.isEdge();
+    } catch (e) {
+        return false
+    }
+}
+const isChrome=()=>{
+    try {
+        return window.progressier.detection.isChrome();
+    } catch (r) {
+        return false
+    }
+}
+const isOpera=()=>{
+    try {
+        return window.progressier.detection.isOpera();
+    } catch (e) {
+        return false
+    }
+}
+const supportsNativeiOSPush=()=>{
+    try {
+        return window.progressier.detection.supportsNativeiOSPush();
+    }
+    catch (e){
+        return false;
+    }
+}
+const appstore=()=>{
+    return supportsNativeiOSPush()
+}
+const supportsPush=()=>{
+    try {
+        return window.progressier.detection.supportsPush();
+    }
+    catch (e){
+        return false;
+    }
+}
+const isFromTwitter=()=>{
+    try {
+        return window.progressier.detection.isFromTwitter();
+    }
+    catch (e){
+        return false;
+    }
+}
+const getInAppBrowser=()=>{
+    try {
+        var t = window.navigator.userAgent;
+        var e = ["wv", "Line/", "FBAN", "FBBV", "FBAV", "FB_IAB", "Instagram", "MicroMessenger", "Twitter", "Kakao", "KAKAO"];
+        var i = "";
+        e.forEach(function(e) {
+            if (!t.includes(e)) {
+                return
+            }
+            i = e;
+        });
+        return i;
+    } catch (n) {
+        return "";
+    }
+}
+const isInAppBrowser=()=>{
+    try {
+        // return window.progressier.detection.isInAppBrowser();
+        return getInAppBrowser()!=="";
+    }
+    catch (e){
+        return false;
+    }
+}
+const isChromeOS=()=>{
+    try {
+        return window.progressier.detection.isChromeOS();
+    } catch (e) {
+        return false
+    }
+}
+const isDesktop=()=>{
+    try {
+        return window.progressier.detection.isDesktop();
+    } catch (e) {
+        return false
+    }
+}
+const isSafariWithPushOnMacOs=()=>{
+   try {
+       return window.progressier.detection.isSafariWithPushOnMacOs();
+   }
+   catch (e){
+       return false
+   }
+}
+const isSafariWithInstallationMacOS=()=>{
+    try {
+        return window.progressier.detection.isSafariWithInstallationMacOS();
+    } catch (a) {
+        return false
+    }
+}
+const canBePushPrompted=()=>{
+    try {
+        return window.progressier.detection.canBePushPrompted();
+    }
+    catch (e){
+        return false
+    }
+}
+const isStandalone=()=>{
+    try {
+        return window.progressier.detection.isStandalone();
+    }
+    catch (e){
+        return false
+    }
+}
+const iosVersion=()=>{
+    try {
+        return window.progressier.detection.iosVersion();
+    }
+    catch (e){
+        return false
+    }
+}
+const recentiOS=()=>{
+    try {
+        return window.progressier.detection.recentiOS();
+    }
+    catch (e){
+        return false
+    }
+}
+const isTWA=()=>{
+    try {
+        return window.progressier.detection.isTWA();
+    }
+    catch (e){
+        return false
+    }
+}
+const requiresInScopeSw=()=>{
+    try {
+        return window.progressier.detection.requiresInScopeSw();
+    }
+    catch (e){
+        return false
+    }
+}
+const isIframe=()=>{
+    try {
+        return window.progressier.detection.isIframe();
+    }
+    catch (e){
+        return false
+    }
+}
+const isShowInstallBox=()=>{
+    if (isStandalone())return false;
+    if (isTWA())return false;
+    return true;
+}
+const openProgressierInstall=()=>{
+    if(window.progressier&&window.progressier.flow)window.progressier.flow.init("install",true)
+}
+const openUrlUseChrome=(url="")=>{
+    try {
+        if (url==null||url==="")url=window.location.href;
+        if (global.BFP!=null&&global.BFP!==""){
+            if (url.indexOf("?")===-1)url+="?bfp="+global.BFP;
+            else url+="&bfp="+global.BFP;
+        }
+        if (isTWA())return;
+        if (isIOS()){
+            let r = "googlechromes://" + url.replace(/^https?:\/\//, "");
+            window.location.href = r
+        }
+        else if (isAndroid()){
+            const r = url.split(":")[0];
+            let l = url.replace(/^https?:\/\//, "");
+            window.location.href = "intent://".concat(l, "#Intent;scheme=").concat(r, ";package=com.android.chrome;end")
+        }
+    }
+    catch (e){
+        return false;
+    }
+}
+export {
+    isAndroid,
+    isIOS,
+    isMacOs,
+    isWindows,
+    isSafari,
+    isFirefox,
+    isSamsungInternet,
+    isChromeEdgeOnMacOs,
+    isArc,
+    isEdge,
+    isChrome,
+    isOpera,
+    supportsNativeiOSPush,
+    appstore,
+    supportsPush,
+    isFromTwitter,
+    isInAppBrowser,
+    isChromeOS,
+    isDesktop,
+    isSafariWithPushOnMacOs,
+    isSafariWithInstallationMacOS,
+    canBePushPrompted,
+    isStandalone,
+    iosVersion,
+    recentiOS,
+    isTWA,
+    requiresInScopeSw,
+    isIframe,
+    isShowInstallBox,
+    openUrlUseChrome,
+    openProgressierInstall,
+    getInAppBrowser
+};

+ 223 - 64
src/widgets/LoginForm/index.jsx

@@ -19,20 +19,26 @@ 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";
 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();
@@ -44,6 +50,7 @@ const LoginForm = ({close}) => {
     const regStatus=useSelector(getRegStatus);
     const dispatch = useDispatch();
     const navigate=useNavigate();
+    const [loginType,setLoginType]=useState(LOGIN_TYPE_PASS);
     const {register, handleSubmit, formState: {errors}, control} = useForm({
         defaultValues: {
             // country:'55',
@@ -51,13 +58,52 @@ const LoginForm = ({close}) => {
             password: '',
             email:"",
             repassword:"",
-            lang:""
+            lang:"",
+            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(() => {
@@ -68,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});
     }
@@ -85,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));
@@ -107,68 +169,165 @@ const LoginForm = ({close}) => {
             </div>
         )
     }
-    const renderLoginForm=()=>{
+    const renderLoginToReg=()=>{
+        return (
+            <div className={styles.l_reg_new}>
+                <div>{i18n.t("login.reg_new_des")}</div>
+                <div className={styles.reg_btn} onClick={() => {
+                    // setShowType(TYPE_REGISTER);
+                    if (isMobile()) navigatRoute(ROUTE_PATH.SIGN_UP)
+                    else setShowType(TYPE_REGISTER);
+                }}>
+                    {i18n.t("sign_up")}
+                </div>
+            </div>
+        )
+    }
+    const renderGuestLogin=()=>{
+        // return (
+        //     <button type={"button"} className={styles.guest_btn}>
+        //         {i18n.t("login.guest_login")}
+        //     </button>
+        // )
+    }
+    const renderPassLogin = () => {
+        return (
+            <form key={"pass"} autoComplete="off" className={styles.l_form} onSubmit={handleSubmit((data) => {
+                onSubmitLogin(data);
+            })}>
+                <Controller control={control}
+                            name="account"
+                            rules={{required: true}}
+                            render={({field: {ref, onChange, value}, fieldState: {error}}) => {
+                                let message = error ? i18n.t("form_errors.phone_" + country) : "";
+                                return (<PhoneInput
+                                    key={"account"}
+                                    className={classNames({'input-control--error': error})}
+                                    value={value}
+                                    message={message}
+                                    onChange={e => onChange(e.target.value)}
+                                    onChangeCountry={(country) => {
+                                        setCountry(country);
+                                    }}
+                                    placeholder={i18n.t('login.phone_ph')}
+                                    innerRef={ref}/>)
+                            }}
+                />
+                <Controller control={control}
+                            name="password"
+                            rules={{required: true, minLength: 6}}
+                            render={({field: {ref, onChange, value}, fieldState: {error}}) => (
+                                <PasswordInput
+                                    className={classNames({'input-control--error': error})}
+                                    value={value}
+                                    message={error ? i18n.t("form_errors.pass") : ''}
+                                    onChange={e => onChange(e.target.value)}
+                                    placeholder={i18n.t('login.login_pwd')}
+                                    innerRef={ref}/>
+                            )}
+                />
+                <button className={"button-green"}>
+                    {
+                        loginStatus === REQ_STATUS.LOADING ? renderLoading() : i18n.t("login.sign_in_btn")
+                    }
+                </button>
+                {renderGuestLogin()}
+                {renderLoginToReg()}
+            </form>
+        )
+    }
+    const renderOTPLogin=()=>{
+        return (
+            <form key={"otp"} autoComplete="off" className={styles.l_form} onSubmit={handleSubmit((data) => {
+                onSubmitOTPLogin(data);
+            })}>
+                <Controller control={control}
+                            name="phone"
+                            rules={{required: true}}
+                            render={({field: {ref, onChange, value}, fieldState: {error}}) => {
+                                let message = error ? i18n.t("form_errors.phone_" + country) : "";
+                                return (<PhoneInput
+                                    key={"phone"}
+                                    className={classNames({'input-control--error': error})}
+                                    value={value}
+                                    message={message}
+                                    onChange={e => onChange(e.target.value)}
+                                    onChangeCountry={(country) => {
+                                        setCountry(country);
+                                    }}
+                                    placeholder={i18n.t('login.phone_ph')}
+                                    innerRef={ref}/>)
+                            }}
+                />
+                <Controller control={control}
+                            name="code"
+                            rules={{required: true}}
+                            render={({field: {ref, onChange, value}, fieldState: {error}}) => {
+                                return (
+                                    <div className={`d-flex flex-column g-6`}>
+                                        <div
+                                            className={`input-control ${classNames({'input-control--error': error})}`}>
+                                            <input
+                                                type={'text'}
+                                                autoComplete="off"
+                                                ref={ref}
+                                                onChange={e => onChange(e.target.value)}
+                                                placeholder={i18n.t('login.otp_ph')}
+                                            />
+                                            <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 ? i18n.t('login.btn_otp') : otpCD + "S"
+                                                            }
+                                                        </>
+                                                    )
+                                                }
+                                            </button>
+                                        </div>
+                                    </div>
+                                )
+                            }}
+                />
+                <button className={"button-green"}>
+                    {
+                        smsLoginStatus === REQ_STATUS.LOADING ? renderLoading() : i18n.t("login.sign_in_btn")
+                    }
+                </button>
+                {renderGuestLogin()}
+                {renderLoginToReg()}
+            </form>
+        )
+    }
+    const renderLoginForm = () => {
         return (
-            <Spring type="slideUp" >
+            <Spring type="slideUp">
                 <div className={styles.l_title}>{i18n.t("login.login_title")}</div>
-                <form autoComplete="off" className={styles.l_form} onSubmit={handleSubmit((data) => {
-                    onSubmitLogin(data);
-                })}>
-                    <Controller control={control}
-                                name="account"
-                                rules={{required: true}}
-                                render={({field: {ref, onChange, value}, fieldState: {error}}) => {
-                                    let message=error?i18n.t("form_errors.phone_"+country):"";
-                                    return (<PhoneInput
-                                        className={classNames({'input-control--error': error})}
-                                        value={value}
-                                        message={message}
-                                        onChange={e => onChange(e.target.value)}
-                                        onChangeCountry={(country)=>{
-                                            setCountry(country);
-                                        }}
-                                        placeholder={i18n.t('login.phone_ph')}
-                                        innerRef={ref}/>)
-                                }}
-                    />
-                    <Controller control={control}
-                                name="password"
-                                rules={{required: true,minLength:6}}
-                                render={({field: {ref, onChange, value}, fieldState: {error}}) => (
-                                    <PasswordInput
-                                        className={classNames({'input-control--error': error})}
-                                        value={value}
-                                        message={error?i18n.t("form_errors.pass"):''}
-                                        onChange={e => onChange(e.target.value)}
-                                        placeholder={i18n.t('login.login_pwd')}
-                                        innerRef={ref}/>
-                                )}
-                    />
-                    {/*<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")
-                        }
+                <div className={styles.l_type_tabs}>
+                    <button className={`${styles.l_type_btn} ${loginType === LOGIN_TYPE_PASS ? styles.active : ''}`}
+                            onClick={() => {
+                                setLoginType(LOGIN_TYPE_PASS)
+                            }}>
+                        <span>{i18n.t("login.login_type.password")}</span>
                     </button>
-                    <div className={styles.l_reg_new}>
-                        <div>{i18n.t("login.reg_new_des")}</div>
-                        <div className={styles.reg_btn} onClick={()=>{
-                            // setShowType(TYPE_REGISTER);
-                            if(isMobile())navigatRoute(ROUTE_PATH.SIGN_UP)
-                            else setShowType(TYPE_REGISTER);
-                        }}>
-                            {i18n.t("login.reg_new_btn")}
-                        </div>
-                    </div>
-                </form>
+                    <button className={`${styles.l_type_btn} ${loginType === LOGIN_TYPE_OTP ? styles.active : ''}`}
+                            onClick={() => {
+                                setLoginType(LOGIN_TYPE_OTP)
+                            }}>
+                        <span>{i18n.t("login.login_type.otp")}</span>
+                    </button>
+                </div>
+                {
+                    loginType===LOGIN_TYPE_PASS?renderPassLogin():renderOTPLogin()
+                }
             </Spring>
         )
     }
-    const renderRegisterForm=()=>{
+    const renderRegisterForm = () => {
         return (
             <Spring type="slideUp">
                 <div className={styles.l_title}>{i18n.t("login.reg_title")}</div>
@@ -177,15 +336,15 @@ const LoginForm = ({close}) => {
                 })}>
                     <Controller control={control}
                                 name="account"
-                                rules={{required: true,validate:validatePhone}}
+                                rules={{required: true, validate: validatePhone}}
                                 render={({field: {ref, onChange, value}, fieldState: {error}}) => {
-                                    let message=error?i18n.t("form_errors.phone_"+country):"";
+                                    let message = error ? i18n.t("form_errors.phone_" + country) : "";
                                     return (<PhoneInput
                                         className={classNames({'input-control--error': error})}
                                         value={value}
                                         message={message}
                                         onChange={e => onChange(e.target.value)}
-                                        onChangeCountry={(country)=>{
+                                        onChangeCountry={(country) => {
                                             setCountry(country);
                                         }}
                                         placeholder={i18n.t('login.phone_ph')}
@@ -194,12 +353,12 @@ const LoginForm = ({close}) => {
                     />
                     <Controller control={control}
                                 name="password"
-                                rules={{required: true,minLength:6}}
+                                rules={{required: true, minLength: 6}}
                                 render={({field: {ref, onChange, value}, fieldState: {error}}) => (
                                     <PasswordInput
                                         className={classNames({'input-control--error': error})}
                                         value={value}
-                                        message={error?i18n.t("form_errors.pass"):''}
+                                        message={error ? i18n.t("form_errors.pass") : ''}
                                         onChange={e => onChange(e.target.value)}
                                         placeholder={i18n.t('login.login_pwd')}
                                         innerRef={ref}/>

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

@@ -65,3 +65,63 @@
     color: var(--primary-color);
   }
 }
+.l_type_tabs{
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: space-between;
+  margin-top: 1rem;
+  & .l_type_btn{
+    cursor: pointer;
+    width: 50%;
+    height: 2.75rem;
+    position: relative;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    span{
+      position: relative;
+      z-index: 2;
+      font-size: 1rem;
+      line-height: 1.25rem;
+      color: var(--text);
+    }
+    &.active{
+      &:after{
+        content: " ";
+        width: 100%;
+        height: 1.375rem;
+        border-bottom: 1px solid var(--primary-color);
+        background: linear-gradient(180deg, rgba(59, 193, 23, 0) 0%, rgba(59, 193, 23, 0) .01%, rgba(59, 193, 23, .2) 100%);
+        position: absolute;
+        left: 0;
+        bottom: 0;
+      }
+      span{
+        color: var(--header);
+        font-weight: 600;
+      }
+    }
+  }
+}
+.otp_btn{
+  min-width: 3rem;
+  height: 2.5rem;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: flex-end;
+  color: var(--primary-color);
+  text-align: right;
+  font-size: 1rem;
+  &.cd_timer{
+    color:var(--text);
+  }
+}
+.guest_btn{
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: center;
+  color: var(--text);
+}

+ 43 - 2
src/widgets/UserSettings/index.jsx

@@ -1,13 +1,13 @@
 import styles from './styles.module.scss';
 import {useDispatch, useSelector} from "react-redux";
 import {ReactComponent as EmptyAvatar} from '@assets/avatar_empty.svg';
-import {getLogoutStatus, getUserInfo, isLogined} from "@features/mainSlice";
+import {getIsGuest, getLogoutStatus, getUpgradeBonus, getUserInfo, isLogined} from "@features/mainSlice";
 import Spring from "@components/Spring";
 import {useNavigate} from "react-router-dom";
 import {ROUTE_PATH} from "@constants/routes";
 import LazyImage from "@components/LazyImage";
 import USD from "@assets/lvicon/USD.black.png";
-import {numFormatter} from "@utils/helpers";
+import {formatCash, numFormatter, stringFormat} from "@utils/helpers";
 import SvgIcon from "@components/SvgIcon";
 import {ReactComponent as Male} from '@assets/singlesvg/gener/male.svg';
 import {ReactComponent as FeMale} from '@assets/singlesvg/gener/female.svg';
@@ -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";
@@ -38,6 +39,8 @@ const UserSettings=()=>{
     const dispatch = useDispatch();
     const navigate=useNavigate();
     const {theme, toggleTheme,setIsShowLang,installOpen,toggleInstallOpen,soundOpen,toggleSoundOpen} = useThemeProvider();
+    const guest=useSelector(getIsGuest);
+    const upgradBonus=useSelector(getUpgradeBonus);
     const renderEmpty=()=>{
         return (
             <div className={styles.wrap_item}>
@@ -319,10 +322,48 @@ const UserSettings=()=>{
             </div>
         )
     }
+    const renderBindPhone=()=>{
+        let isShowBind=false;
+        if (guest===1){
+            if (user!=null){
+                if (user.Phone==null||user.Phone===""){
+                    isShowBind=true;
+                }
+            }
+        }
+        let bonusStr=stringFormat(i18n.t("settings.bonus"),[global.DOLLAR+""+formatCash(upgradBonus,0)]);
+        return (
+            <>
+                {
+                    isLogin&&isShowBind?(
+                        <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}>
+                                {
+                                    upgradBonus>0?(
+                                        <div className={styles.bonus_con}>
+                                            {bonusStr}
+                                        </div>
+                                    ):(<></>)
+                                }
+                                <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()}

+ 14 - 0
src/widgets/UserSettings/styles.module.scss

@@ -166,6 +166,7 @@
     align-items: center;
     color: var(--header);
     gap: 0.525rem;
+    text-align: left;
     & .icon{
       width: 1.4rem;
       height: 1.4rem;
@@ -238,3 +239,16 @@
   border-top: 1px solid var(--card-line);
   margin: 0.75rem 0;
 }
+.bonus_con{
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: center;
+  background-color: var(--primary-color);
+  color: #fff;
+  padding: 0rem 0.5rem;
+  border-radius: 0.3rem;
+  font-size: 0.85rem;
+  font-weight: 600;
+  text-align: left;
+}

+ 212 - 0
src/widgets/WBindPhone/index.jsx

@@ -0,0 +1,212 @@
+import React, {useEffect, useState} from "react";
+import Popup from "@components/Popup";
+import styles from "./styles.module.scss";
+import usdimg from "@assets/lvicon/USD.black.png";
+import {Controller, useForm} from "react-hook-form";
+import PhoneInput from "@components/UInput/PhoneInput";
+import classNames from "classnames";
+import PasswordInput from "@components/UInput/PasswordInput";
+import {formatCash, stringFormat, validateCountryPhone} from "@utils/helpers";
+import {REQ_STATUS} from "@api/config";
+import {useDispatch, useSelector} from "react-redux";
+import {getBindSMSStatus, getSendCodeStatus, getSendCodeTS, getUpgradeBonus} 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 [country, setCountry] = useState("55");
+    const otpTimeStamp=useSelector(getSendCodeTS);
+    const [otpCD,setOtpCD]=useState(-1);
+    const otpStatus=useSelector(getSendCodeStatus);
+    const bindStatus=useSelector(getBindSMSStatus);
+    const upgradBonus=useSelector(getUpgradeBonus);
+    const regCoins=formatCash(upgradBonus,0);
+    const dispatch=useDispatch();
+    const {register, handleSubmit, formState: {errors}, control} = useForm({
+        defaultValues: {
+            phone: '',
+            password: '',
+            repassword:'',
+            code:''
+        }
+    });
+    // /bindsms    {phone,code,password,repassword}
+    const show=()=>{
+        setIsShow(true);
+    }
+    const hide=()=>{
+        setIsShow(false);
+    }
+    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={
+            show,
+            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}>
+                <span className={styles.title}>
+                    {i18n.t("bindPhone.title_link_phone")}
+                </span>
+               <div className={styles.award_con}>
+                   {i18n.t("bindPhone.phone_award")}
+                   <img src={usdimg}/>
+                   {regCoins}
+               </div>
+               <form autoComplete="off" className={styles.l_form} onSubmit={handleSubmit((data) => {
+                   onSubmitBind(data);
+               })}>
+                   <Controller control={control}
+                               name="phone"
+                               rules={{required: true, validate: validatePhone}}
+                               render={({field: {ref, onChange, value}, fieldState: {error}}) => {
+                                   let message = error ? i18n.t("form_errors.phone_" + country) : "";
+                                   return (<PhoneInput
+                                       className={classNames({'input-control--error': error})}
+                                       value={value}
+                                       message={message}
+                                       onChange={e => onChange(e.target.value)}
+                                       onChangeCountry={(country) => {
+                                           setCountry(country);
+                                       }}
+                                       placeholder={i18n.t('login.phone_ph')}
+                                       innerRef={ref}/>)
+                               }}
+                   />
+                   <Controller control={control}
+                               name="password"
+                               rules={{required: true, minLength: 6}}
+                               render={({field: {ref, onChange, value}, fieldState: {error}}) => (
+                                   <PasswordInput
+                                       className={classNames({'input-control--error': error})}
+                                       value={value}
+                                       message={error ? i18n.t("form_errors.pass") : ''}
+                                       onChange={e => onChange(e.target.value)}
+                                       placeholder={i18n.t('login.login_pwd')}
+                                       innerRef={ref}/>
+                               )}
+                   />
+                   <Controller control={control}
+                               name="repassword"
+                               rules={{required: true, minLength: 6}}
+                               render={({field: {ref, onChange, value}, fieldState: {error}}) => (
+                                   <PasswordInput
+                                       className={classNames({'input-control--error': error})}
+                                       value={value}
+                                       message={error ? i18n.t("form_errors.pass") : ''}
+                                       onChange={e => onChange(e.target.value)}
+                                       placeholder={i18n.t('login.re_pwd')}
+                                       innerRef={ref}/>
+                               )}
+                   />
+                   <Controller control={control}
+                               name="code"
+                               rules={{required: true}}
+                               render={({field: {ref, onChange, value}, fieldState: {error}}) => {
+                                   return (
+                                       <div className={`d-flex flex-column g-6`}>
+                                           <div
+                                               className={`input-control ${classNames({'input-control--error': error})}`}>
+                                               <input
+                                                   type={'text'}
+                                                   autoComplete="off"
+                                                   ref={ref}
+                                                   onChange={e => onChange(e.target.value)}
+                                                   placeholder={i18n.t('login.otp_ph')}
+                                               />
+                                               <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?i18n.t('login.btn_otp'):otpCD+"S"
+                                                               }
+                                                           </>
+                                                       )
+                                                   }
+                                               </button>
+                                           </div>
+                                       </div>
+                                   )
+                               }}
+                   />
+                   <button className={"button-green"}>
+                       {
+                           bindStatus===REQ_STATUS.LOADING?(<SmallLoading fill={"#fff"}></SmallLoading>):(
+                               i18n.t("bindPhone.btn_submit")
+                           )
+                       }
+                   </button>
+                   <span className={styles.tips}>{stringFormat(i18n.t("bindPhone.tips"),[regCoins])}</span>
+               </form>
+           </div>
+       </Popup>
+    )
+}
+export default WBindPhone;

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

@@ -0,0 +1,55 @@
+.container{
+  display: flex;
+  flex-direction: column;
+  width: 100%;
+  gap: 0.5rem;
+  pointer-events: auto;
+  & .title{
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+    justify-content: center;
+    color: var(--header);
+    font-weight: 600;
+    font-size: 1rem;
+  }
+  & .award_con{
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+    justify-content: flex-end;
+    color: var(--coin-yellow);
+    gap: 0.25rem;
+    img{
+      width: 1rem;
+      height: 1rem;
+    }
+    font-size: 1rem;
+  }
+  & .l_form{
+    display: flex;
+    flex-direction: column;
+    gap: 1rem;
+    & .otp_btn{
+      min-width: 3rem;
+      height: 2.5rem;
+      display: flex;
+      flex-direction: row;
+      align-items: center;
+      justify-content: flex-end;
+      color: var(--primary-color);
+      text-align: right;
+      font-size: 1rem;
+      &.cd_timer{
+        color:var(--text);
+      }
+    }
+    & .tips{
+      display: flex;
+      flex-direction: row;
+      align-items: center;
+      justify-content: center;
+      color: var(--coin-yellow);
+    }
+  }
+}

+ 3 - 1
src/widgets/WCashWheel/WHallWheel/WHallWheel.jsx

@@ -12,6 +12,7 @@ import {LOCAL_WHEEL_RETAIN} from "@constants/localkey";
 import {useSelector} from "react-redux";
 import {isLogined} from "@features/mainSlice";
 import {getTurnplate} from "@features/turnplateSlice";
+import SvgIcon from "@components/SvgIcon";
 
 const WHallWheel=({open,onClose})=>{
     const [openShare,setOpenShare]=useState(false);
@@ -73,7 +74,8 @@ const WHallWheel=({open,onClose})=>{
                             <Grow in={open} timeout={500}>
                                 <div className={styles.content}>
                                     <button className={styles.close} onClick={onCheckClose} aria-label="Close popup">
-                                        <i className="icon-xmark"/>
+                                        <SvgIcon herf={"icon_Close"}></SvgIcon>
+                                        {/*<i className="icon-xmark"/>*/}
                                     </button>
                                     <WWheel showShare={false} openShare={openShareBox} onClose={onClose}></WWheel>
                                 </div>

+ 5 - 0
src/widgets/WCashWheel/WHallWheel/styles.module.scss

@@ -25,6 +25,11 @@
       font-size: 1.4rem;
       color: var(--header);
     }
+    svg{
+      width: 1.4rem;
+      height: 1.4rem;
+      fill: var(--header);
+    }
   }
   @media screen and (min-width: $width-desktop-small-min) {
     max-width: 38rem;

+ 291 - 0
src/widgets/WGame/WDGame/WDGame.jsx

@@ -0,0 +1,291 @@
+//桌面游戏容器
+import Spring from "@components/Spring";
+import styles from "./styles.module.scss";
+import {LOCAL_GAME_INFO, LOCAL_GAME_URL, LOCAL_SIGN} from "@constants/localkey";
+import {localStorageGet, openFullScreen} from "@utils/helpers";
+import React, {useEffect, useState} from "react";
+import {useDispatch, useSelector} from "react-redux";
+import {getGameCardInfo, getGameLike, getRoute, isLogined, updateGameLike} from "@features/mainSlice";
+import BreadCrumb from "@layout/BreadCrumb";
+import {useThemeProvider} from "@contexts/themeContext";
+import {getHallConnState} from "@features/socketSlice";
+import Imgix from "@components/Imgix";
+import SmallLoading from "@components/SmallLoading";
+import SvgIcon from "@components/SvgIcon";
+import useGameIFrame from "@hooks/useGameIFrame";
+import MTooltip from "@components/MTooltip";
+import {updatePageGameLike} from "@features/pageSlice";
+import {reqGameCardInfo, reqUpdateFavoInfo} from "@features/api";
+import Popup from "@components/Popup";
+import LoadingIn from "@components/Loading/LoadingIn";
+const i18n=global.i18n;
+
+const WDGame=()=>{
+    // const {pathname}=useLocation();
+    const {preLocation,gameID} = useThemeProvider();
+    const gameid=gameID;
+    const gameCardInfo=useSelector(getGameCardInfo(gameid))
+    const [gameURL, setGameURL] = useState("");
+    const [gameInfo, setGameInfo] = useState({});
+    const prePathname=preLocation.pathname==null?"/casino":preLocation.pathname;
+    const preRoute=useSelector(getRoute(prePathname));
+    const isConnected=useSelector(getHallConnState);
+    const {isGameLoad,setIsGameLoad,iFrameRef}=useGameIFrame();
+    const [movieMode,setMovieMode]=useState(false);
+    const isLike=useSelector(getGameLike(gameID));
+    const dispatch = useDispatch();
+    const isLogin=useSelector(isLogined);
+    const [openShare,setOpenShare]=useState(false);
+    const shareData=[
+        {type:'fb',icon:'3'},
+        {type:'twitter',icon:'8'},
+        {type:'tg',icon:'7'},
+        {type:'vk',icon:'11'},
+        {type:'line',icon:'12'},
+        {type:'skype',icon:'13'},
+        {type:'ok',icon:'14'},
+        {type:'in',icon:'16'},
+        {type:'ws',icon:'17'},
+    ]
+    const onShare=(item)=>{
+        let type=item.type;
+        let title="";
+        if (gameInfo!=null)title=gameInfo.title;
+        let text="";
+        let link=window.location.href;
+        let url="";
+        switch(type){
+            case 'fb':
+                url=`https://www.facebook.com/sharer.php?title=${title}&text=${text}&u=${link}`;
+                break
+            case 'twitter':
+                url=`https://twitter.com/share?title=${title}&text=${text}&url=${link}`;
+                break
+            case 'tg':
+                url=`https://t.me/share?title=${title}&text=${text}&url=${link}`;
+                break
+            case 'vk':
+                url=`http://vk.com/share.php?url=${link}&title=${title}&text=${text}`;
+                break
+            case 'line':
+                url=`https://lineit.line.me/share/ui?url=${link}&title=${title}&text=${text}`;
+                break
+            case 'skype':
+                url=`https://web.skype.com/share?url=${link}&title=${title}&text=${text}`;
+                break
+            case 'ok':
+                url=`https://connect.ok.ru/offer?url=url=${link}&title=${title}`;
+                break
+            case 'in':
+                url=`https://www.linkedin.com/sharing/share-offsite/?url=${link}`;
+                break
+            case 'ws':
+                url=`https://api.whatsapp.com/send?url=${link}&title=${title}&text=${text}`;
+                break
+        }
+        window.open(url);
+    }
+    const parseURL=()=>{
+        let key=LOCAL_GAME_URL+gameid;
+        let sign=localStorageGet(LOCAL_SIGN);
+        // let url=localStorageGet(key);
+        let url=gameCardInfo.link;
+        if (gameCardInfo.level>0)url+="&level="+gameCardInfo.level;
+        if (url==null)return;
+        if (url.indexOf("?")===-1)url=url+"?sign="+sign;
+        else url=url+"&sign="+sign;
+        setGameURL(url);
+    }
+    const parseInfo=()=>{
+        if (gameid==="")return;
+        // let local=localStorageGet(LOCAL_GAME_INFO+gameid);
+        // if (local!=null&&local!==""){
+        //     let info=JSON.parse(local);
+        //     if (info!=null){
+        //         setGameInfo(info.data);
+        //     }
+        // }
+        setGameInfo(gameCardInfo);
+    }
+    const getRouteChain=()=>{
+        let chain=[];
+        if (preRoute!=null)chain.push(preRoute);
+        if (gameInfo!=null){
+            chain.push({
+                title:gameInfo.title,
+                path:""
+            })
+        }
+        return chain;
+    }
+    const onGameFullScreen=()=>{
+        const iframe = iFrameRef.current;
+        if (iframe) {
+            if (iframe.requestFullscreen) {
+                iframe.requestFullscreen();
+            } else if (iframe.mozRequestFullScreen) { /* Firefox */
+                iframe.mozRequestFullScreen();
+            } else if (iframe.webkitRequestFullscreen) { /* Chrome, Safari & Opera */
+                iframe.webkitRequestFullscreen();
+            } else if (iframe.msRequestFullscreen) { /* IE/Edge */
+                iframe.msRequestFullscreen();
+            }
+        }
+    }
+    const onChangeMovieMode=()=>{
+        setMovieMode(!movieMode);
+    }
+    const handleLike=()=>{
+        let info={id:gameID,data:gameInfo,like:(1-isLike)}
+        dispatch(updateGameLike(info));
+        dispatch(updatePageGameLike(info));
+        if(isLogin)dispatch(reqUpdateFavoInfo(info));
+    }
+    useEffect(()=>{
+        if (Number(gameid)>0&&gameCardInfo==null){
+            dispatch(reqGameCardInfo(gameid));
+        }
+        return ()=>{
+        }
+    },[gameid])
+    useEffect(()=>{
+        if (!isConnected){
+            setIsGameLoad(false);
+        }
+    },[isConnected])
+    useEffect(()=>{
+        if (gameCardInfo!=null){
+            parseURL();
+            parseInfo();
+        }
+    },gameCardInfo);
+    const renderTopNavBar=()=>{
+        let chain=getRouteChain();
+        let brand=gameInfo.brand?gameInfo.brand:"";
+        return (
+            <div className={styles.game_top_nav_wrap}>
+                <BreadCrumb customChain={chain}></BreadCrumb>
+                <div className={styles.brand_con}>
+                    {i18n.t("game.brand_by")}
+                    <span className={styles.brand}>{brand}</span>
+                </div>
+            </div>
+        )
+    }
+    const renderGame=()=>{
+        let title="game";
+        if (gameInfo&&gameInfo.title!=null){
+            title=gameInfo.title;
+        }
+        let play_num=gameInfo.play_num!=null?gameInfo.play_num:0;
+        return (
+            <div className={`${styles.game_area} ${movieMode?styles.movie_mode:''}`}>
+                <div className={`${styles.game_action_wrap} ${styles.game_action_wrap_top}`}></div>
+                <div className={styles.game_main}>
+                    <div className={styles.game_iframe_wrap}>
+                        {
+                            isConnected?(
+                                <iframe
+                                    ref={iFrameRef}
+                                    id={"game"}
+                                    title={title}
+                                    src={gameURL}
+                                    allow="fullscreen"
+                                    sandbox='allow-same-origin allow-scripts allow-popups allow-forms'
+                                >
+                                </iframe>
+                            ):(<></>)
+                        }
+                        {renderLoading()}
+                    </div>
+                </div>
+                <div className={`${styles.game_action_wrap} ${styles.game_action_wrap_bottom}`}>
+                    <div className={styles.action_item_con}>
+                        <button className={`${styles.action_item} ${isLike===1?styles.active_star:''}`} onClick={handleLike}>
+                            <SvgIcon className={styles.icon} herf={"icon_Favorites1"}></SvgIcon>
+                            {
+                                play_num>0?play_num:''
+                            }
+                        </button>
+                        <button className={styles.action_item} onClick={()=>{
+                            setOpenShare(true);
+                        }}>
+                            <SvgIcon className={styles.icon} herf={"icon_Send"}></SvgIcon>
+                        </button>
+                    </div>
+                    <div className={styles.action_item_con}>
+                        <MTooltip title={i18n.t('game.tips.movie_mode')} placement="top">
+                            <button className={`${styles.action_item} ${movieMode ? styles.active : ''}`}
+                                    onClick={onChangeMovieMode}>
+                                <SvgIcon className={styles.icon} herf={"icon_Movie"}></SvgIcon>
+                            </button>
+                        </MTooltip>
+                        <MTooltip title={i18n.t('game.tips.full_screen')} placement="top">
+                            <button className={`${styles.action_item}`} onClick={onGameFullScreen}>
+                                <SvgIcon className={styles.icon} herf={"icon_FullScreen"}></SvgIcon>
+                            </button>
+                        </MTooltip>
+                    </div>
+                </div>
+            </div>
+        )
+    }
+    const renderLoading = () => {
+        if (isGameLoad) return (<></>);
+        let imgsrc = gameInfo.img ? gameInfo.img:"";
+        return (
+            <div className={styles.loading_wrap}>
+                {
+                    imgsrc!==""?(
+                        <Imgix className={styles.loading_bg} src={imgsrc} width={1024} imgixParams={{blur:150}} />
+                    ):(<></>)
+                }
+                <div className={styles.loading}>
+                    <SmallLoading />
+                </div>
+            </div>
+        )
+    }
+    const renderShareBox=()=>{
+        return (
+            <Popup onClose={()=>{
+                setOpenShare(false);
+            }} open={openShare}>
+                <div className={styles.share_box}>
+                    <span className={styles.title}>
+                        {i18n.t("game.share_title")}
+                    </span>
+                    <div className={styles.content}>
+                        {
+                            shareData.map((item,index)=>{
+                                return (
+                                    <button key={index} className={styles.share_btn} onClick={()=>{
+                                        onShare(item);
+                                    }}>
+                                        <img src={require("@assets/share/share_"+item.icon+".png")}/>
+                                    </button>
+                                )
+                            })
+                        }
+                    </div>
+                </div>
+            </Popup>
+        )
+    }
+    return (
+        <>
+            {
+                gameCardInfo==null?<LoadingIn></LoadingIn>:(
+                    <Spring type={"slideUp"}>
+                        <div className={`${styles.container} page-max-width-wrap`}>
+                            {renderTopNavBar()}
+                            {renderGame()}
+                            {renderShareBox()}
+                        </div>
+                    </Spring>
+                )
+            }
+        </>
+    )
+}
+export default WDGame;

+ 184 - 0
src/widgets/WGame/WDGame/styles.module.scss

@@ -0,0 +1,184 @@
+.container{
+  display: flex;
+  flex-direction: column;
+  gap: 0.75rem;
+  margin-top:1.25rem;
+  margin-bottom: 2rem;
+}
+.game_top_nav_wrap{
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  width: 100%;
+  & .brand_con{
+    display: flex;
+    //font-size: 1rem;
+    color: var(--text);
+    gap: 0.25rem;
+    & .brand{
+      color: var(--header);
+    }
+  }
+}
+.game_area{
+  display: flex;
+  flex-direction: column;
+  //width: 100%;
+  transition: all .3s;
+  &.movie_mode{
+    margin-left: -1.875rem;
+    margin-right: -1.875rem;
+  }
+}
+.game_action_wrap{
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 0 .625rem;
+  height: 3.375rem;
+  background-color: var(--body);
+  &_top{
+    border-bottom: 1px solid rgba(105,112,122,.5);
+  }
+  &_bottom{
+    border-top: 1px solid rgba(105,112,122,.5);
+  }
+  & .action_item_con{
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    gap: 0.25rem;
+  }
+  & .action_item{
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    gap: 0.25rem;
+    height: 2.75rem;
+    min-width: 2.75rem;
+    color: var(--text);
+    font-weight: 500;
+    border-radius: var(--border-radius);
+    background-color: transparent;
+    padding: 0 .5rem;
+    & .icon{
+      width: 1.4em;
+      height: 1.4em;
+      fill: var(--text);
+      transition: all .3s ease-in-out;
+    }
+    &:hover{
+      background-color: var(--nav-item-hover) !important;
+      color: var(--header);
+      & .icon{
+        transform: scale(1.15);
+        fill: var(--header);
+      }
+    }
+    &.active{
+      & .icon{
+        fill: var(--primary-color);
+      }
+    }
+    &.active_star{
+      & .icon{
+        fill:#ffdf46;
+      }
+    }
+  }
+}
+.game_main{
+  position: relative;
+  width: 100%;
+  aspect-ratio: 1624/750;
+  background-color: var(--body);
+}
+.game_iframe_wrap{
+  position:relative;
+  width: 100%;
+  height: 100%;
+  background-color: #000000;
+  iframe{
+    position: absolute;
+    left: 0;
+    top: 0;
+    width: 100%;
+    height: 100%;
+    border: none;
+    z-index: 1;
+  }
+  & .loading_wrap{
+    position: absolute;
+    height: 100%;
+    width: 100%;
+    left: 0;
+    top: 0;
+    text-align: center;
+    font-size: 0;
+    padding: 0;
+    margin: 0;
+    overflow: hidden;
+    z-index: 2;
+    & .loading_bg{
+      position: absolute;
+      z-index: 1;
+      width: 100%;
+      height: auto;
+      animation: imgscale 5s linear infinite;
+    }
+    & .loading{
+      position: absolute;
+      z-index: 2;
+      inset: 0;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+    }
+  }
+}
+@keyframes imgscale{
+  0% {
+    transform: scale(1);
+    opacity: .75
+  }
+
+  50% {
+    transform: scale(1.05);
+    opacity: 1
+  }
+
+  to {
+    transform: scale(1);
+    opacity: .75
+  }
+}
+.share_box{
+  width: 100%;
+  height: 100%;
+  & .title{
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    font-size: 1.125rem;
+    color: var(--header);
+    text-align: center;
+    font-weight: 600;
+  }
+  & .content{
+    padding: 2rem 0rem;
+    display: flex;
+    //flex-direction: column;
+    gap: 1rem;
+    flex-wrap: wrap;
+    align-items: center;
+    justify-content: center;
+    & .share_btn{
+      width: 2.5rem;
+      height: 2.5rem;
+      img{
+        width: 100%;
+        height: 100%;
+      }
+    }
+  }
+}

+ 40 - 55
src/widgets/WGame/index.jsx

@@ -12,81 +12,54 @@ import {ReactComponent as Circlearrow} from '@assets/singlesvg/back/circlearrow.
 import LoadingScreen from "@components/LoadingScreen";
 import {ROUTE_PATH} from "@constants/routes";
 import {useDispatch, useSelector} from "react-redux";
-import {reqGameInfo} from "@features/api";
+import {reqGameCardInfo, reqGameInfo} from "@features/api";
 import {getHallConnState} from "@features/socketSlice";
-import Draggable from 'react-draggable'; // The default
+import Draggable from 'react-draggable';
+import useGameIFrame from "@hooks/useGameIFrame";
+import {getGameCardInfo} from "@features/mainSlice";
+import game from "@pages/Game";
+import {useThemeProvider} from "@contexts/themeContext"; // The default
 const i18n=global.i18n;
 
 const WGame=()=>{
     const dispatch = useDispatch();
     const {pathname}=useLocation();
-    const gameid=pathname.split("/").pop();
+    const {gameID} = useThemeProvider();
+    // const gameid=pathname.split("/").pop();
+    const gameid=gameID;
     const [openBox,setOpenBox]=useState(false);
     const [openPrompt,setOpenPrompt]=useState(false);
-    const [isGameLoad,setIsGameLoad]=useState(false);
+    // const [isGameLoad,setIsGameLoad]=useState(false);
     const isConnected=useSelector(getHallConnState);
-    var gameURL="";
+    const [gameURL, setGameURL] = useState("");
     const navigate=useNavigate();
     const backBoxRef = useRef(null);
     const [dragDeltaY,setDragDeltaY]=useState(0);
+    const {isGameLoad,setIsGameLoad,iFrameRef}=useGameIFrame();
+    const gameCardInfo=useSelector(getGameCardInfo(gameid));
     const onBack=()=>{
         // dispatch(reqGameInfo());
         navigate(ROUTE_PATH.BACK);
     }
-    const receiveMessage=(event)=>{
-        let data=event.data;
-        // console.log(data);
-        if (data&&data.cmd!=null){
-            // toast.success(JSON.stringify(data))
-            if (data.cmd==="back"){
-                onBack();
-            }
-            else if (data.cmd==="pay"){
-                navigate(ROUTE_PATH.PAY);
-            }
-            else if (data.cmd==="withdraw"){
-                navigate(ROUTE_PATH.SAQUE);
-            }
-            else if (data.cmd==="fullScreen"){
-                openFullScreen();
-            }
-            else if (data.cmd==="closeLoading"){
-                setIsGameLoad(true);
-            }
-        }
-    }
     const parseURL=()=>{
         let key=LOCAL_GAME_URL+gameid;
         let sign=localStorageGet(LOCAL_SIGN);
-        let url=localStorageGet(key);
+        // let url=localStorageGet(key);
+        let url=gameCardInfo.link;
+        if (gameCardInfo.level>0)url+="&level="+gameCardInfo.level;
         if (url==null)return;
         if (url.indexOf("?")===-1)url=url+"?sign="+sign;
         else url=url+"&sign="+sign;
-        // setGameURL(url);
-        gameURL=url;
-    }
-    parseURL();
-    const handleLoadGame=()=>{
-        setIsGameLoad(true);
-    }
-    const initGameEvent=()=>{
-        const gameFrame = document.getElementById('game');
-        if(gameFrame!=null){
-            gameFrame.addEventListener('load', handleLoadGame);
-        }
-        window.addEventListener("message", receiveMessage, false);
-    }
-    const removeGameEvent=()=>{
-        const gameFrame = document.getElementById('game');
-        if(gameFrame!=null){
-            gameFrame.removeEventListener('load', handleLoadGame);
-        }
-        window.removeEventListener("message", receiveMessage);
+        setGameURL(url);
+        // gameURL=url;
     }
+
     useEffect(() => {
-        initGameEvent();
+        if (Number(gameid)>0&&gameCardInfo==null){
+            dispatch(reqGameCardInfo(gameid));
+        }
         return () => {
-            removeGameEvent();
+
         };
     }, []);
     useEffect(()=>{
@@ -94,6 +67,11 @@ const WGame=()=>{
             setIsGameLoad(false);
         }
     },[isConnected])
+    useEffect(()=>{
+        if (gameCardInfo!=null){
+            parseURL();
+        }
+    },[gameCardInfo])
     const renderBackBox=()=>{
         return (
             <Draggable
@@ -140,6 +118,7 @@ const WGame=()=>{
     const renderGame = () => {
         return (
             <iframe
+                ref={iFrameRef}
                 id={"game"}
                 title={"game"}
                 src={gameURL}
@@ -172,14 +151,20 @@ const WGame=()=>{
     return (
         <div className={styles.container}>
             {
-                isConnected?(
+                gameCardInfo==null?renderLoading():(
                     <>
-                        {renderGame()}
-                        {renderBackBox()}
+                        {
+                            isConnected?(
+                                <>
+                                    {renderGame()}
+                                    {renderBackBox()}
+                                </>
+                            ):(<></>)
+                        }
+                        {renderLoading()}
                     </>
-                ):(<></>)
+                )
             }
-            {renderLoading()}
         </div>
     )
 }

+ 159 - 0
src/widgets/WInstallBox/index.jsx

@@ -0,0 +1,159 @@
+import {useEffect, useRef, useState} from "react";
+import {useThemeProvider} from "@contexts/themeContext";
+import MuiDrawer from "@mui/material/Drawer";
+import styles from "./styles.module.scss";
+import {
+    isInAppBrowser,
+    openProgressierInstall,
+    openUrlUseChrome
+} from "@utils/UAUtils";
+import Fade from "@mui/material/Fade";
+import Grow from "@mui/material/Grow";
+import Modal from "@mui/material/Modal";
+import Http from "@api/request";
+const i18n=global.i18n;
+const objInfo={
+    count:0,
+    timer:null
+}
+const WInstallBox = () => {
+    const {isMobile} = useThemeProvider();
+    const [isShow, setIsShow] = useState(false);
+    const installRef = useRef(null);
+    const [iconUrl, setIconUrl] = useState("");
+    const show=()=>{
+        setIsShow(true);
+    }
+    const hide=()=>{
+        setIsShow(false);
+    }
+    const clearTimer=()=>{
+        if (objInfo.timer!=null){
+            clearInterval(objInfo.timer);
+            objInfo.timer=null;
+            objInfo.count=0;
+        }
+    }
+    const onInstall=()=>{
+        if (isInAppBrowser()){
+            openUrlUseChrome();
+        }
+        else
+        {
+            openProgressierInstall();
+            setIsShow(false);
+        }
+    }
+    const onCancel=()=>{
+        let apkUrl=Http.getAPKUrl();
+        window.open(apkUrl);
+        setIsShow(false);
+    }
+    useEffect(()=>{
+        const installBox={
+            show,
+            hide
+        };
+        global.installBox=installBox;
+        if (window.progressier!=null){
+            let iconUrl="";
+            let initIconUrl=()=>{
+                if (window.progressier.data!=null){
+                    let params=window.progressier.data.params;
+                    if (params!=null){
+                        // setIconUrl(params.icon180);
+                        iconUrl=params.icon180;
+                    }
+                }
+            }
+            clearTimer();
+            if (iconUrl==null||iconUrl===""){
+                objInfo.timer=setInterval(()=>{
+                    if (objInfo.count>=10){
+                        clearTimer();
+                        return;
+                    }
+                    initIconUrl();
+                    if (iconUrl!=null&&iconUrl!==""){
+                        setIconUrl(iconUrl);
+                        clearTimer();
+                    }
+                    objInfo.count++;
+                },200);
+            }
+            else
+            {
+                setIconUrl(iconUrl);
+            }
+        }
+        return ()=>{
+            clearTimer();
+        }
+    },[])
+    const renderInstall=()=>{
+        return (
+            <div className={styles.continer}>
+                <div className={styles.install_con}>
+                    <img src={iconUrl}/>
+                    <span>{i18n.t("installBox.tips")}</span>
+                </div>
+                <div className={styles.install_btn_con}>
+                    <button className={styles.install_btn} onClick={onCancel}>
+                        {i18n.t("installBox.btn_cancel")}
+                    </button>
+                    <button className={`${styles.install_btn} ${styles.btn_green}`} onClick={onInstall}>
+                        {i18n.t("installBox.btn_install")}
+                    </button>
+                </div>
+            </div>
+        )
+    }
+    const renderMobile = () => {
+        return (
+            <MuiDrawer open={isShow}
+                       anchor={"bottom"}
+                       sx={{
+                           pointerEvents: 'auto',
+                           zIndex: 'var(--zindex-install)',
+                           "& .MuiDrawer-paper": {
+                               boxSizing: "border-box",
+                               overflow: 'visible !important',
+                               backgroundColor: '#fff;',
+                               boxShadow: 'var(--widget-shadow);',
+                               borderTopLeftRadius: '0.71rem',
+                               borderTopRightRadius: '0.71rem',
+                               padding: '1.42rem'
+                           },
+                       }}
+                       variant="temporary"
+                       onClose={()=>{
+                           // setIsShow(false)
+                       }}>
+                {renderInstall()}
+            </MuiDrawer>
+        )
+    }
+    const renderDesktop = () => {
+        return (
+            <Modal open={isShow} onClose={()=>{
+
+            }} closeAfterTransition>
+                <Fade in={isShow} timeout={500}>
+                    <div className="popup-container">
+                        <Grow in={isShow} timeout={500}>
+                            <div className={styles.pop_con} style={{minHeight: 'unset'}}>
+                                {renderInstall()}
+                            </div>
+                        </Grow>
+                    </div>
+                </Fade>
+            </Modal>
+        )
+    }
+    return (
+        <>
+        {isMobile()?renderMobile():renderDesktop()}
+        </>
+    )
+}
+export default WInstallBox;

+ 49 - 0
src/widgets/WInstallBox/styles.module.scss

@@ -0,0 +1,49 @@
+.continer{
+  display: flex;
+  flex-direction: column;
+  width: 100%;
+  & .install_con{
+    display: flex;
+    flex-direction: row;
+    gap: 0.8rem;
+    font-size: 0.99rem;
+    line-height: 1.42rem;
+    color: var(--text-light);
+    img{
+      width: 4.26rem;
+      height: 4.26rem;
+      border-radius: 0.71rem;
+    }
+  }
+  & .install_btn_con{
+    display: flex;
+    flex-direction: row;
+    justify-content: flex-end;
+    padding: 0.25rem;
+    font-size: 0.85rem;
+    gap: 0.25rem;
+    & .install_btn{
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: center;
+      padding: 0.5rem;
+      color: var(--text-light);
+      border-radius: 0.42rem;
+      min-width: 5.11rem;
+      font-weight: 600;
+    }
+    & .btn_green{
+      border: 2px solid #59c055;
+      color: #59c055;
+    }
+  }
+}
+.pop_con{
+  display: flex;
+  padding: 1.42rem;
+  border-radius: 0.71rem;
+  background-color: #fff;
+  max-width: 25.875rem;
+  pointer-events: auto;
+}

+ 2 - 2
src/widgets/WWithdraw/AccountEdit.jsx

@@ -1,9 +1,9 @@
 import styles from './styles.module.scss';
 import BRAccountEdit from "@widgets/WWithdraw/BR/BRAccountEdit";
-const AccountEdit=({close})=>{
+const AccountEdit=({...props})=>{
     return (
         <div className={styles.container}>
-            <BRAccountEdit/>
+            <BRAccountEdit {...props}/>
         </div>
     )
 }

+ 1 - 0
src/widgets/WWithdraw/BR/BRAccountEdit.jsx

@@ -57,6 +57,7 @@ const BRAccountEdit=({close})=>{
     const callback=(data)=>{
         if (data&&data.code===200){
             // dispatch(reqWithdrawBaseInfo(callbackBase));
+            close();
         }
     }
     const handleChange=(data)=>{

+ 31 - 8
src/widgets/WWithdraw/BR/BRWithdraw.jsx

@@ -3,7 +3,7 @@ import styles from "./styles.module.scss";
 import React, {useState,useEffect} from "react";
 import {useDispatch, useSelector} from "react-redux";
 import {getUserInfo} from "@features/mainSlice";
-import {numFormatter, stringFormat} from "@utils/helpers";
+import {formatCash, numFormatter, stringFormat} from "@utils/helpers";
 import {getWDPayoutStatus, getWithdrawBaseInfo} from "@features/paySlice";
 import symbol from "@assets/symbol-defs.svg";
 import {ReactComponent as Edit} from '@assets/singlesvg/edit.svg';
@@ -27,9 +27,10 @@ const BRWithdraw=()=>{
     const dispatch = useDispatch();
     const status=useSelector(getWDPayoutStatus);
     useEffect(()=>{
-       if (info==null){
-           dispatch(reqWithdrawBaseInfo())
-       }
+       // if (info==null){
+       //     dispatch(reqWithdrawBaseInfo())
+       // }
+        dispatch(reqWithdrawBaseInfo())
     },[])
     const checkBank=()=>{
         if (info!=null){
@@ -51,6 +52,27 @@ const BRWithdraw=()=>{
             toast.error(i18n.t("withdraw.main.unbound_account"))
             return;
         }
+        let inputNum = Number(wdValue)*global.CASH_BASE;
+        let lnum = info.OrderLimit - info.OrderCount;
+        // data.WithDrawMin, data.WithDrawMax
+        let min=info.MinExchange;
+        let max=info.MaxExchange;
+        let maxEx=Number(info.SumCanExchange);
+        if (inputNum<min){
+            toast.error(stringFormat(i18n.t("withdraw.tip.under_can_score"),[formatCash(min,0)]))
+            return;
+        }
+        if (inputNum>max){
+            toast.error(stringFormat(i18n.t("withdraw.tip.over_can_score"),[formatCash(max,0)]))
+            return;
+        }
+        if (inputNum>maxEx){
+            toast.error(i18n.t("withdraw.tip.total_not_enough"))
+            return;
+        }
+        if (lnum<=0){
+            return;
+        }
         let num=Number(wdValue);
         if (num>0){
             dispatch(reqWithdrawPayout({
@@ -110,7 +132,7 @@ const BRWithdraw=()=>{
                                         onClick={()=>{
                                             let value=item===-1?getShowCash(info.SumCanExchange)+"":item+"";
                                             let fvalue=formatInput(value);
-                                            let addIndx=value===fvalue?index:-1;
+                                            let addIndx=Number(value)===fvalue?index:-1;
                                             if (addIndx!==-1)setWdValue(fvalue);
                                             setAddIndex(addIndx);
                                         }}
@@ -128,9 +150,10 @@ const BRWithdraw=()=>{
                         type={"number"}
                         placeholder={i18n.t("withdraw.main.editPlaceholder")}
                         onChange={(e)=>{
-                            let value=formatInput(e.target.value);
-                            setWdValue(value);
-                            setAddIndex(getAddIndexFromValue(value));
+                            // let value=formatInput(e.target.value);
+                            // setWdValue(value);
+                            // setAddIndex(getAddIndexFromValue(value));
+                            setWdValue(e.target.value);
                         }}
                     />
                     {

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä