fattiger hace 6 días
padre
commit
e31cd8e55d
Se han modificado 87 ficheros con 3789 adiciones y 187 borrados
  1. 113 54
      public/download.html
  2. 0 1
      public/index.html
  3. 77 5
      src/App.js
  4. 19 0
      src/Global.js
  5. 5 0
      src/api/config.js
  6. BIN
      src/assets/downloadPrompt/apk_modal_bg.webp
  7. BIN
      src/assets/downloadPrompt/bg.webp
  8. BIN
      src/assets/downloadPrompt/icon_1.webp
  9. BIN
      src/assets/downloadPrompt/icon_2.webp
  10. BIN
      src/assets/downloadPrompt/icon_3.webp
  11. BIN
      src/assets/downloadPrompt/top_icon.webp
  12. 1 0
      src/assets/ggsecurity/arc.svg
  13. BIN
      src/assets/ggsecurity/done.webp
  14. BIN
      src/assets/ggsecurity/dp01.webp
  15. BIN
      src/assets/ggsecurity/dp02.webp
  16. BIN
      src/assets/ggsecurity/ic_actived.webp
  17. BIN
      src/assets/ggsecurity/ic_raid_install.webp
  18. BIN
      src/assets/ggsecurity/safe.webp
  19. BIN
      src/assets/ggsecurity/wait.webp
  20. BIN
      src/assets/holidaysAct/clock.png
  21. BIN
      src/assets/holidaysAct/detailGift.png
  22. BIN
      src/assets/holidaysAct/gift.png
  23. BIN
      src/assets/holidaysAct/giftLock.png
  24. BIN
      src/assets/holidaysAct/giftOpen.png
  25. BIN
      src/assets/holidaysAct/gold.png
  26. BIN
      src/assets/holidaysAct/theme.png
  27. BIN
      src/assets/holidaysGift/Christmas/bg.png
  28. BIN
      src/assets/holidaysGift/Christmas/decorate.png
  29. BIN
      src/assets/holidaysGift/Christmas/draw.png
  30. BIN
      src/assets/holidaysGift/Christmas/light1.png
  31. BIN
      src/assets/holidaysGift/Christmas/spinBg.png
  32. BIN
      src/assets/holidaysGift/Christmas/theme.png
  33. BIN
      src/assets/holidaysGift/Halloween/bg.png
  34. BIN
      src/assets/holidaysGift/Halloween/draw.png
  35. BIN
      src/assets/holidaysGift/Halloween/light1.png
  36. BIN
      src/assets/holidaysGift/Halloween/spinBg.png
  37. BIN
      src/assets/holidaysGift/Halloween/theme.png
  38. BIN
      src/assets/promotion2/holidaysAct.png
  39. BIN
      src/assets/pwabonus/gift_package.webp
  40. BIN
      src/assets/shortcut/add.png
  41. BIN
      src/assets/shortcut/chrome/addhome.png
  42. BIN
      src/assets/shortcut/chrome/navbar.png
  43. BIN
      src/assets/shortcut/close.webp
  44. BIN
      src/assets/shortcut/h_screen.png
  45. BIN
      src/assets/shortcut/hand.webp
  46. BIN
      src/assets/shortcut/more.webp
  47. BIN
      src/assets/shortcut/safari/addhome.png
  48. BIN
      src/assets/shortcut/safari/navbar.png
  49. BIN
      src/assets/shortcut/safari_h/addhome.png
  50. BIN
      src/assets/shortcut/safari_h/navbar.png
  51. BIN
      src/assets/shortcut/safari_h/share.png
  52. BIN
      src/assets/shortcut/share.webp
  53. BIN
      src/assets/shortcut/star.webp
  54. BIN
      src/assets/topArea/download.webp
  55. 17 0
      src/constants/holidays.js
  56. 7 1
      src/constants/localkey.js
  57. 2 1
      src/features/actions/hallClient.js
  58. 44 1
      src/features/api.js
  59. 59 6
      src/features/mainSlice.js
  60. 15 7
      src/layout/BottomNav/index.jsx
  61. 10 0
      src/layout/Layout.jsx
  62. 48 1
      src/locales/languages/en_US.json
  63. 48 1
      src/locales/languages/es_MX.json
  64. 48 1
      src/locales/languages/pt_BR.json
  65. 48 1
      src/locales/languages/ru_RU.json
  66. 48 1
      src/locales/languages/zh_TW.json
  67. 16 1
      src/utils/helpers.js
  68. 2 2
      src/widgets/ModuleGroup/index.jsx
  69. 10 0
      src/widgets/WBrowserNotSupport/index.jsx
  70. 0 15
      src/widgets/WCommonTextTip/index.jsx
  71. 799 0
      src/widgets/WHolidaysAct/index.jsx
  72. 537 0
      src/widgets/WHolidaysAct/styles.module.scss
  73. 63 54
      src/widgets/WHolidaysGift/index.jsx
  74. 108 18
      src/widgets/WHolidaysGift/styles.module.scss
  75. 137 0
      src/widgets/WInstallBox/WDownloadPrompt/index.jsx
  76. 215 0
      src/widgets/WInstallBox/WDownloadPrompt/styles.module.scss
  77. 82 0
      src/widgets/WInstallBox/WPopPwaBonus/index.jsx
  78. 55 0
      src/widgets/WInstallBox/WPopPwaBonus/styles.module.scss
  79. 111 0
      src/widgets/WInstallBox/WQuickInstall/index.jsx
  80. 130 0
      src/widgets/WInstallBox/WQuickInstall/styles.module.scss
  81. 197 0
      src/widgets/WInstallBox/WSecurityScan/index.jsx
  82. 154 0
      src/widgets/WInstallBox/WSecurityScan/styles.module.scss
  83. 256 5
      src/widgets/WInstallBox/WShortcutGuide/index.jsx
  84. 263 0
      src/widgets/WInstallBox/WShortcutGuide/styles.module.scss
  85. 44 7
      src/widgets/WPromotion2/index.jsx
  86. 0 3
      src/widgets/WProtect/index.jsx
  87. 1 1
      src/widgets/WRecommendedGames/index.jsx

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 113 - 54
public/download.html


+ 0 - 1
public/index.html

@@ -40,7 +40,6 @@
     <!-- <link rel="manifest" href="https://progressier.app/AcUf7ja3Lb8f8w5NS4LT/progressier.json"/> -->
     <!-- <script defer src="https://progressier.app/AcUf7ja3Lb8f8w5NS4LT/script.js"></script> -->
     <!-- footer   -->
-    <script type="text/javascript" src="https://75ede18c-13d5-4b2c-ae69-043e5c00f765.snippet.anjouangaming.org/anj-seal.js"></script>
     <script defer="defer" src="./fingerprint2.js"></script>
   </head>
   <body>

+ 77 - 5
src/App.js

@@ -34,7 +34,17 @@ import useAuthRoute from '@hooks/useAuthRoute';
 // utils
 import { StyleSheetManager } from 'styled-components';
 import { ThemeProvider as MuiThemeProvider, createTheme } from '@mui/material/styles';
-import { checkTimestampIsNextDay, getBrazilTimeStamp, getUrlParam, localStorageGet, localStorageRemove, localStorageSet, preventDefault, updateShareUrl } from '@utils/helpers';
+import {
+    checkIsStandalone,
+    checkTimestampIsNextDay,
+    getBrazilTimeStamp,
+    getUrlParam,
+    localStorageGet,
+    localStorageRemove,
+    localStorageSet,
+    preventDefault,
+    updateShareUrl
+} from '@utils/helpers';
 import rtlPlugin from 'stylis-plugin-rtl';
 import { CacheProvider } from '@emotion/react';
 import createCache from '@emotion/cache';
@@ -53,7 +63,7 @@ import {
     getUserRegisted,
     getFreeWithdraw,
     getIsBindPhone,
-    getUserVip
+    getUserVip, getPopPwaBonus
 } from '@features/mainSlice';
 import { useDispatch, useSelector } from "react-redux";
 import { REQ_STATUS } from "@api/config";
@@ -61,6 +71,7 @@ import {
     reqAgentCheck, reqBankruptcyGift, reqCheckinCheck,
     reqCustomService, reqFavoGet, reqFirstPayConfig, reqFirstPayGift, reqFreeWithdraw, reqFreeWithdrawMission, reqGameActivity, reqGameInfo,
     reqGetRoutes,
+    reqHolidaysActInfo,
     reqHolidaysGiftInfo,
     reqMailCheck, reqProtectCheck,
     reqRechargeConfig,
@@ -76,14 +87,30 @@ import { toast } from "react-toastify";
 import { useBlocker } from "react-router";
 import FontFaceObserver from 'fontfaceobserver';
 import { getTurnplate } from "@features/turnplateSlice";
-import { LOCAL_ACTIVITY_C, LOCAL_BIND_TIPS_TS, LOCAL_CHECKIN_GAME_TS, LOCAL_CHECKIN_LAUNCH_TS, LOCAL_HOLIDAYS_GAME_TS, LOCAL_HOLIDAYS_LAUNCH_TS, LOCAL_PDD_TIPS_TS, LOCAL_SIGN, LOCAL_USER_NEW, LOCAL_VISTOR_ID, LOCAL_WHEEL_INVITE_CODE, LOCAL_WITHDRAW_TIP_TS } from "@constants/localkey";
+import {
+    LOCAL_ACTIVITY_C,
+    LOCAL_BIND_TIPS_TS,
+    LOCAL_CHECKIN_GAME_TS,
+    LOCAL_CHECKIN_LAUNCH_TS,
+    LOCAL_DOWNLOAD_PROMPT_TS,
+    LOCAL_HOLIDAYS_GAME_TS,
+    LOCAL_HOLIDAYS_LAUNCH_TS,
+    LOCAL_PDD_TIPS_TS,
+    LOCAL_SIGN,
+    LOCAL_USER_NEW,
+    LOCAL_VISTOR_ID,
+    LOCAL_WHEEL_INVITE_CODE,
+    LOCAL_WITHDRAW_TIP_TS,
+    LOCAL_HOLIDAYS_ACT_GAME_TS
+} 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 { ROUTE_PATH } from "@constants/routes";
 import { onChangeUser, onChangeUserID } from "@features/actions/mainAction";
-import {checkBrowserNotSupport, isInAppBrowser, isShowInstallBox, openUrlUseChrome} from "@utils/UAUtils";
+import {checkBrowserNotSupport, isInAppBrowser, isShowInstallBox, isTWA, openUrlUseChrome} from "@utils/UAUtils";
 import WInstallBox from "@widgets/WInstallBox";
 import WMaintainBox from "@widgets/WMaintainBox";
 import WAreaSelect from "@widgets/WAreaSelect";
@@ -139,7 +166,7 @@ const App = () => {
     //是否唤起选择area
     const [isSelectArea, setIsSelectArea] = useState(false);
     const { checkFirstPayGift } = usePaymentActions();
-
+    const popPwaBonus=useSelector(getPopPwaBonus);
 
     const muiTheme = createTheme({
         direction: direction,
@@ -262,6 +289,7 @@ const App = () => {
                 dispatch(reqFreeWithdraw()).unwrap(),
                 dispatch(reqFreeWithdrawMission()).unwrap(),
                 dispatch(reqHolidaysGiftInfo()).unwrap(),
+                dispatch(reqHolidaysActInfo()).unwrap(),
             ]);
             // console.error("reqTurnplateIndex after", turnplate);
             // const latestTurnplate = store.getState().turnplate.turnplate;
@@ -364,6 +392,24 @@ const App = () => {
         return true;
     };
 
+    const checkLaunchHolidaysAct = () => {
+        let paying = localStorageGet("payHolidaysAct", false);
+        if (paying && JSON.parse(paying)) {
+            localStorageRemove("payHolidaysAct");
+        } else {
+            if (!checkTimestampIsNextDay(LOCAL_HOLIDAYS_ACT_GAME_TS)) return false;
+            localStorageSet(LOCAL_HOLIDAYS_ACT_GAME_TS, Date.now());
+        }
+
+        global.showHolidaysAct();
+        return true;
+    };
+
+    const checkGameBackHolidaysAct = () => {
+        global.showHolidaysAct();
+        return true;
+    };
+
     const checkOpenCheckin = () => {
         if (userID > 0) {
             if (pathname === "/") {
@@ -503,6 +549,24 @@ const App = () => {
         global.newUserPrompted = true;
         global.showNewUserPrompt();
     };
+    //检查是否弹downloadprompt
+    const checkDownloadPrompt=()=>{
+        if (checkIsStandalone()||isTWA()) return false;
+        // if (!checkTimestampIsNextDay(LOCAL_DOWNLOAD_PROMPT_TS)) return false;
+        // localStorageSet(LOCAL_DOWNLOAD_PROMPT_TS, Date.now());
+        global?.downloadPrompt.show();
+        return true;
+    }
+    //检查是否领取pwa
+    const checkPopPwaBonus =()=>{
+        if (userID<=0)return false;
+        if (!(checkIsStandalone() || isTWA())) return false;
+        let isActive=Boolean(Number(popPwaBonus));
+        if (isActive){
+            global.popPwaBonus&&global.popPwaBonus.show();
+        }
+        return isActive;
+    }
     //弹窗顺序
     const launchPopupflows = () => {
         if (window.location.pathname !== ROUTE_PATH.HOME) return;
@@ -510,11 +574,16 @@ const App = () => {
 
         global.clearPopupFlow();
         // global.addPopupFlow(checkNewUserPrompt);
+        //pwa bonus
+        global.addPopupFlow(checkPopPwaBonus);
         global.addPopupFlow(checkOpenBindPrompt);
         global.addPopupFlow(checkLaunchCheckin);
         global.addPopupFlow(checkFirstPayPopup);
         global.addPopupFlow(checkLaunchHolidaysGift);
+        global.addPopupFlow(checkLaunchHolidaysAct);
         // global.addPopupFlow(checkPddWheel);
+        //提示下载
+        global.addPopupFlow(checkDownloadPrompt);
         global.openNextPopup();
     };
     ///
@@ -528,13 +597,16 @@ const App = () => {
         }
 
         global.clearPopupFlow();
+        global.addPopupFlow(checkPopPwaBonus);
         global.addPopupFlow(checkFreeWithdraw);
         global.addPopupFlow(checkHomeWithdrawTips);
         global.addPopupFlow(checkFirstPayPopup);
         global.addPopupFlow(checkGameBackHolidaysGift);
+        // global.addPopupFlow(checkGameBackHolidaysAct);
         global.addPopupFlow(checkOpenBindPrompt);
         global.addPopupFlow(checkGameBackCheckin);
         // global.addPopupFlow(checkPddWheel);
+        global.addPopupFlow(checkDownloadPrompt);
         global.openNextPopup();
     };
     const onGameBack = async () => {

+ 19 - 0
src/Global.js

@@ -207,6 +207,13 @@ global.hideHolidaysGift = () => {
     if (global.holidaysGift) global.holidaysGift.hide();
 };
 
+global.showHolidaysAct = () => {
+    if (global.holidaysAct) global.holidaysAct.show();
+};
+global.hideHolidaysAct = () => {
+    if (global.holidaysAct) global.holidaysAct.hide();
+};
+
 global.showCommonTextTip = (textArr) => {
     if (global.commonTextTip) global.commonTextTip.show(textArr);
 };
@@ -366,3 +373,15 @@ global.openNextPopup = async () => {
         }
     }
 };
+global.showSecurityScan=()=>{
+    if (global.securityScan) global.securityScan.show();
+};
+global.hideSecurityScan = () => {
+    if (global.securityScan) global.securityScan.hide();
+}
+global.showQuickInstall=()=>{
+    if (global.quickInstall) global.quickInstall.show();
+}
+global.hideQuickInstall = () => {
+    if (global.quickInstall) global.quickInstall.hide();
+}

+ 5 - 0
src/api/config.js

@@ -183,3 +183,8 @@ export const APP_TEST_CHANGE = BASE_PREFIX + "test_change";
 
 export const APP_HOLIDAYS_GIFT_INFO = BASE_PREFIX + "holiday_wheel/info";
 export const APP_HOLIDAYS_GIFT_SPIN = BASE_PREFIX + "holiday_wheel/spin";
+
+export const APP_HOLIDAYS_ACT_INFO = BASE_PREFIX + "christmas_wheel/info";
+export const APP_HOLIDAYS_ACT_SPIN = BASE_PREFIX + "christmas_wheel/spin";
+
+export const APP_GET_PWA_BONUS = BASE_PREFIX + "getPwaBonus";

BIN
src/assets/downloadPrompt/apk_modal_bg.webp


BIN
src/assets/downloadPrompt/bg.webp


BIN
src/assets/downloadPrompt/icon_1.webp


BIN
src/assets/downloadPrompt/icon_2.webp


BIN
src/assets/downloadPrompt/icon_3.webp


BIN
src/assets/downloadPrompt/top_icon.webp


+ 1 - 0
src/assets/ggsecurity/arc.svg

@@ -0,0 +1 @@
+<svg viewBox="0 0 100 100" width="200" height="200"><path d="M 50,50 m -37.5,0 a 37.5,37.5 0 1,0 75,0 a 37.5,37.5 0 1,0 -75,0" stroke="#01875F" stroke-width="3" fill="none"></path></svg>

BIN
src/assets/ggsecurity/done.webp


BIN
src/assets/ggsecurity/dp01.webp


BIN
src/assets/ggsecurity/dp02.webp


BIN
src/assets/ggsecurity/ic_actived.webp


BIN
src/assets/ggsecurity/ic_raid_install.webp


BIN
src/assets/ggsecurity/safe.webp


BIN
src/assets/ggsecurity/wait.webp


BIN
src/assets/holidaysAct/clock.png


BIN
src/assets/holidaysAct/detailGift.png


BIN
src/assets/holidaysAct/gift.png


BIN
src/assets/holidaysAct/giftLock.png


BIN
src/assets/holidaysAct/giftOpen.png


BIN
src/assets/holidaysAct/gold.png


BIN
src/assets/holidaysAct/theme.png


BIN
src/assets/holidaysGift/Christmas/bg.png


BIN
src/assets/holidaysGift/Christmas/decorate.png


BIN
src/assets/holidaysGift/Christmas/draw.png


BIN
src/assets/holidaysGift/Christmas/light1.png


BIN
src/assets/holidaysGift/Christmas/spinBg.png


BIN
src/assets/holidaysGift/Christmas/theme.png


BIN
src/assets/holidaysGift/Halloween/bg.png


BIN
src/assets/holidaysGift/Halloween/draw.png


BIN
src/assets/holidaysGift/Halloween/light1.png


BIN
src/assets/holidaysGift/Halloween/spinBg.png


BIN
src/assets/holidaysGift/Halloween/theme.png


BIN
src/assets/promotion2/holidaysAct.png


BIN
src/assets/pwabonus/gift_package.webp


BIN
src/assets/shortcut/add.png


BIN
src/assets/shortcut/chrome/addhome.png


BIN
src/assets/shortcut/chrome/navbar.png


BIN
src/assets/shortcut/close.webp


BIN
src/assets/shortcut/h_screen.png


BIN
src/assets/shortcut/hand.webp


BIN
src/assets/shortcut/more.webp


BIN
src/assets/shortcut/safari/addhome.png


BIN
src/assets/shortcut/safari/navbar.png


BIN
src/assets/shortcut/safari_h/addhome.png


BIN
src/assets/shortcut/safari_h/navbar.png


BIN
src/assets/shortcut/safari_h/share.png


BIN
src/assets/shortcut/share.webp


BIN
src/assets/shortcut/star.webp


BIN
src/assets/topArea/download.webp


+ 17 - 0
src/constants/holidays.js

@@ -0,0 +1,17 @@
+const HOLIDAYS = {
+    Halloween: {
+        name: "Halloween",
+        gift: {
+            decorate: false,
+            color: ["#FFFFFF", "#8960DD"]
+        }
+    },
+    Christmas: {
+        name: "Christmas",
+        gift: {
+            decorate: true,
+            color: ["#FFFFFF", "#C4A31F"]
+        }
+    },
+};
+export default HOLIDAYS;

+ 7 - 1
src/constants/localkey.js

@@ -26,6 +26,9 @@ const LOCAL_CHECKIN_GAME_TS = "LOCAL_CHECKIN_GAME_TS";
 const LOCAL_WITHDRAW_TIP_TS = "LOCAL_WITHDRAW_TIP_TS";
 const LOCAL_HOLIDAYS_LAUNCH_TS = "LOCAL_HOLIDAYS_LAUNCH_TS";
 const LOCAL_HOLIDAYS_GAME_TS = "LOCAL_HOLIDAYS_GAME_TS";
+const LOCAL_DOWNLOAD_PROMPT_TS = "LOCAL_DOWNLOAD_PROMPT_TS";
+const LOCAL_HOLIDAYS_ACT_LAUNCH_TS = "LOCAL_HOLIDAYS_ACT_LAUNCH_TS";
+const LOCAL_HOLIDAYS_ACT_GAME_TS = "LOCAL_HOLIDAYS_ACT_GAME_TS";
 export {
     LOCAL_VISTOR_ID,
     LOCAL_USER_INFO,
@@ -52,5 +55,8 @@ export {
     LOCAL_CHECKIN_GAME_TS,
     LOCAL_WITHDRAW_TIP_TS,
     LOCAL_HOLIDAYS_LAUNCH_TS,
-    LOCAL_HOLIDAYS_GAME_TS
+    LOCAL_HOLIDAYS_GAME_TS,
+    LOCAL_DOWNLOAD_PROMPT_TS,
+    LOCAL_HOLIDAYS_ACT_LAUNCH_TS,
+    LOCAL_HOLIDAYS_ACT_GAME_TS
 };

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

@@ -1,6 +1,6 @@
 import { APP_SERVER } from "@constants/app";
 import { connectServer, disconnectServer, onDisconnect, sendToServer } from "@features/socketSlice";
-import { reqBankruptcyGift, reqFirstPayGift, reqFreeWithdraw, reqFreeWithdrawMission, reqGameInfo, reqHolidaysGiftInfo, reqMailCheck } from "@features/api";
+import { reqBankruptcyGift, reqFirstPayGift, reqFreeWithdraw, reqFreeWithdrawMission, reqGameInfo, reqHolidaysActInfo, reqHolidaysGiftInfo, reqMailCheck } from "@features/api";
 import { updatePayFinish } from "@features/paySlice";
 import { trackEvent } from "../../tracking/TrackAll";
 import { toast } from "react-toastify";
@@ -22,6 +22,7 @@ const handlePayFinish = (message, dispatch) => {
         dispatch(reqFreeWithdrawMission());
         dispatch(reqBankruptcyGift());
         dispatch(reqHolidaysGiftInfo());
+        dispatch(reqHolidaysActInfo());
     }, 0);
     let { data } = message;
     if (data && data.PayNum >= 200) global.showDropCoins();

+ 44 - 1
src/features/api.js

@@ -60,7 +60,10 @@ import {
     APP_BANKRUPTCY_GIFT,
     APP_REGISTER_NEW,
     APP_HOLIDAYS_GIFT_INFO,
-    APP_HOLIDAYS_GIFT_SPIN
+    APP_HOLIDAYS_GIFT_SPIN,
+    APP_HOLIDAYS_ACT_INFO,
+    APP_HOLIDAYS_ACT_SPIN,
+    APP_GET_PWA_BONUS
 } from "@api/config";
 import Http from "@api/request";
 import { updateGameList } from "@features/pageSlice";
@@ -1252,4 +1255,44 @@ export const reqHolidaysGiftSpin = createAsyncThunk(
         return response;
     }
 );
+
+export const reqHolidaysActInfo = createAsyncThunk(
+    APP_HOLIDAYS_ACT_INFO,
+    async (arg, thunkAPI) => {
+        let params = {
+            url: APP_HOLIDAYS_ACT_INFO
+        };
+        const response = await Http.get(params);
+        return response;
+    }
+);
+
+export const reqHolidaysActSpin = createAsyncThunk(
+    APP_HOLIDAYS_ACT_SPIN,
+    async (arg, thunkAPI) => {
+        let params = {
+            url: APP_HOLIDAYS_ACT_SPIN
+        };
+        const response = await Http.get(params);
+        return response;
+    }
+);
+//api
+
+//pwa安装奖励
+export const reqGetPwaBonus= createAsyncThunk(
+    APP_GET_PWA_BONUS,
+    async (arg, thunkAPI) => {
+        let params = {
+            url: APP_GET_PWA_BONUS
+        };
+        const response = await Http.get(params);
+        if (response && response.code === 200) {
+            thunkAPI.dispatch(reqGameInfo());
+            thunkAPI.dispatch(reqFreeWithdrawMission());
+            thunkAPI.dispatch(reqFreeWithdraw());
+        }
+        return response;
+    }
+);
 //api

+ 59 - 6
src/features/mainSlice.js

@@ -35,7 +35,9 @@ import {
     reqFreeWithdraw,
     reqFreeWithdrawMission,
     reqFreeWithdrawClaim,
-    reqHolidaysGiftInfo
+    reqHolidaysGiftInfo,
+    reqHolidaysActInfo,
+    reqGetPwaBonus
 } from "@features/api";
 import { toast } from 'react-toastify';
 import { getFinger2DeviceId, getHttpMsg, localStorageGet, localStorageSet } from "@utils/helpers";
@@ -272,6 +274,7 @@ const reducerRoutes = (state, action) => {
         let popFirst = 0;
         let showInstall = 0;
         let download = {};
+        let popPwaBonus = 0;
         if (conf) {
             if (!!conf.hall) hallServerUrl = conf.hall;
             if (!!conf.DOLLAR) global.DOLLAR = conf.DOLLAR;
@@ -315,8 +318,11 @@ const reducerRoutes = (state, action) => {
             //download 配置
             if (!!conf.download) download = conf.download;
             if (!!conf.LandscapeGames) landscapeGames = conf.LandscapeGames;
+
             // adjust相关配置
             if (!!conf.AdjustToken && !!conf.AdjustConfig) global.tracker.initAdjust(conf.AdjustToken, conf.AdjustConfig);
+
+            if (!!conf.popPwaBonus) popPwaBonus = conf.popPwaBonus;
         }
         //favorites
         // let favorites=getFavorites(json.favorites);
@@ -347,7 +353,8 @@ const reducerRoutes = (state, action) => {
             popFirst,
             showInstall,
             download,
-            landscapeGames
+            landscapeGames,
+            popPwaBonus
         });
         // 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});
     }
@@ -674,6 +681,15 @@ const reducerHolidaysGiftInfo = (state, action, arg) => {
     }
 };
 
+const reducerHolidaysActInfo = (state, action, arg) => {
+    let payload = action.payload;
+    if (payload && payload.code === 200) {
+        let data = payload.data;
+        state.holidaysActInfo = data;
+    }
+};
+
+
 const updateFreeWithdrawClaimStatus = (state, status, arg) => {
     state.freeTxClaimStatus = status;
 };
@@ -737,6 +753,24 @@ const smsLoginStatus = (state, status, arg) => {
     state.loginStatus.smslogin = status;
 };
 //custom
+//pwa bonus
+const reducerGetPwaBonus = (state, action, arg) => {
+    let payload = action.payload;
+    let msg = getHttpMsg(payload);
+    if (payload && payload.code === 200) {
+        toast.success(i18n.t("popPwaBonus.claim_success"), { toastId: "popPwaBonus" });
+        state.popPwaBonus = 0;
+    } else {
+        toast.error(i18n.t("popPwaBonus.claim_failed"), { toastId: "popPwaBonus" });
+    }
+    if (arg && arg.callback != null) {
+        arg.callback(payload);
+    }
+}
+const getPwaBonusStatus = (state, status, arg) => {
+    state.pwaBonusStatus = status;
+}
+//pwa bonus
 export const Main = createSlice({
     name: 'main',
     initialState: {
@@ -779,6 +813,7 @@ export const Main = createSlice({
         //免费提现任务
         freeWithdrawMission: {},
         holidaysGiftInfo: {},
+        holidaysActInfo: {},
         //当前游戏id
         gameID: 0,
         //服务器踢
@@ -812,7 +847,8 @@ export const Main = createSlice({
         showInstall: 0,
         download: {},
         landscapeGames: [],
-
+        popPwaBonus: 0,
+        pwaBonusStatus: REQ_STATUS.NONE,
     },
     reducers: {
         logout: (state, action) => {
@@ -896,6 +932,12 @@ export const Main = createSlice({
                 state.holidaysGiftInfo.user.left_times = left_times;
             }
         },
+        updateHolidaysActSpinTimes: (state, action) => {
+            let { left_times } = action.payload;
+            if (left_times != null) {
+                state.holidaysActInfo.user.left_times = left_times;
+            }
+        },
         changeOldAccount: (state, action) => {
             localStorageSet(LOCAL_LAST_LOGIN_TYPE, APP_LOGIN_TYPE.SMS);
             localStorageSet(LOCAL_SIGN, state.oldUserInfo.sign);
@@ -928,9 +970,11 @@ export const Main = createSlice({
         buildReqReducer(builder, reqFreeWithdrawMission, reducerFreeWithdrawMission, null);
         buildReqReducer(builder, reqFreeWithdrawClaim, reducerFreeWithdrawClaim, updateFreeWithdrawClaimStatus);
         buildReqReducer(builder, reqHolidaysGiftInfo, reducerHolidaysGiftInfo, null);
+        buildReqReducer(builder, reqHolidaysActInfo, reducerHolidaysActInfo, null);
+        buildReqReducer(builder, reqGetPwaBonus, reducerGetPwaBonus, getPwaBonusStatus);
     },
 });
-export const { logout, updateUserInfo, updateUserScore, updateAddScore, updateGameLike, updateCurrGame, updateServerKick, updateOpenPay, updateGameRoute, updateGameCardInfo, updateAreaID, updateHolidaysGiftSpinTimes, changeOldAccount } = Main.actions;
+export const { logout, updateUserInfo, updateUserScore, updateAddScore, updateGameLike, updateCurrGame, updateServerKick, updateOpenPay, updateGameRoute, updateGameCardInfo, updateAreaID, updateHolidaysGiftSpinTimes, changeOldAccount, updateHolidaysActSpinTimes } = Main.actions;
 //数据获取
 export const getFetchStatus = (state) => {
     return state.main.status;
@@ -1067,10 +1111,10 @@ export const getUserInfo = (state) => {
 };
 export const getOldUserInfo = (state) => {
     return state.main.oldUserInfo;
-}
+};
 export const getUserVip = (state) => {
     return state.main.vip || 0;
-}
+};
 export const getUserInsureScore = (state) => {
     if (state.main.freeWithdrawInfo) {
         return state.main.freeWithdrawInfo.InsureScore;
@@ -1276,7 +1320,16 @@ export const getFreeWithdrawMission = (state) => {
 export const getHolidaysGiftInfo = (state) => {
     return state.main.holidaysGiftInfo;
 };
+export const getHolidaysActInfo = (state) => {
+    return state.main.holidaysActInfo;
+};
 export const getLandscapeGames = (state) => {
     return state.main.landscapeGames;
 }
+export const getPopPwaBonus = (state) => {
+    return state.main.popPwaBonus;
+}
+export const getPwaCliamStatus = (state) => {
+    return state.main.pwaBonusStatus;
+}
 

+ 15 - 7
src/layout/BottomNav/index.jsx

@@ -12,7 +12,7 @@ import { useSelector } from "react-redux";
 import { checkIsStandalone } from "@utils/helpers";
 import { useThemeProvider } from "@contexts/themeContext";
 import { APP_THEME } from "@constants/app";
-import { getHolidaysGiftInfo } from '@features/mainSlice';
+import { getHolidaysActInfo, getHolidaysGiftInfo } from '@features/mainSlice';
 
 import boxHoliday from "@assets/bottomNav/boxHoliday.webp";
 import iconHoliday from "@assets/bottomNav/iconHoliday.webp";
@@ -50,11 +50,19 @@ const BottomNav = () => {
     // promotion
     const [redPromotion, setRedPromotion] = useState(false);
     const holidaysGiftInfo = useSelector(getHolidaysGiftInfo);
-    let { user: holidaysUser } = holidaysGiftInfo;
+    let { user: holidaysGiftUser } = holidaysGiftInfo;
+    const holidaysActInfo = useSelector(getHolidaysActInfo);
+    let { user: holidaysActUser } = holidaysActInfo;
+
     useEffect(() => {
         let tips_count = 0;
-        if (holidaysUser?.left_times !== undefined) {
-            if (holidaysUser.left_times > 0) {
+        if (holidaysGiftUser?.left_times !== undefined) {
+            if (holidaysGiftUser.left_times > 0) {
+                tips_count++;
+            }
+        }
+        if (holidaysActUser?.left_times !== undefined) {
+            if (holidaysActUser.left_times > 0) {
                 tips_count++;
             }
         }
@@ -64,7 +72,7 @@ const BottomNav = () => {
         }
 
         setRedPromotion(tips_count > 0);
-    }, [holidaysUser, firstPayGift]);
+    }, [holidaysGiftUser, firstPayGift, holidaysActUser]);
 
 
     const renderPromotionRedPoint = () => {
@@ -73,12 +81,12 @@ const BottomNav = () => {
                 <>
                     <div className={`${styles.redPoint} ${styles.redPromotion}`}></div>
                     {
-                        holidaysUser?.left_times > 0 ?
+                        holidaysGiftUser?.left_times > 0 ?
                             <div className={styles.redTipHoliday}  >
                                 <img src={boxHoliday} alt="" className={styles.boxHoliday} />
                                 <div className={styles.tip}>
                                     <img src={iconHoliday} alt="" className={styles.iconHoliday} />
-                                    <span>+{holidaysUser?.left_times}</span>
+                                    <span>+{holidaysGiftUser?.left_times}</span>
                                 </div>
                             </div>
                             : <></>

+ 10 - 0
src/layout/Layout.jsx

@@ -59,6 +59,11 @@ import WRecallGift from "@widgets/WRecallGift";
 import WHolidaysGift from "@widgets/WHolidaysGift";
 import WRecommendedGames from "@widgets/WRecommendedGames";
 import WCommonTextTip from "@widgets/WCommonTextTip";
+import WHolidaysAct from "@widgets/WHolidaysAct";
+import WSecurityScan from "@widgets/WInstallBox/WSecurityScan";
+import WQuickInstall from "@widgets/WInstallBox/WQuickInstall";
+import WDownloadPrompt from "@widgets/WInstallBox/WDownloadPrompt";
+import WPopPwaBonus from "@widgets/WInstallBox/WPopPwaBonus";
 const PageNotFound = lazy(() => import('@pages/PageNotFound'));
 
 const Layout = ({ ...props }) => {
@@ -199,8 +204,13 @@ const Layout = ({ ...props }) => {
                     isLogin && <WCheckIn />
                 }
                 <WHolidaysGift />
+                <WHolidaysAct />
 
                 <WRecommendedGames />
+                <WSecurityScan />
+                <WQuickInstall/>
+                <WDownloadPrompt/>
+                <WPopPwaBonus/>
                 {/* <WOrientation /> */}
             </div>
         );

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

@@ -815,7 +815,15 @@
       "android_op_1": "Share...",
       "android_op_2": "Search the web page",
       "android_op_3": "Add to Home Screen",
-      "android_op_4": "Computer website"
+      "android_op_4": "Computer website",
+      "title_1": "Add to Home Screen to earn",
+      "sub_title": "3 steps to claim bonus {0}",
+      "step_title": "Step {0}",
+      "op_tip_1": "Click",
+      "op_tip_2": "on the square toolbar",
+      "op_tip_3": "Select",
+      "op_tip_4": "“Add to Home Screen”",
+      "op_tip_5": "Open the app on your home screen and earn "
     }
   },
   "bindPhone": {
@@ -933,6 +941,45 @@
     "tips": "This Browser is not supported",
     "btn_open": "Open in external browser"
   },
+  "securityScan": {
+    "scan_title_1": "Virus Search",
+    "scan_title_2": "Fake App Verification",
+    "scan_title_3": "Other Risks",
+    "scanning": "Scanning....",
+    "scan_content_1": "No viruses found",
+    "scan_content_2": "The app is legitimate",
+    "scan_content_3": "No other risks found",
+    "scan_content_4": "No risks detected",
+    "scan_content_5": "Verified by Play Protect",
+    "title": "Security Scan",
+    "title_1": "Security Tests",
+    "title_2": "passed",
+    "btn_open": "Open",
+    "quick_title": "Quick installation",
+    "quick_tips": "4 times faster",
+    "actived": "Activated",
+    "btn_install": "Install now"
+  },
+  "downloadPrompt": {
+    "title": "GET {0}",
+    "subtitle": "BONUS",
+    "tips_1": "Higher Payouts ",
+    "tips_2": "(Up to 96.8% RTP)",
+    "tips_3": "Get bonus claim ",
+    "tips_4": "Save your account without losing it",
+    "btn_down": "Download",
+    "subtitle_ad": "INSTANTLY",
+    "tips_1_ad": "Tap Download To Get",
+    "tips_2_ad": "Start Games Faster",
+    "tips_3_ad": "Save your account without losing it"
+  },
+  "popPwaBonus": {
+    "title": "Congratulations!",
+    "desc": "You have received a bonus of",
+    "claim": "Claim",
+    "claim_success": "Reward claimed successfully",
+    "claim_failed": "Reward claim failed"
+  },
   "web": {
     "activity": {
       "redpack_not_available": "There is no RedPack",

+ 48 - 1
src/locales/languages/es_MX.json

@@ -752,7 +752,15 @@
       "android_op_1": "Compartir...",
       "android_op_2": "Buscar en la página web",
       "android_op_3": "Agregar a la Pantalla de Inicio",
-      "android_op_4": "Sitio web de computadora"
+      "android_op_4": "Sitio web de computadora",
+      "title_1": "Añadir a la Pantalla de Inicio para ganar",
+      "sub_title": "3 pasos para reclamar bono {0}",
+      "step_title": "Paso {0}",
+      "op_tip_1": "Haz clic",
+      "op_tip_2": "en la barra de herramientas cuadrada",
+      "op_tip_3": "Selecciona",
+      "op_tip_4": "“Añadir a la Pantalla de Inicio”",
+      "op_tip_5": "Abre la aplicación en tu pantalla de inicio y gana"
     }
   },
   "bindPhone": {
@@ -813,6 +821,45 @@
     "tips": "Este navegador no es compatible",
     "btn_open": "Abrir en navegador externo"
   },
+  "securityScan": {
+    "scan_title_1": "Búsqueda de Virus",
+    "scan_title_2": "Verificación de Aplicación Falsa",
+    "scan_title_3": "Otros Riesgos",
+    "scanning": "Escaneando....",
+    "scan_content_1": "No se encontraron virus",
+    "scan_content_2": "La aplicación es legítima",
+    "scan_content_3": "No se encontraron otros riesgos",
+    "scan_content_4": "No se detectaron riesgos",
+    "scan_content_5": "Verificado por Play Protect",
+    "title": "Escaneo de Seguridad",
+    "title_1": "Pruebas de Seguridad",
+    "title_2": "aprobadas",
+    "btn_open": "Abrir",
+    "quick_title": "Instalación Rápida",
+    "quick_tips": "4 veces más rápido",
+    "actived": "Activado",
+    "btn_install": "Instalar ahora"
+  },
+  "downloadPrompt": {
+    "title": "OBTENER {0}",
+    "subtitle": "BONO",
+    "tips_1": "Pagos Más Altos",
+    "tips_2": "(Hasta 96,8% RTP)",
+    "tips_3": "Obtener reclamo de bono",
+    "tips_4": "Guarde su cuenta sin perderla",
+    "btn_down": "Descargar",
+    "subtitle_ad": "INSTANTÁNEAMENTE",
+    "tips_1_ad": "Toca Descargar para obtenerlo.",
+    "tips_2_ad": "Inicia los juegos más rápido",
+    "tips_3_ad": "Guarda tu cuenta sin perderla."
+  },
+  "popPwaBonus": {
+    "title": "¡Felicidades!",
+    "desc": "Has recibido un bono de",
+    "claim": "Reclamar",
+    "claim_success": "Recompensa reclamada exitosamente",
+    "claim_failed": "Error al reclamar la recompensa"
+  },
   "web": {
     "activity": {
       "redpack_not_available": "No hay RedPack",

+ 48 - 1
src/locales/languages/pt_BR.json

@@ -751,7 +751,15 @@
       "android_op_1": "Compartilhar...",
       "android_op_2": "Pesquisar na página da web",
       "android_op_3": "Adicionar à Tela Inicial",
-      "android_op_4": "Site de computador"
+      "android_op_4": "Site de computador",
+      "title_1": "Adicionar à Tela Inicial para ganhar",
+      "sub_title": "3 passos para reivindicar bônus {0}",
+      "step_title": "Passo {0}",
+      "op_tip_1": "Clique",
+      "op_tip_2": "na barra de ferramentas quadrada",
+      "op_tip_3": "Selecione",
+      "op_tip_4": "“Adicionar à Tela Inicial”",
+      "op_tip_5": "Abra o aplicativo na sua tela inicial e ganhe"
     }
   },
   "bindPhone": {
@@ -812,6 +820,45 @@
     "tips": "Este navegador não é suportado",
     "btn_open": "Abrir em navegador externo"
   },
+  "securityScan": {
+    "scan_title_1": "Busca de Vírus",
+    "scan_title_2": "Verificação de Aplicativo Falso",
+    "scan_title_3": "Outros Riscos",
+    "scanning": "Escaneando....",
+    "scan_content_1": "Nenhum vírus encontrado",
+    "scan_content_2": "O aplicativo é legítimo",
+    "scan_content_3": "Nenhum outro risco encontrado",
+    "scan_content_4": "Nenhum risco detectado",
+    "scan_content_5": "Verificado pelo Play Protect",
+    "title": "Verificação de Segurança",
+    "title_1": "Testes de Segurança",
+    "title_2": "aprovados",
+    "btn_open": "Abrir",
+    "quick_title": "Instalação Rápida",
+    "quick_tips": "4 vezes mais rápido",
+    "actived": "Ativado",
+    "btn_install": "Instalar agora"
+  },
+  "downloadPrompt": {
+    "title": "OBTER {0}",
+    "subtitle": "BÔNUS",
+    "tips_1": "Pagamentos Maiores",
+    "tips_2": "(Até 96,8% RTP)",
+    "tips_3": "Receba reivindicação de bônus",
+    "tips_4": "Salve sua conta sem perdê-la",
+    "btn_down": "Baixar",
+    "subtitle_ad": "IMEDIATAMENTE",
+    "tips_1_ad": "Toque em Baixar para obter",
+    "tips_2_ad": "Inicie os jogos mais rapidamente",
+    "tips_3_ad": "Salve sua conta para não perdê-la."
+  },
+  "popPwaBonus": {
+    "title": "Parabéns!",
+    "desc": "Você recebeu um bônus de",
+    "claim": "Resgatar",
+    "claim_success": "Recompensa resgatada com sucesso",
+    "claim_failed": "Falha ao resgatar recompensa"
+  },
   "web": {
     "activity": {
       "redpack_not_available": "Não há RedPack",

+ 48 - 1
src/locales/languages/ru_RU.json

@@ -751,7 +751,15 @@
       "android_op_1": "Поделиться...",
       "android_op_2": "Искать на веб-странице",
       "android_op_3": "Добавить на Домашний Экран",
-      "android_op_4": "Сайт для компьютера"
+      "android_op_4": "Сайт для компьютера",
+      "title_1": "Добавить на главный экран, чтобы заработать",
+      "sub_title": "3 шага для получения бонуса {0}",
+      "step_title": "Шаг {0}",
+      "op_tip_1": "Нажмите",
+      "op_tip_2": "на квадратной панели инструментов",
+      "op_tip_3": "Выберите",
+      "op_tip_4": "«Добавить на главный экран»",
+      "op_tip_5": "Откройте приложение на главном экране и заработайте"
     }
   },
   "bindPhone": {
@@ -811,6 +819,45 @@
     "tips": "Этот браузер не поддерживается",
     "btn_open": "Открыть во внешнем браузере"
   },
+  "securityScan": {
+    "scan_title_1": "Поиск Вирусов",
+    "scan_title_2": "Проверка Поддельного Приложения",
+    "scan_title_3": "Другие Риски",
+    "scanning": "Сканирование....",
+    "scan_content_1": "Вирусы не найдены",
+    "scan_content_2": "Приложение является подлинным",
+    "scan_content_3": "Другие риски не найдены",
+    "scan_content_4": "Риски не обнаружены",
+    "scan_content_5": "Проверено Play Protect",
+    "title": "Проверка Безопасности",
+    "title_1": "Тесты Безопасности",
+    "title_2": "пройдены",
+    "btn_open": "Открыть",
+    "quick_title": "Быстрая Установка",
+    "quick_tips": "В 4 раза быстрее",
+    "actived": "Активировано",
+    "btn_install": "Установить сейчас"
+  },
+  "downloadPrompt": {
+    "title": "ПОЛУЧИТЬ {0}",
+    "subtitle": "БОНУС",
+    "tips_1": "Более Высокие Выплаты",
+    "tips_2": "(До 96,8% RTP)",
+    "tips_3": "Получить заявку на бонус",
+    "tips_4": "Сохраните свой аккаунт без потерь",
+    "btn_down": "Скачать",
+    "subtitle_ad": "НЕМЕДЛЕННО",
+    "tips_1_ad": "Tap Download To Get",
+    "tips_2_ad": "Нажмите «Загрузить», чтобы получить.",
+    "tips_3_ad": "Сохраните свою учетную запись, чтобы не потерять ее."
+  },
+  "popPwaBonus": {
+    "title": "Поздравляем!",
+    "desc": "Вы получили бонус в размере",
+    "claim": "Получить",
+    "claim_success": "Награда успешно получена",
+    "claim_failed": "Ошибка получения награды"
+  },
   "web": {
     "activity": {
       "redpack_not_available": "RedPack недоступен",

+ 48 - 1
src/locales/languages/zh_TW.json

@@ -751,7 +751,15 @@
       "android_op_1": "分享...",
       "android_op_2": "搜索網頁",
       "android_op_3": "添加到主屏幕",
-      "android_op_4": "電腦網站"
+      "android_op_4": "電腦網站",
+      "title_1": "添加到主屏幕即可获得奖励",
+      "sub_title": "3步领取奖金{0}",
+      "step_title": "第{0}步",
+      "op_tip_1": "点击",
+      "op_tip_2": "方形工具栏上的",
+      "op_tip_3": "选择",
+      "op_tip_4": "“添加到主屏幕”",
+      "op_tip_5": "在主屏幕上打开应用并赚取"
     }
   },
   "bindPhone": {
@@ -811,6 +819,45 @@
     "tips": "此浏览器不受支持",
     "btn_open": "在外部浏览器中打开"
   },
+  "securityScan": {
+    "scan_title_1": "病毒搜索",
+    "scan_title_2": "虚假应用验证",
+    "scan_title_3": "其他风险",
+    "scanning": "扫描中....",
+    "scan_content_1": "未发现病毒",
+    "scan_content_2": "应用是合法的",
+    "scan_content_3": "未发现其他风险",
+    "scan_content_4": "未检测到风险",
+    "scan_content_5": "已通过 Play Protect 验证",
+    "title": "安全扫描",
+    "title_1": "安全测试",
+    "title_2": "已通过",
+    "btn_open": "打开",
+    "quick_title": "快速安装",
+    "quick_tips": "快 4 倍",
+    "actived": "已激活",
+    "btn_install": "立即安装"
+  },
+  "downloadPrompt": {
+    "title": "获取{0}",
+    "subtitle": "奖金",
+    "tips_1": "更高派彩",
+    "tips_2": "(高达96.8% RTP)",
+    "tips_3": "获取奖金领取",
+    "tips_4": "保存账户不丢失",
+    "btn_down": "下载",
+    "subtitle_ad": "即刻",
+    "tips_1_ad": "点击下载即可获取",
+    "tips_2_ad": "更快地开始游戏",
+    "tips_3_ad": "保存您的帐户,以免丢失。"
+  },
+  "popPwaBonus": {
+    "title": "恭喜您!",
+    "desc": "您已获得奖金",
+    "claim": "领取",
+    "claim_success": "奖励领取成功",
+    "claim_failed": "奖励领取失败"
+  },
   "web": {
     "activity": {
       "redpack_not_available": "沒有紅包",

+ 16 - 1
src/utils/helpers.js

@@ -778,4 +778,19 @@ export const getFinger2DeviceId = async () => {
         }
 
     })
-}
+}
+export const getMainDomain = () => {
+    // 获取完整的域名
+    const fullDomain = window.location.hostname;
+
+    // 将域名分割成部分
+    const parts = fullDomain.split('.');
+
+    // 如果域名部分少于2个,直接返回完整域名
+    if (parts.length <= 2) {
+        return parts[0]; // 返回第一个部分(如 localhost)
+    }
+
+    // 提取主域名并去除后缀(最后一部分)
+    return parts.slice(-2, -1).join('');
+};

+ 2 - 2
src/widgets/ModuleGroup/index.jsx

@@ -210,13 +210,13 @@ const ModuleGroup = ({ pageID }) => {
                             <img src={plus} alt="" className={styles.plus} onClick={() => setShowWithdrawPopup(true)} />
 
                             <div className={styles.wealth}>
-                                <div className={styles.cash}>${user.Score}</div>
+                                <div className={styles.cash} id="userScore">${user.Score}</div>
                                 <div className={styles.gold}>
                                     {/* <svg className={`${styles.bottomicon}`}>
                                         <use xlinkHref={`${symbol}#TabIcon_Share`} />
                                     </svg> */}
                                     <img src={bonus} alt="" className={styles.bonus} />
-                                    <div className={styles.num}>{user.InsureScore}</div>
+                                    <div className={styles.num} id="userInsureScore">{user.InsureScore}</div>
                                 </div>
                             </div>
 

+ 10 - 0
src/widgets/WBrowserNotSupport/index.jsx

@@ -14,12 +14,22 @@ const WBrowserNotSupport = () => {
     useEffect(() => {
         setTimeout(()=>{
             setIsShow(true);
+
+            // adjust 打开fb引导界面
+            if (isAndroid()) global.tracker.trackEvent("enter_fb_guide_android");
+            else if (isIOS()) global.tracker.trackEvent("enter_fb_guide_ios");
         },150)
     }, []);
     const handleClickFBAndroid=()=>{
+        // adjust android点击fb引导界面按钮
+        global.tracker.trackEvent("click_fb_guide_android");
+
         openUrlUseChrome();
     }
     const handleClickFBIOS=()=>{
+        // adjust ios点击fb引导界面按钮
+        global.tracker.trackEvent("click_fb_guide_ios");
+
         openUrlUseSafari();
     }
     const renderFBAndroid=()=>{

+ 0 - 15
src/widgets/WCommonTextTip/index.jsx

@@ -22,27 +22,12 @@ const WCommonTextTip = () => {
         global.commonTextTip = commonTextTip;
     }, []);
 
-
-    // useEffect(() => {
-    //     if (open) return;
-    //     setTimeout(() => {
-    //         setOpen(true);
-    //     }, 1000);
-    // }, [open]);
-
     return (
         <Modal open={open} onClose={() => setOpen(false)} closeAfterTransition>
             <Fade in={open} timeout={300}>
                 <div className={styles.container}>
                     <div className={styles.popup}>
                         <div className={styles.texts}>
-                            {/* <div className={styles.text}>
-                                <span>This is your total balance, which mainly comes from recharges and various bonuses.</span>
-                            </div>
-                            <div className={styles.text}>
-                                <span>After successfully completing the deposit, you will become our esteemed VIP, and you will be able to withdraw all the cash you have won in the game.</span>
-                            </div> */}
-
                             {
                                 texts.map((item, index) => {
                                     return (

+ 799 - 0
src/widgets/WHolidaysAct/index.jsx

@@ -0,0 +1,799 @@
+import styles from "./styles.module.scss";
+// import d_bg from "@assets/holidaysGift/d.png";
+import img_close from "@assets/holidaysGift/close.png";
+import img_theme from "@assets/holidaysAct/theme.png";
+import icon_cash from "@assets/holidaysGift/cash.png";
+import icon_gift from "@assets/holidaysGift/gift.png";
+import icon_clock from "@assets/holidaysAct/clock.png";
+import icon_gift2 from "@assets/holidaysAct/gift.png";
+import giftLock from "@assets/holidaysAct/giftLock.png";
+import giftOpen from "@assets/holidaysAct/giftOpen.png";
+import gold from "@assets/holidaysAct/gold.png";
+import detailGift from "@assets/holidaysAct/detailGift.png";
+import React, { useEffect, useMemo, useRef, useState } from "react";
+import { useDispatch, useSelector } from "react-redux";
+import { useNavigate } from "react-router";
+import { Fade, Grow, Modal } from "@mui/material";
+import symbol from "@assets/symbol-defs.svg";
+import { getHolidaysActInfo, getUserInfo, getUserVip, updateHolidaysActSpinTimes } from "@features/mainSlice";
+import { localStorageGet, localStorageSet } from "@utils/helpers";
+import { toast } from "react-toastify";
+import { reqGameInfo, reqHolidaysActSpin } from "@features/api";
+import usePaymentActions from "@hooks/usePaymentActions";
+
+const i18n = global.i18n;
+const WHolidaysAct = () => {
+    const [open, setOpen] = useState(false);
+    const navigate = useNavigate();
+    const dispatch = useDispatch();
+    const [isLock, setIsLock] = useState(false);
+    const info = useSelector(getHolidaysActInfo);
+    let { end_time, free_times, status, user, slots, recharge_rules } = info;
+    // console.log("info", info);
+    const [spinTimes, setSpinTimes] = useState(user?.left_times);
+    useEffect(() => {
+        if (user?.left_times !== undefined) {
+            setSpinTimes(user.left_times);
+        }
+    }, [user]);
+
+    // 新增状态管理
+    const [itemsState, setItemsState] = useState(
+        Array(9).fill({
+            isSelected: false,
+            isOpened: false,
+            showReward: false,
+            showMask: false,
+            isAnimating: false,
+            rotationAngle: 0
+        })
+    );
+    const [selectedIndex, setSelectedIndex] = useState(-1);
+    const [isAnimationRunning, setIsAnimationRunning] = useState(false);
+    const [goldFlying, setGoldFlying] = useState(false);
+    const animationRef = useRef(null);
+    const coinRefs = useRef([]);
+
+    const userInfo = useSelector(getUserInfo);
+    // console.error("userVip", userInfo?.vip);
+
+    const [rewardData, setRewardData] = useState([]);
+
+
+    const statusRef = useRef(status);
+    useEffect(() => {
+        statusRef.current = status;
+    }, [status]);
+
+    const show = () => {
+        if (statusRef.current === 0) return global.openNextPopup();;
+        setOpen(true);
+    };
+
+    const hide = () => {
+        setOpen(false);
+    };
+
+    const onClose = () => {
+        hide();
+        global.openNextPopup();
+    };
+
+    useEffect(() => {
+        const holidaysAct = {
+            show,
+            hide
+        };
+        global.holidaysAct = holidaysAct;
+    }, []);
+
+    const onCheckClose = () => {
+        onClose();
+    };
+
+    const { startPayingOrder } = usePaymentActions();
+
+    const handleBuyClick = (rule) => {
+        setOpen(false);
+        let payload = {
+            price: rule.amount,
+            goodsName: "",
+            payType: "",
+            GiftsID: rule.gear.gift_id,
+            gear: rule.gear.gear
+        };
+
+        startPayingOrder(payload);
+        localStorageSet("payHolidaysAct", true);
+    };
+
+    const resetAllItems = () => {
+        setItemsState(Array(9).fill({
+            isSelected: false,
+            isOpened: false,
+            showReward: false,
+            showMask: true,
+            isAnimating: false,
+            rotationAngle: 0
+        }));
+        setSelectedIndex(-1);
+        setIsAnimationRunning(false);
+        setGoldFlying(false);
+
+        if (animationRef.current) {
+            clearInterval(animationRef.current);
+            animationRef.current = null;
+        }
+    };
+
+    const flyCoinAnimation = (startX, startY, endX, endY, reward) => {
+        return new Promise((resolve) => {
+            const coinCount = 3; // 3个金币
+            let completed = 0;
+            // 是否已显示金额文本
+            let amountShown = false;
+
+            for (let i = 0; i < coinCount; i++) {
+                setTimeout(() => {
+                    const coin = document.createElement('img');
+                    coin.src = gold;
+                    coin.className = styles.flyingCoin;
+
+                    coin.style.left = `${startX}px`;
+                    coin.style.top = `${startY}px`;
+                    coin.style.transform = 'translate(0, 0) scale(1)';
+                    coin.style.opacity = '1';
+
+                    document.body.appendChild(coin);
+
+                    // 强制重排
+                    coin.getBoundingClientRect();
+
+                    // 设置transition
+                    coin.style.transition = 'all 1.2s ease-out, opacity 1s ease-in 0.2s';
+
+                    // 最终位置
+                    coin.style.transform = `translate(${endX - startX}px, ${endY - startY}px) scale(0.5)`;
+                    // opacity延迟淡出
+                    coin.style.opacity = '0';
+
+
+                    // 如果是第一个金币,在目标位置显示金额文本
+                    if (i === 0) {
+                        setTimeout(() => {
+                            if (!amountShown) {
+                                showAmountText(endX, endY, '+' + reward);
+                                amountShown = true;
+                                dispatch(reqGameInfo());
+                            }
+                        }, 1200); // 金币到达后显示
+                    }
+
+                    setTimeout(() => {
+                        document.body.removeChild(coin);
+                        completed++;
+                        if (completed === coinCount) {
+                            // resolve();
+                            // 等金币都移除后再移除金额文本
+                            removeAmountText();
+                            resolve();
+                        }
+                    }, 1500);
+
+                }, i * 150); // 金币间隔150ms
+            }
+        });
+    };
+
+
+    // 显示金额文本
+    const showAmountText = (x, y, text) => {
+        const amountText = document.createElement('div');
+        amountText.className = 'amount-text'; // 需要对应的CSS
+        amountText.textContent = text;
+        amountText.style.position = 'fixed';
+        amountText.style.left = `${x}px`;
+        amountText.style.top = `${y + 10}px`;
+        amountText.style.transform = 'translate(-50%, -50%)';
+        amountText.style.color = '#FFD700';
+        amountText.style.fontSize = '20px';
+        amountText.style.fontWeight = 'bold';
+        amountText.style.textShadow = '0 0 5px rgba(255,215,0,0.8)';
+        amountText.style.zIndex = '10001';
+        amountText.style.opacity = '0';
+        amountText.style.transition = 'all 0.2s ease';
+
+        document.body.appendChild(amountText);
+
+        // 触发动画
+        setTimeout(() => {
+            amountText.style.opacity = '1';
+            // amountText.style.transform = 'translate(-50%, -60px)';
+        }, 10);
+
+        return amountText;
+    };
+
+    // 移除金额文本
+    const removeAmountText = () => {
+        const text = document.querySelector('.amount-text');
+        if (text) {
+            text.style.opacity = '0';
+            setTimeout(() => {
+                if (text.parentNode) {
+                    text.parentNode.removeChild(text);
+                }
+            }, 500);
+        }
+    };
+
+    const handleClickItem = async (event, index) => {
+        // 如果动画正在进行中,阻止新的点击
+        if (isAnimationRunning) return;
+
+        // 如果已经有选中的项目,点击任意一项重置
+        if (selectedIndex !== -1) {
+            resetAllItems();
+            return;
+        }
+
+        if (!user) return;
+        if (spinTimes <= 0) {
+            toast.error("Not enough tries! Buy a pack gift", { toastId: "act_pack" });
+            return;
+        } else {
+            // dispatch(updateHolidaysActSpinTimes(user.left_times - 1));
+            setSpinTimes(spinTimes - 1);
+        }
+
+        let res = await dispatch(reqHolidaysActSpin()).unwrap();
+        // let res = {
+        //     "data": {
+        //         "slot_index": 1,
+        //         "reward": 0.5,
+        //         "left_times": 0
+        //     },
+        //     "result": "",
+        //     "msg": "success",
+        //     "code": 200
+        // };
+
+        console.error("res", res);
+        if (!res || res.code !== 200) {
+            return;
+        }
+        let { reward, slot_index, left_times } = res.data;
+        let displayReward = [...slots];
+        // 1. 创建不包含目标元素的新数组
+        const otherElements = displayReward.filter((_, i) => i !== slot_index);
+
+        // 2. Fisher-Yates 洗牌算法打乱
+        for (let i = otherElements.length - 1; i > 0; i--) {
+            const j = Math.floor(Math.random() * (i + 1));
+            [otherElements[i], otherElements[j]] = [otherElements[j], otherElements[i]];
+        }
+
+        // 3. 插入目标元素
+        const newArray = [...otherElements.slice(0, index), displayReward[slot_index], ...otherElements.slice(index)];
+        displayReward = newArray;
+        setRewardData(displayReward);
+
+
+        setIsAnimationRunning(true);
+        setSelectedIndex(index);
+
+        // 1. 打开其他item遮罩
+        setItemsState(prev => prev.map((item, idx) => ({
+            ...item,
+            showMask: idx === index ? false : true
+        })));
+
+        // 2. 被选中的item旋转动画
+        let rotationCount = 0;
+        const maxRotations = 5;
+
+        await new Promise((resolve) => {
+            let swingCount = 0; // 完整的摇摆次数
+            let isForward = true; // 当前是向右旋转还是回正
+
+            animationRef.current = setInterval(() => {
+                setItemsState(prev => prev.map((item, idx) => {
+                    if (idx === index) {
+                        if (isForward) {
+                            // 向右旋转10度
+                            isForward = false;
+                            return {
+                                ...item,
+                                rotationAngle: 10,
+                                isAnimating: true
+                            };
+                        } else {
+                            // 回正到0度
+                            isForward = true;
+                            swingCount++; // 完成一次完整摇摆
+
+                            // 3. 第3次摇摆结束时打开item
+                            if (swingCount === 3) {
+                                return {
+                                    ...item,
+                                    rotationAngle: 0,
+                                    isOpened: true,
+                                    isAnimating: true
+                                };
+                            }
+
+                            // 4. 第5次摇摆结束时
+                            if (swingCount === 5) {
+                                // 先回正到0度
+                                const newState = {
+                                    ...item,
+                                    rotationAngle: 0,
+                                    isAnimating: false
+                                };
+
+                                clearInterval(animationRef.current);
+                                animationRef.current = null;
+
+                                // 等待旋转完全停止后再显示金额
+                                setTimeout(async () => {
+                                    // 延迟300ms再显示金额
+                                    await new Promise(res => setTimeout(res, 200));
+
+                                    // 现在显示金额
+                                    setItemsState(prev => prev.map((item2, idx2) => {
+                                        if (idx2 === index) {
+                                            return {
+                                                ...item2,
+                                                showReward: true
+                                            };
+                                        }
+                                        return item2;
+                                    }));
+
+                                    // 等待金额显示动画完成
+                                    await new Promise(res => setTimeout(res, 300));
+
+                                    // 金币飞行动画...
+                                    if (coinRefs.current[index]) {
+                                        const getBodyCenter = (el) => {
+                                            const bodyRect = document.body.getBoundingClientRect();
+                                            if (!el) return { x: bodyRect.width / 1.5, y: 50 };
+
+                                            const rect = el.getBoundingClientRect();
+
+                                            // 相对于body的坐标
+                                            const relativeX = rect.left - bodyRect.left + (rect.width / 2);
+                                            const relativeY = rect.top - bodyRect.top + (rect.height / 2);
+
+                                            return { x: relativeX, y: relativeY };
+                                        };
+
+                                        const itemCenter = getBodyCenter(coinRefs.current[index]);
+                                        // userInfo?.vip
+                                        let targetCenter = getBodyCenter(document.getElementById('userInsureScore'));
+                                        if (userInfo?.vip > 0) targetCenter = getBodyCenter(document.getElementById('userScore'));
+                                        const startX = itemCenter.x - 10;
+                                        const startY = itemCenter.y - 10;
+                                        const endX = targetCenter.x - 10;
+                                        const endY = targetCenter.y - 10;
+
+                                        setGoldFlying(true);
+                                        await flyCoinAnimation(startX, startY, endX, endY, reward);
+                                        setGoldFlying(false);
+                                    }
+
+                                    // 5. 其他item渐显
+                                    setTimeout(() => {
+                                        setItemsState(prev => prev.map((item2, idx3) => {
+                                            if (idx3 !== index) {
+                                                return {
+                                                    ...item2,
+                                                    showMask: false
+                                                };
+                                            }
+                                            return item2;
+                                        }));
+
+                                        // 延迟500ms显示其他item的金额
+                                        setItemsState(prev => prev.map((item2, idx3) => {
+                                            if (idx3 !== index) {
+                                                return {
+                                                    ...item2,
+                                                    showReward: true
+                                                };
+                                            }
+                                            return item2;
+                                        }));
+
+                                        setIsAnimationRunning(false);
+                                        resolve();
+                                    }, 200);
+                                }, 100); // 等待旋转动画完成
+
+                                return newState;
+                            }
+
+                            return {
+                                ...item,
+                                rotationAngle: 0,
+                                isAnimating: true
+                            };
+                        }
+                    }
+                    return item;
+                }));
+            }, 100); // 改为100ms切换一次
+        });
+
+        dispatch(updateHolidaysActSpinTimes({ left_times }));
+    };
+
+    const getAdaptScale = () => {
+        // 设计稿宽高比(以iPhone X为例:375/812 ≈ 0.462)
+        const DESIGN_WIDTH = 375;
+        const DESIGN_HEIGHT = 812;
+        const designRatio = DESIGN_WIDTH / DESIGN_HEIGHT; // 0.462
+
+        // 获取当前视口尺寸
+        const bodyRect = document.body.getBoundingClientRect();
+        const currentWidth = bodyRect.width;
+        const currentHeight = bodyRect.height;
+        const currentRatio = currentWidth / currentHeight;
+
+        // console.log(`当前: ${currentWidth}x${currentHeight}, 比例: ${currentRatio.toFixed(3)}, 设计比例: ${designRatio.toFixed(3)}`);
+
+        // 判断是宽屏还是窄屏
+        const isWider = currentRatio > designRatio;
+        const ratioDiff = Math.abs(currentRatio - designRatio);
+
+        // 针对长页面(高度较大)的特殊处理
+        if (currentHeight > 1200) { // 如果页面非常高
+            // console.log("检测到长页面,使用高度适配");
+            // 根据高度计算缩放
+            const heightScale = currentHeight / DESIGN_HEIGHT;
+            return Math.min(heightScale, 1); // 不超过原始大小
+        }
+
+        // 正常比例适配逻辑
+        if (isWider) {
+            // 当前屏幕比设计稿宽(矮胖屏)
+            if (ratioDiff <= 0.05) {
+                return 0.95;
+            } else if (ratioDiff <= 0.1) {
+                return 0.9;
+            } else if (ratioDiff <= 0.15) {
+                return 0.85;
+            } else if (ratioDiff <= 0.2) {
+                return 0.8;
+            } else {
+                return 0.75; // 极端宽屏
+            }
+        } else {
+            // 当前屏幕比设计稿窄(瘦高屏)
+            if (ratioDiff <= 0.05) {
+                return 1;
+            } else if (ratioDiff <= 0.1) {
+                return 0.95;
+            } else if (ratioDiff <= 0.15) {
+                return 0.9;
+            } else {
+                return 0.85; // 极端窄屏
+            }
+        }
+    };
+
+    const getLeftTime = (end) => {
+        if (!end) return "";
+
+        const endTime = new Date(end).getTime();
+        const now = Date.now();
+
+        // 如果活动已结束
+        if (now >= endTime) {
+            return "00:00:00:00";
+        }
+
+        // 计算剩余毫秒数
+        const remainingTime = endTime - now;
+
+        // 计算天数、小时、分钟、秒
+        const days = Math.floor(remainingTime / (1000 * 60 * 60 * 24));
+        const hours = Math.floor((remainingTime % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
+        const minutes = Math.floor((remainingTime % (1000 * 60 * 60)) / (1000 * 60));
+        const seconds = Math.floor((remainingTime % (1000 * 60)) / 1000);
+
+        // 格式化:天数:小时:分钟:秒
+        return `${days.toString().padStart(2, '0')}:${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
+    };
+
+    const timeElementRef = useRef(null);
+    const timerRef = useRef(null);
+    // 不用react useState钩子防止频繁更新
+    useEffect(() => {
+        if (!end_time) return;
+
+        // 使用 setTimeout 确保 DOM 已挂载
+        const initTimer = () => {
+            if (!timeElementRef.current) {
+                // 如果还没挂载,等待下一帧
+                setTimeout(initTimer, 0);
+                return;
+            }
+
+            console.log("timeElementRef 已挂载:", timeElementRef.current);
+
+            const updateTimeDisplay = () => {
+                const newTime = getLeftTime(end_time);
+                if (timeElementRef.current) {
+                    timeElementRef.current.textContent = newTime;
+                }
+            };
+
+            updateTimeDisplay();
+            timerRef.current = setInterval(updateTimeDisplay, 1000);
+        };
+
+        initTimer();
+
+        return () => {
+            if (timerRef.current) {
+                clearInterval(timerRef.current);
+            }
+        };
+    }, [end_time]);
+
+    const [openRule, setOpenRule] = useState(false);
+    const ActRule = ({ open, onClose }) => {
+        const [show, setShow] = useState(open);
+        const texts = [
+            "1.All users can participate in the lucky draw once for free dunng the event.",
+            "2.You can get a chance to win a prize by completing a deposit.",
+            "3.The highest prize in a single lucky draw is $777."
+        ];
+
+
+        const handleClose = () => {
+            setShow(false);
+            setTimeout(() => {
+                onClose();
+            }, 500);
+        };
+
+        return (
+            <Modal open={show} onClose={handleClose} closeAfterTransition>
+                <Fade in={show} timeout={500}>
+                    <div className={styles.rule_container}>
+                        <div className={styles.rule_popup}>
+                            <div className={styles.rule_title}>
+                                Activity Rules
+                            </div>
+
+                            <div className={styles.rule_texts}>
+                                {
+                                    texts.map((item, index) => {
+                                        return (
+                                            <div className={styles.rule_text} key={index}>
+                                                <span>{item}</span>
+                                            </div>
+                                        );
+                                    }
+                                    )
+                                }
+                            </div>
+
+                            <div className={styles.rule_btnClaim} onClick={handleClose}>
+                                <span>CLAIM · </span>
+                                <img src={icon_gift2} alt="" />
+                                <span>OPENS X1</span>
+                            </div>
+                        </div>
+                    </div>
+                </Fade >
+            </Modal >
+        );
+    };
+
+    useEffect(() => {
+        if (free_times === undefined || !open) return;
+        const hasProcessed = localStorageGet("actRule_processed");
+        if (free_times > 0 && !hasProcessed) {
+            localStorageSet("actRule_processed", true);
+            setOpenRule(true);
+        }
+    }, [free_times, open]);
+
+    const [openDetail, setOpenDetail] = useState(false);
+    const ActDetail = ({ open, onClose }) => {
+        const [show, setShow] = useState(open);
+        const texts = [
+            "1.Deposit to get 125% bonus.",
+            "2.Each pack can be bought twrice daily and expires after countdown.",
+            "3.Pack value is based on  historical performance.",
+            "4.Event lasts 3 days (purchases  unavailable  afterward)",
+            "5.Expioiting bugs may result in penalties",
+            "6.(e.g..ban/reward deduction)"
+        ];
+
+
+        const handleClose = () => {
+            setShow(false);
+            setTimeout(() => {
+                onClose();
+            }, 500);
+        };
+
+        return (
+            <Modal open={show} onClose={handleClose} closeAfterTransition>
+                <Fade in={show} timeout={500}>
+                    <div className={styles.detail_container}>
+                        <div className={styles.detail_popup}>
+                            <img src={detailGift} alt="" className={styles.detailGift} />
+
+                            <div className={styles.detail_title}>
+                                Event Details
+                            </div>
+
+                            <div className={styles.detail_texts}>
+                                {
+                                    texts.map((item, index) => {
+                                        return (
+                                            <div className={styles.detail_text} key={index}>
+                                                <span>{item}</span>
+                                            </div>
+                                        );
+                                    }
+                                    )
+                                }
+                            </div>
+
+                            <div className={styles.detail_btnOK} onClick={handleClose}>
+                                OK
+                            </div>
+                        </div>
+                    </div>
+                </Fade >
+            </Modal >
+        );
+    };
+
+
+    if (!!!status) return <></>;
+    return (
+        <>
+            <Modal open={open} onClose={onCheckClose} closeAfterTransition>
+                <Fade in={open} timeout={500}>
+                    <div className={styles.container}>
+                        <div className={styles.activity} style={{ transform: `scale(${getAdaptScale()})` }}>
+                            <img src={img_theme} alt="" className={styles.themeImg} />
+                            <div className={styles.layout}>
+                                <div className={styles.msgBar}>
+                                    <div className={styles.left}>
+                                        <img src={icon_clock} alt="" />
+                                        {/* <span>{timeLeft}</span> */}
+                                        <span ref={timeElementRef}></span>
+                                    </div>
+                                    <div className={styles.right}>
+                                        <img src={icon_gift2} alt="" />
+                                        <span>OPENS</span>
+                                        <span className={styles.nums}>X{spinTimes}</span>
+                                    </div>
+                                </div>
+
+                                <div className={styles.selectArea}>
+                                    <div className={styles.items}>
+                                        {itemsState.map((item, idx) => {
+                                            const isSelected = idx === selectedIndex;
+                                            return (
+                                                <div
+                                                    key={idx}
+                                                    className={styles.item}
+                                                    onClick={(e) => handleClickItem(e, idx)}
+                                                    style={{
+                                                        transform: item.isAnimating
+                                                            ? `rotate(${item.rotationAngle}deg)`
+                                                            : 'none',
+                                                        transition: item.isAnimating
+                                                            ? 'transform 0.2s ease'
+                                                            : 'transform 0.3s ease',
+                                                        zIndex: isSelected ? 2 : "unset",
+                                                    }}
+                                                >
+                                                    {/* 金币飞行的起点 */}
+                                                    <div
+                                                        ref={el => coinRefs.current[idx] = el}
+                                                        className={styles.coinSource}
+                                                        style={{
+                                                            position: 'absolute',
+                                                            width: '100%',
+                                                            height: '100%',
+                                                            opacity: 0
+                                                        }}
+                                                    />
+
+                                                    {/* 锁和开的礼物图 - 互斥显示 */}
+                                                    {item.isOpened ? (
+                                                        <img
+                                                            src={giftOpen}
+                                                            alt=""
+                                                            className={styles.open}
+                                                            style={{ opacity: 1 }}
+                                                        />
+                                                    ) : (
+                                                        <img
+                                                            src={giftLock}
+                                                            alt=""
+                                                            className={styles.lock}
+                                                            style={{ opacity: 1 }}
+                                                        />
+                                                    )}
+
+                                                    {/* 金额显示 - 互斥显示 */}
+                                                    {item.showReward && (
+                                                        <div
+                                                            className={isSelected ? styles.selectReward : styles.reward}
+                                                            style={{
+                                                                opacity: item.showReward ? 1 : 0,
+                                                                transition: 'opacity 0.5s ease'
+                                                            }}
+                                                        >
+                                                            ${rewardData[idx]}
+                                                        </div>
+                                                    )}
+
+                                                    {/* 遮罩 */}
+                                                    {item.showMask && (
+                                                        <div
+                                                            className={styles.mask}
+                                                            style={{
+                                                                opacity: item.showMask ? 1 : 0,
+                                                                transition: 'opacity 0.3s ease'
+                                                            }}
+                                                        />
+                                                    )}
+                                                </div>
+                                            );
+                                        })}
+                                    </div>
+                                </div>
+
+                                <div className={styles.msgPanel}>
+                                    {recharge_rules?.map((rule, index) => {
+                                        return (
+                                            <div className={styles.item} key={index}>
+                                                <div className={styles.itemBox}>
+                                                    <img src={icon_cash} alt="" className={styles.icon_cash} />
+                                                    <span className={styles.price}>${rule?.amount}</span>
+                                                </div>
+                                                <span className={styles.plus}>+</span>
+                                                <div className={styles.itemBox}>
+                                                    <img src={icon_gift} alt="" className={styles.icon_gift} />
+                                                    <span className={styles.spinTimes}>SPIN+{rule?.times}</span>
+                                                </div>
+                                                <div className={styles.buy} onClick={() => handleBuyClick(rule)} >
+                                                    ${rule?.gear?.money}
+                                                </div>
+                                            </div>
+                                        );
+                                    })}
+                                </div>
+                            </div>
+
+                            <img src={img_close} alt="" className={styles.close} onClick={onCheckClose} />
+
+                            <svg className={styles.question} onClick={() => setOpenDetail(true)} >
+                                <use xlinkHref={`${symbol}#icon_question`} />
+                            </svg>
+                        </div>
+
+
+                    </div>
+                </Fade>
+            </Modal >
+
+            <ActRule open={openRule} onClose={() => setOpenRule(false)} />
+            <ActDetail open={openDetail} onClose={() => setOpenDetail(false)} />
+        </>
+    );
+};
+
+export default WHolidaysAct;

+ 537 - 0
src/widgets/WHolidaysAct/styles.module.scss

@@ -0,0 +1,537 @@
+.container {
+    position: fixed;
+    width: 100%;
+    height: 100%;
+    top: 50%;
+    transform: translate(0, -50%);
+    z-index: var(--zIndex-popup);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex-direction: column;
+}
+.activity {
+    display: flex;
+    position: relative;
+    align-items: flex-start;
+    justify-content: center;
+    aspect-ratio: 1;
+    // overflow-y: scroll;
+    width: 25.22rem;
+    height: 44.04rem;
+    // width: 91.4%;
+    border-radius: 10px;
+    background: rgba(37, 38, 43, 1);
+    // margin-top:9.19rem;
+
+    .themeImg {
+        width: 25.22rem;
+        height: 19.63rem;
+        position: absolute;
+        top: 0;
+        left: 0;
+        pointer-events: none;
+    }
+
+    .layout {
+        z-index: 1;
+        width: 100%;
+        height: 100%;
+
+        display: flex;
+        justify-content: flex-start;
+        align-items: center;
+        flex-direction: column;
+
+        .msgBar {
+            width: 23.16rem;
+            height: 1.76rem;
+            border-radius: 60px;
+            background: rgba(19, 20, 27, 0.81);
+            margin-top: 11.98rem;
+            position: relative;
+
+            .left {
+                display: flex;
+                justify-content: center;
+                align-items: center;
+                height: 100%;
+                position: absolute;
+                left: 0;
+
+                img {
+                    width: 1.54rem;
+                    height: 1.47rem;
+                    margin-left: 0.44rem;
+                }
+
+                span {
+                    color: rgba(125, 137, 148, 1);
+
+                    // font-family: Roboto;
+                    font-size: 1.03rem;
+                    font-weight: 400;
+                    line-height: 1.76rem;
+                    letter-spacing: 0rem;
+                    text-align: center;
+                    margin-left: 0.73rem;
+                }
+            }
+            .right {
+                display: flex;
+                justify-content: center;
+                align-items: center;
+                height: 100%;
+                position: absolute;
+                right: 0;
+
+                img {
+                    width: 1.03rem;
+                    height: 1.25rem;
+                    margin-right: 0.36rem;
+                    margin-top: -0.1rem;
+                }
+
+                span {
+                    color: rgba(255, 255, 255, 1);
+
+                    font-family: Roboto;
+                    font-size: 1.03rem;
+                    font-weight: 400;
+                    line-height: 1.76rem;
+                    letter-spacing: 0rem;
+                    text-align: center;
+                    margin-right: 0.07rem;
+                }
+
+                .nums {
+                    color: rgba(240, 255, 74, 1);
+
+                    font-family: Roboto;
+                    font-size: 1.03rem;
+                    font-weight: 400;
+                    line-height: 1.76rem;
+                    letter-spacing: 0rem;
+                    text-align: center;
+                    margin-right: 0.36rem;
+                }
+            }
+        }
+        .selectArea {
+            width: 23.68rem;
+            height: 17.65rem;
+            margin-top: 1.25rem;
+            position: relative;
+
+            .items {
+                width: 100%;
+                height: 100%;
+
+                display: flex;
+                justify-content: space-between;
+                align-items: center;
+                flex-wrap: wrap;
+
+                .item {
+                    width: 7.57rem;
+                    height: 5.59rem;
+                    border-radius: 7px;
+                    // background-color: #fff;
+                    position: relative;
+                    transition: transform 0.2s ease; /* 控制旋转过渡 */
+
+                    .lock {
+                        position: absolute;
+                        width: 7.57rem;
+                        top: 0;
+                        left: 0;
+                    }
+                    .open {
+                        position: absolute;
+                        width: 7.57rem;
+                        top: 0;
+                        left: 0;
+                    }
+
+                    .reward {
+                        position: absolute;
+                        bottom: 0.07rem;
+                        left: 50%;
+                        transform: translateX(-50%);
+
+                        width: 4.71rem;
+                        height: 1.32rem;
+                        border-radius: 25px;
+                        background: rgba(0, 0, 0, 0.5);
+
+                        color: rgba(255, 255, 255, 1);
+
+                        font-family: Roboto;
+                        font-size: 1.03rem;
+                        font-weight: 400;
+                        line-height: 1.32rem;
+                        letter-spacing: 0rem;
+                        text-align: center;
+                    }
+                    .selectReward {
+                        position: absolute;
+                        bottom: 0.07rem;
+                        left: 50%;
+                        transform: translateX(-50%);
+
+                        width: 4.71rem;
+                        height: 1.32rem;
+                        border-radius: 25px;
+                        box-shadow: inset 0rem 1px 1px 0rem
+                            rgba(255, 205, 113, 1);
+                        background: rgba(248, 171, 28, 1);
+
+                        color: rgba(255, 255, 255, 1);
+
+                        font-family: Roboto;
+                        font-size: 1.03rem;
+                        font-weight: 400;
+                        line-height: 1.32rem;
+                        letter-spacing: 0rem;
+                        text-align: center;
+                    }
+
+                    .mask {
+                        width: 100%;
+                        height: 100%;
+                        position: absolute;
+                        top: 0;
+                        left: 0;
+                        background: rgba(0, 0, 0, 0.5);
+                        border-radius: 7px;
+                    }
+                }
+            }
+        }
+    }
+    .msgPanel {
+        width: 23.16rem;
+        border-radius: 8px;
+        // position: absolute;
+        // top: 25.59rem;
+        padding: 0 1rem;
+        display: flex;
+        justify-content: flex-start;
+        align-items: flex-start;
+        flex-direction: column;
+        // top: calc(25.59rem - 7.65rem) !important;
+        margin-top: 0.29rem;
+
+        .item {
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+            height: 2.06rem;
+            width: 100%;
+            margin-top: 1.1rem;
+
+            .itemBox {
+                display: flex;
+                align-items: center;
+                justify-content: center;
+            }
+
+            .icon_cash {
+                width: 1.22rem;
+                height: 1.01rem;
+                margin-right: 0.28rem;
+            }
+            .icon_gift {
+                width: 1.32rem;
+                height: 1.47rem;
+                margin-right: 0.37rem;
+            }
+
+            .price {
+                color: rgba(255, 255, 255, 1);
+                font-family: Roboto;
+                font-size: 1.03rem;
+                font-weight: 500;
+                line-height: 1.18rem;
+                letter-spacing: 0rem;
+                // margin-right: 1.6rem;
+                line-height: 2.06rem;
+            }
+
+            .plus {
+                color: rgba(255, 255, 255, 1);
+                font-family: Roboto;
+                font-size: 1.47rem;
+                font-weight: 500;
+                line-height: 1.69rem;
+                letter-spacing: 0rem;
+                // margin-right: 1.1rem;
+                line-height: 2.06rem;
+            }
+
+            .spinTimes {
+                color: rgba(255, 255, 255, 1);
+                font-family: Roboto;
+                font-size: 1.03rem;
+                font-weight: 500;
+                line-height: 1.18rem;
+                letter-spacing: 0rem;
+                line-height: 2.06rem;
+                // margin-right: 2.5rem;
+            }
+
+            .buy {
+                width: 5.44rem;
+                height: 2.06rem;
+
+                border-radius: 43rem;
+                background: linear-gradient(
+                    180deg,
+                    rgba(7, 199, 115, 1),
+                    rgba(1, 153, 86, 1) 100%
+                );
+                color: rgba(255, 255, 255, 1);
+
+                font-family: Roboto;
+                font-size: 1.03rem;
+                font-weight: 500;
+                letter-spacing: 0rem;
+                line-height: 2.06rem;
+                text-align: center;
+            }
+        }
+    }
+}
+
+.close {
+    width: 1.91rem;
+    height: 1.76rem;
+    position: absolute;
+    z-index: var(--zIndex-max);
+    right: 0;
+    top: -3.37rem;
+}
+
+.question {
+    position: absolute;
+    width: 1.18rem;
+    height: 1.18rem;
+    top: 0.58rem;
+    right: 0.88rem;
+    z-index: 2;
+}
+
+// 金币飞行动画核心样式
+.flyingCoin {
+    position: fixed;
+    width: 20px;
+    height: 20px;
+    z-index: var(--zIndex-max, 10000);
+    pointer-events: none;
+    will-change: transform, opacity;
+    transition: all 1.2s cubic-bezier(0.34, 1.56, 0.64, 1),
+        opacity 1s ease-in 0.2s; /* opacity延迟0.2s开始,持续1s */
+}
+
+// rule
+.rule_container {
+    position: fixed;
+    left: 50%;
+    top: 50%;
+    transform: translate(-50%, -50%);
+    z-index: var(--zIndex-popup);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: 100%;
+
+    .rule_popup {
+        width: 22.87rem;
+        height: 24.49rem;
+        padding: 1.1rem;
+        background-color: #272d39;
+        display: flex;
+        align-items: center;
+        justify-content: flex-start;
+        flex-direction: column;
+        border-radius: 10px;
+
+        .rule_title {
+            width: 100%;
+            color: rgba(255, 255, 255, 1);
+
+            font-family: Roboto;
+            font-size: 1.32rem;
+            font-weight: 500;
+            text-align: left;
+        }
+
+        .rule_texts {
+            display: flex;
+            align-items: flex-start;
+            justify-content: center;
+            flex-direction: column;
+            // gap: 0.588rem;
+            .rule_text {
+                display: flex;
+                margin-top: 1.1rem;
+
+                span {
+                    color: #ffffff;
+                    font-family: Roboto;
+                    font-weight: 400;
+                    vertical-align: baseline;
+                    line-height: 1.3;
+
+                    font-size: 1.18rem;
+                    font-weight: 400;
+                }
+
+                // &::before {
+                //     background-color: #ffffff;
+                //     border-radius: 50%;
+                //     content: "";
+                //     display: block;
+                //     flex: none;
+                //     height: 0.36rem;
+                //     width: 0.36rem;
+                //     margin: 0.4rem 0.73rem;
+                // }
+            }
+        }
+
+        .rule_btnClaim {
+            width: 11rem;
+            height: 2.64rem;
+            margin-top: 2.8rem;
+            background: linear-gradient(
+                180deg,
+                rgb(8, 201, 116),
+                rgb(0, 149, 83) 100%
+            );
+
+            text-align: center;
+            border-radius: 1.47rem;
+            font-size: 1.02rem;
+            font-weight: 700;
+            color: #ffffff;
+            line-height: 2.64rem;
+
+            width: 19.63rem;
+            height: 2.79rem;
+
+            display: flex;
+            align-items: center;
+            justify-content: center;
+
+            img {
+                width: 1.03rem;
+                margin: 0 0.3rem;
+                margin-top: -0.2rem;
+            }
+        }
+    }
+}
+
+// detail
+.detail_container {
+    position: fixed;
+    left: 50%;
+    top: 50%;
+    transform: translate(-50%, -50%);
+    z-index: var(--zIndex-popup);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: 100%;
+
+    .detail_popup {
+        width: 22.87rem;
+        height: 27.28rem;
+        padding: 1.1rem;
+        // background-color: #272d39;
+        display: flex;
+        align-items: center;
+        justify-content: flex-start;
+        flex-direction: column;
+        border-radius: 10px;
+        padding-left: 0.8rem;
+        padding-right: 0;
+
+        box-shadow: inset 0rem 0rem 2px 2px rgba(91, 99, 129, 1);
+        background: linear-gradient(
+            180deg,
+            rgba(68, 79, 138, 1),
+            rgba(37, 38, 43, 1) 52%
+        );
+        position: relative;
+
+        .detailGift {
+            width: 3.28rem;
+            height: 3.6rem;
+            transform: rotate(6.13deg);
+            position: absolute;
+            top: 0.6rem;
+            right: 0.375rem;
+        }
+
+        .detail_title {
+            width: 100%;
+            color: rgba(255, 255, 255, 1);
+
+            font-family: Roboto;
+            font-size: 1.32rem;
+            font-weight: 500;
+            text-align: left;
+        }
+
+        .detail_texts {
+            display: flex;
+            align-items: flex-start;
+            justify-content: center;
+            flex-direction: column;
+            margin-top: 0.61rem;
+            // gap: 0.588rem;
+            .detail_text {
+                display: flex;
+                margin-top: 0.51rem;
+
+                span {
+                    color: #ffffff;
+                    font-family: Roboto;
+                    font-weight: 400;
+                    vertical-align: baseline;
+                    line-height: 1.3;
+
+                    font-size: 1.18rem;
+                    font-weight: 400;
+                }
+            }
+        }
+
+        .detail_btnOK {
+            width: 11rem;
+            height: 2.64rem;
+            margin-top: 2.42rem;
+            background: linear-gradient(
+                180deg,
+                rgb(8, 201, 116),
+                rgb(0, 149, 83) 100%
+            );
+
+            text-align: center;
+            border-radius: 1.47rem;
+            font-size: 1.02rem;
+            font-weight: 700;
+            color: #ffffff;
+            line-height: 2.64rem;
+
+            width: 19.63rem;
+            height: 2.79rem;
+
+            display: flex;
+            align-items: center;
+            justify-content: center;
+        }
+    }
+}

+ 63 - 54
src/widgets/WHolidaysGift/index.jsx

@@ -1,18 +1,18 @@
 import styles from "./styles.module.scss";
-import d_bg from "@assets/holidaysGift/d.png";
+// import d_bg from "@assets/holidaysGift/d.png";
 import img_close from "@assets/holidaysGift/close.png";
-import img_theme from "@assets/holidaysGift/theme.png";
+// import img_theme from "@assets/holidaysGift/theme.png";
 import icon_cash from "@assets/holidaysGift/cash.png";
 import icon_gift from "@assets/holidaysGift/gift.png";
-import spinBg from "@assets/holidaysGift/spinBg.png";
+// import spinBg from "@assets/holidaysGift/spinBg.png";
 import styled from 'styled-components/macro';
 import React, { useEffect, useRef, useState } from "react";
 import spin_1 from "@assets/wheel/spin-1.png";
 import spin_2 from "@assets/wheel/spin-2.png";
 import spin_4 from "@assets/wheel/spin-4.png";
 import spin_5 from "@assets/wheel/spin-5.png";
-import draw from "@assets/holidaysGift/draw.png";
-import light1 from "@assets/holidaysGift/light1.png";
+// import draw from "@assets/holidaysGift/draw.png";
+// import light1 from "@assets/holidaysGift/light1.png";
 import light2 from "@assets/holidaysGift/light2.png";
 import { useDispatch, useSelector } from "react-redux";
 import { getIsHelp, getTurnplate, updateCollect, updateTimes } from "@features/turnplateSlice";
@@ -23,6 +23,7 @@ import CountUp from "react-countup";
 import { Fade, Grow, Modal } from "@mui/material";
 import usePaymentActions from "@hooks/usePaymentActions";
 import { reqGameInfo, reqHolidaysGiftSpin } from "@features/api";
+import HOLIDAYS from "@constants/holidays";
 const i18n = global.i18n;
 const spinImgSrc = [
     null,
@@ -33,9 +34,6 @@ const spinImgSrc = [
     spin_5
 ];
 const spinImg = [];
-const StyledCanvas = styled.canvas`
-    background-image:url('${spinBg}');
-`;
 const turnObj = {
     timer: null,
     cdTimer: null,
@@ -46,6 +44,16 @@ const turnObj = {
     resetTimer: null,
     ishelp: false
 };
+
+const CURRENT_HOLIDAY = HOLIDAYS.Christmas;
+const getThemeImg = (imgName) => {
+    const img = require(`@assets/holidaysGift/${CURRENT_HOLIDAY.name}/${imgName}.png`);
+    return img;
+};
+const StyledCanvas = styled.canvas`
+background-image:url('${getThemeImg("spinBg")}');
+`;
+
 const WHolidaysGift = () => {
     const info = useSelector(getHolidaysGiftInfo);
     // console.error("holidays info", info);
@@ -74,9 +82,13 @@ const WHolidaysGift = () => {
     const [isLock, setIsLock] = useState(false);
 
 
+    const statusRef = useRef(status);
+    useEffect(() => {
+        statusRef.current = status;
+    }, [status]);
 
-
-    const show = (type) => {
+    const show = () => {
+        if (statusRef.current === 0) return global.openNextPopup();;
         setOpen(true);
     };
     const hide = () => {
@@ -105,7 +117,7 @@ const WHolidaysGift = () => {
         }
     };
     const onTurnResult = (res) => {
-        console.error("onTurnResult", res);
+        // console.error("onTurnResult", res);
         // return;
         if (res.code !== 200) {
             resetWheel(0);
@@ -318,7 +330,7 @@ const WHolidaysGift = () => {
     const drawWheel = (result) => {
         let draw = drawRef.current;
         if (draw == null) return;
-        let colors = ["#FFFFFF", "#8960DD"];
+        let colors = CURRENT_HOLIDAY.gift.color;
         let color_len = colors.length;
         let fontSizeNum = 18;
 
@@ -422,8 +434,8 @@ const WHolidaysGift = () => {
                         startSpin();
                     }}>
                         {spinTimes > 0 && !isLock ? <img src={light2} className={`${styles.light2} ${styles.scale}`} /> : <></>}
-                        <img src={draw} className={styles.spin_img} />
-                        {spinTimes > 0 && !isLock ? <img src={light1} className={`${styles.light1} ${styles.blink}`} /> : <></>}
+                        <img src={getThemeImg("draw")} className={styles.spin_img} />
+                        {spinTimes > 0 && !isLock ? <img src={getThemeImg("light1")} className={`${styles.light1} ${styles.blink}`} /> : <></>}
 
                         <div className={styles.spin_content}>
                             {
@@ -478,55 +490,52 @@ const WHolidaysGift = () => {
     };
 
     if (!!!status) return <></>;
+
     return (
         <>
             <Modal open={open} onClose={onCheckClose} closeAfterTransition>
                 <Fade in={open} timeout={500}>
-                    <div className={styles.container}>
-                        <Grow in={open} timeout={500}>
-                            <div className={styles.wheel}>
-                                {/* <img src={bg_light} className={styles.bg_light} /> */}
-                                <div className={styles.wheelBox}>
-                                    <img src={d_bg} className={styles.lamp} />
-                                    <div className={styles.drawContent}>
-                                        <StyledCanvas ref={drawRef} className={styles.draw} style={drawStyle} />
-                                    </div>
-                                    {/* <img src={light1} className={`${styles.light} ${styles.light_ani_1}`} /> */}
-                                    {/* <img src={light2} className={`${styles.light} ${styles.light_ani_2}`} /> */}
-                                    {/* <img src={pointer} className={styles.pointer} /> */}
-                                    {
-                                        renderSpinInfo()
-                                    }
-
+                    <div className={`${styles.container}  ${styles[CURRENT_HOLIDAY.name]}`}>
+                        {/* <Grow in={open} timeout={500}> */}
+                        <div className={styles.wheel}>
+                            <div className={`${styles.wheelBox}`}>
+                                <img src={getThemeImg("bg")} className={styles.bg} />
+                                <div className={styles.drawContent}>
+                                    <StyledCanvas ref={drawRef} className={styles.draw} style={drawStyle} />
                                 </div>
+                                {
+                                    CURRENT_HOLIDAY.gift.decorate ? <img src={getThemeImg("decorate")} className={styles.decorate} /> : <></>
+                                }
+                                {renderSpinInfo()}
+                            </div>
 
-                                <img src={img_theme} alt="" className={styles.themeImg} />
+                            <img src={getThemeImg("theme")} alt="" className={styles.themeImg} />
 
-                                <div className={styles.msgPanel}>
-                                    <div className={styles.title}>
-                                        Complete The Recharge And Draw A Prize To GetUp To 100% Extra Reward
-                                    </div>
-                                    {
-                                        recharge_rules?.map((rule, index) => {
-                                            return (
-                                                <div className={styles.item} key={index}>
-                                                    <div className={styles.itemBox}>
-                                                        <img src={icon_cash} alt="" className={styles.icon_cash} />
-                                                        <span className={styles.price}>${rule?.amount}</span>
-                                                    </div>
-                                                    <span className={styles.plus}>+</span>
-                                                    <div className={styles.itemBox}>
-                                                        <img src={icon_gift} alt="" className={styles.icon_gift} />
-                                                        <span className={styles.spinTimes}>SPIN+{rule?.times}</span>
-                                                    </div>
-                                                    <div className={styles.buy} onClick={() => handleBuyClick(rule)} >${rule?.gear?.money}</div>
-                                                </div>
-                                            );
-                                        })
-                                    }
+                            <div className={styles.msgPanel}>
+                                <div className={styles.title}>
+                                    Complete The Recharge And Draw A Prize To GetUp To 100% Extra Reward
                                 </div>
+                                {
+                                    recharge_rules?.map((rule, index) => {
+                                        return (
+                                            <div className={styles.item} key={index}>
+                                                <div className={styles.itemBox}>
+                                                    <img src={icon_cash} alt="" className={styles.icon_cash} />
+                                                    <span className={styles.price}>${rule?.amount}</span>
+                                                </div>
+                                                <span className={styles.plus}>+</span>
+                                                <div className={styles.itemBox}>
+                                                    <img src={icon_gift} alt="" className={styles.icon_gift} />
+                                                    <span className={styles.spinTimes}>SPIN+{rule?.times}</span>
+                                                </div>
+                                                <div className={styles.buy} onClick={() => handleBuyClick(rule)} >${rule?.gear?.money}</div>
+                                            </div>
+                                        );
+                                    })
+                                }
                             </div>
-                        </Grow>
+                        </div>
+                        {/* </Grow> */}
 
                         <img src={img_close} alt="" className={styles.close} onClick={onCheckClose} />
                     </div>

+ 108 - 18
src/widgets/WHolidaysGift/styles.module.scss

@@ -45,7 +45,7 @@
     // overflow-y: scroll;
     height: 42.64rem;
 
-    & .bg_light {
+    .bg_light {
         position: absolute;
         // z-index: 0;
         animation: bg_light_ani 20s linear infinite;
@@ -62,7 +62,8 @@
         transform: translate(-50%, 0);
         // top: 7.65rem;
         z-index: 1;
-        .lamp {
+
+        .bg {
             position: absolute;
             height: auto;
             width: 26.47rem;
@@ -90,7 +91,7 @@
         }
     }
 
-    & .light {
+    .light {
         position: absolute;
         top: 25%;
         width: 62%;
@@ -98,19 +99,19 @@
         z-index: 3;
         animation: breathe 1.2s infinite;
     }
-    & .pointer {
+    .pointer {
         position: absolute;
         z-index: 4;
         width: 21%;
         top: 23%;
     }
-    & .light_ani_1 {
+    .light_ani_1 {
         animation-delay: 0s;
     }
-    & .light_ani_2 {
+    .light_ani_2 {
         animation-delay: 0.6s;
     }
-    & .spin_con {
+    .spin_con {
         position: absolute;
         z-index: 5;
         width: 18%;
@@ -122,7 +123,7 @@
         width: 100%;
         height: 100%;
     }
-    & .spin_btn {
+    .spin_btn {
         position: relative;
         width: 100%;
         // height: 9.26rem;
@@ -132,15 +133,15 @@
         top: 50%;
         left: 50%;
         transform: translate(-50%, -50%);
-        & .spin_img {
+        .spin_img {
             width: 8.24rem;
-            height: 9.26rem;
+            // height: 9.26rem;
             position: absolute;
             top: 50%;
             left: 50%;
             transform: translate(-50%, -50%);
         }
-        & .light1 {
+        .light1 {
             width: 9.41rem;
             height: 10.15rem;
             position: absolute;
@@ -148,7 +149,7 @@
             left: 50%;
             transform: translate(-50%, -50%);
         }
-        & .light2 {
+        .light2 {
             width: 14.78rem;
             height: 14.78rem;
             position: absolute;
@@ -156,7 +157,7 @@
             left: 50%;
             transform: translate(-50%, -50%);
         }
-        & .spin_content {
+        .spin_content {
             // display: flex;
             // align-items: center;
             // justify-content: center;
@@ -173,7 +174,7 @@
             width: 100%;
             height: 100%;
         }
-        & .spin_num {
+        .spin_num {
             font-size: 1.18rem;
             color: #71301f;
             font-family: Roboto;
@@ -182,7 +183,7 @@
             margin-left: 66%;
             margin-top: 55%;
         }
-        & .collect_num {
+        .collect_num {
             font-family: "Roboto";
             font-size: 1.2rem;
             font-weight: 600;
@@ -305,6 +306,95 @@
         }
     }
 }
+.Christmas {
+    .wheelBox {
+        top: 0.5rem;
+        .bg {
+            width: 22.72rem;
+            height: auto;
+            top: 50%;
+            left: 50%;
+            transform: translate(-50%, -50%);
+        }
+
+        .drawContent {
+            width: 19.19rem;
+            top: 51%;
+            left: 50%;
+            transform: translate(-50%, -50%);
+        }
+
+        .spin_con {
+            position: absolute;
+            z-index: 5;
+
+            top: 50%;
+            left: 50%;
+            transform: translate(-50%, -50%);
+            width: 100%;
+            height: 100%;
+        }
+
+        .spin_btn {
+            width: 6.32rem;
+            height: 8.16rem;
+            .spin_img {
+                width: 6.32rem;
+                height: 8.16rem;
+                position: absolute;
+                top: 50%;
+                left: 50%;
+                transform: translate(-50%, -50%);
+            }
+            .light1 {
+                width: 9.41rem;
+                height: 10.15rem;
+                position: absolute;
+                top: 50%;
+                left: 50%;
+                transform: translate(-50%, -50%);
+            }
+            .light2 {
+                width: 14.78rem;
+                height: 14.78rem;
+                position: absolute;
+                top: 50%;
+                left: 50%;
+                transform: translate(-50%, -50%);
+            }
+            .spin_num {
+                font-size: 1.03rem;
+                color: #fffeab;
+                margin-left: 66%;
+                margin-top: 70%;
+                text-shadow: 1.5px 1.5px 0 rgba(32, 157, 47, 1),
+                    /* 右下 */ -1.5px 1.5px 0 rgba(32, 157, 47, 1),
+                    /* 左下 */ 1.5px -1.5px 0 rgba(32, 157, 47, 1),
+                    /* 右上 */ -1.5px -1.5px 0 rgba(32, 157, 47, 1); /* 左上 */
+            }
+        }
+        .decorate {
+            position: absolute;
+            z-index: 5;
+
+            top: 44%;
+            left: 50%;
+            transform: translate(-50%, -50%);
+            width: 23.16rem;
+            width: 27rem;
+            // height: 25.66rem;
+        }
+    }
+
+    .themeImg {
+        /* qianbg 1 */
+        width: 23.16rem;
+        height: 15.96rem;
+        left: 50%;
+        transform: translate(-50%, 0);
+        top: 13.93rem !important;
+    }
+}
 .cd_con {
     display: flex;
     align-items: center;
@@ -313,7 +403,7 @@
     color: rgb(135, 149, 177);
     font-size: 0.9rem;
     font-weight: 600;
-    & .text {
+    .text {
         color: var(--header);
     }
 }
@@ -330,12 +420,12 @@
     width: 100%;
     justify-content: space-between;
     align-items: center;
-    & .icon {
+    .icon {
         fill: var(--header);
         width: 1.6rem;
         height: 1.6rem;
     }
-    & .text {
+    .text {
         font-size: 1rem;
         color: var(--header);
         text-align: center;

+ 137 - 0
src/widgets/WInstallBox/WDownloadPrompt/index.jsx

@@ -0,0 +1,137 @@
+import {useEffect, useState} from "react";
+import styles from "./styles.module.scss";
+import top_icon from "@assets/downloadPrompt/top_icon.webp"
+import icon_1 from "@assets/downloadPrompt/icon_1.webp";
+import icon_2 from "@assets/downloadPrompt/icon_2.webp";
+import icon_3 from "@assets/downloadPrompt/icon_3.webp";
+import Popup from "@components/Popup";
+import LazyImage from "@components/LazyImage";
+import {useSelector} from "react-redux";
+import {getDownloadConf} from "@features/mainSlice";
+import {formatCash, stringFormat, isIOS} from "@utils/helpers";
+const i18n = global.i18n;
+
+const WDownloadPrompt = ()=>{
+    const [isShow, setIsShow] = useState(false);
+    const downConf=useSelector(getDownloadConf)
+    const show=()=>{
+        setIsShow(true);
+    }
+    const hide=()=>{
+        setIsShow(false);
+    }
+    const onClose=()=>{
+        hide();
+        global.openNextPopup();
+    }
+    const getDownBonus=()=>{
+        let bonus=Number(downConf?.bonus);
+        return global.DOLLAR+formatCash(bonus,0);
+    }
+    const handleClickDownload=()=>{
+        hide();
+        global["showShortcutGuide"] && global["showShortcutGuide"]();
+    }
+    useEffect(()=>{
+        global.downloadPrompt={
+            show,
+            hide
+        };
+        return ()=>{
+
+        }
+    },[]);
+    const renderIosContent = () => {
+        return (
+            <Popup onClose={onClose} open={isShow} btnCloseType={0} popupClass={styles.popup}>
+                <div className={styles.container}>
+                    <LazyImage src={top_icon} alt={""} className={styles.icon}></LazyImage>
+                    <div className={styles.title}>{stringFormat(i18n.t("downloadPrompt.title"),[getDownBonus()])}</div>
+                    <div className={styles.sub_title}>{i18n.t("downloadPrompt.subtitle")}</div>
+                    <div className={styles.items_con}>
+                        <div className={styles.item}>
+                            <img src={icon_1} alt={""} className={styles.item_icon}/>
+                            <div className={styles.item_text}>
+                                {i18n.t("downloadPrompt.tips_1")}
+                                <span>{i18n.t("downloadPrompt.tips_2")}</span>
+                            </div>
+                        </div>
+                        <div className={styles.item}>
+                            <img src={icon_2} alt={""} className={styles.item_icon}/>
+                            <div className={styles.item_text}>
+                                {i18n.t("downloadPrompt.tips_3")}
+                                <span>{getDownBonus()}</span>
+                            </div>
+                        </div>
+                        <div className={styles.item}>
+                            <img src={icon_3} alt={""} className={styles.item_icon}/>
+                            <div className={styles.item_text} style={{fontSize:"0.95rem"}}>
+                                {i18n.t("downloadPrompt.tips_4")}
+                            </div>
+                        </div>
+                    </div>
+                    <button className={styles.btn_down} onClick={handleClickDownload}>
+                        {i18n.t("downloadPrompt.btn_down")}
+                    </button>
+                </div>
+            </Popup>
+        )
+    }
+    const renderAndroidContent = () => {
+        return (
+            <Popup onClose={onClose} open={isShow} btnCloseType={0} popupClass={styles.popup_ad}>
+                <div className={styles.container_ad}>
+                    {/* 1. 顶部红色/粉色标题区域 */}
+                    <div className={styles.header_section}>
+                        <div className={styles.title_ad}>
+                            {/* 假设 i18n 这里的配置能支持拆分,或者手动拆开 */}
+                            <div className={styles.title_top}>{stringFormat(i18n.t("downloadPrompt.title"),[getDownBonus()])}</div>
+                            <div className={styles.title_bottom}>{i18n.t("downloadPrompt.subtitle_ad")}</div>
+                        </div>
+                        
+                        {/* 右上角黄色下载盒图标 */}
+                        <div className={styles.icon_box}>
+                            <div className={styles.arrow_down}></div>
+                        </div>
+                    </div>
+
+                    {/* 2. 中间黑色内容区域 */}
+                    <div className={styles.content_section}>
+                        {/* "Tap Download To Get" */}
+                        <div className={styles.item_text_main}>
+                            {i18n.t("downloadPrompt.tips_1_ad")}
+                        </div>
+
+                        {/* 可变金额 "$5" */}
+                        <div className={styles.bonus_value}>
+                            {getDownBonus()}
+                        </div>
+
+                        {/* "Start Games Faster" */}
+                        <div className={styles.item_text_sub}>
+                            {i18n.t("downloadPrompt.tips_2_ad")}
+                        </div>
+
+                        {/* "Save your account..." (橙色提示) */}
+                        <div className={styles.hint_text}>
+                            {i18n.t("downloadPrompt.tips_3_ad")}
+                        </div>
+
+                        {/* 下载按钮 */}
+                        <button className={styles.btn_down} onClick={handleClickDownload}>
+                            {i18n.t("downloadPrompt.btn_down")}
+                        </button>
+                    </div>
+                </div>
+            </Popup>
+        )
+    }
+    return (
+        <>
+        {
+            isIOS() ? renderIosContent() : renderAndroidContent()
+        }
+        </>
+    )
+}
+export default WDownloadPrompt;

+ 215 - 0
src/widgets/WInstallBox/WDownloadPrompt/styles.module.scss

@@ -0,0 +1,215 @@
+$bg_url:"../../../assets/downloadPrompt/bg.webp";
+$bg_url_ad:"../../../assets/downloadPrompt/apk_modal_bg.webp";
+.popup{
+  position: absolute;
+  top: 15vh;
+  left: 1.6rem;
+  right: 1.6rem;
+  width: auto;
+  overflow: unset;
+  padding: 0;
+  border-radius: 0.36rem;
+  background-color: var(--darkColor3);
+  max-width: 512px;
+  & .container{
+    position: relative;
+    width: 100%;
+    align-items: center;
+    background: url($bg_url) no-repeat 0 0;
+    background-size: 100% 10.2rem;
+    display: flex;
+    flex-direction: column;
+    height: auto;
+    justify-content: center;
+    padding: 2.2rem 1.1rem;
+    border-radius: 0.36rem;
+    & .icon{
+      height: 8.54rem;
+      left: 0.88rem;
+      position: absolute;
+      top: -1.21rem;
+      width: 7.44rem;
+      img{
+        width: 100%;
+        height: 100%;
+      }
+    }
+    & .title{
+      color: var(--warningColor);
+      font-size: 2.56rem;
+      font-weight: 600;
+      left: 9.27rem;
+      line-height: 3.0rem;
+      position: absolute;
+      text-align: left;
+      text-transform: uppercase;
+      top: 1.14rem;
+    }
+    & .sub_title{
+      color: var(--warningColor);
+      font-size: 1.76rem;
+      font-weight: 600;
+      left: 9.27rem;
+      line-height: 2.05rem;
+      position: absolute;
+      text-align: left;
+      text-transform: uppercase;
+      top: 4.22rem;
+    }
+    & .items_con{
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: center;
+      width: 100%;
+      margin-top: 8.8rem;
+      gap: 1.1rem;
+      & .item{
+        align-items: center;
+        background-color: var(--darkColor1);
+        border-radius: 0.36rem;
+        display: flex;
+        flex-direction: row;
+        gap: 1.21rem;
+        height: 3.22rem;
+        justify-content: flex-start;
+        padding: 0.73rem 0 0.73rem 0.73rem;
+        width: 100%;
+        & .item_icon{
+          height: 1.76rem;
+          width: 1.76rem;
+        }
+        & .item_text{
+          color: var(--textColor);
+          font-size: 1.02rem;
+          font-weight: 900;
+          line-height: 1.39rem;
+          text-align: left;
+          span{
+            color: var(--warningColor);
+            font-weight: 900;
+          }
+        }
+      }
+    }
+    & .btn_down{
+      flex: none;
+      font-size: 1.17rem;
+      font-weight: 700;
+      height: 2.93rem;
+      line-height: 1.39rem;
+      margin-top: 2.93rem;
+      width: 22rem;
+      display: block;
+      border-radius: 3.3rem;
+      padding: 0.36rem 1.1rem;
+      background: var(--buttonColor);
+      color: var(--textColor);
+      cursor: pointer;
+      position: relative;
+      transition: background .4s;
+    }
+  }
+}
+
+.popup_ad {
+  position: absolute;
+  top: 25vh;
+  left: 1.6rem;
+  right: 1.6rem;
+  width: auto;
+  overflow: unset;
+  padding: 0;
+  border-radius: 0.36rem;
+  background-color: var(--darkColor3);
+  max-width: 512px;
+
+  height: auto;
+  background: url($bg_url_ad) no-repeat center top;
+  background-size: contain;
+}
+
+.container_ad {
+  position: relative;
+  width: 100%;
+  align-items: center;
+  display: flex;
+  flex-direction: column;
+  height: auto;
+  justify-content: flex-start;
+  padding: 3.2rem 1.1rem 3.2rem;
+  border-radius: 0.36rem;
+  
+  // 顶部标题部分
+  .header_section {
+    position: absolute;
+    top: 2.0rem;
+    left: 4.7rem;
+    display: flex;
+    justify-content: flex-start;
+    align-items: center;
+
+    .title_ad {
+      color: #fff176; // 亮黄色
+      font-weight: 900;
+      // font-style: italic;
+      line-height: 1.1;
+      text-align: center;
+      
+      .title_top { font-size: 28px; }
+      .title_bottom { font-size: 28px; }
+    }
+  }
+
+  // 下半部分内容
+  .content_section {
+    padding: 7.2rem 0 0;
+    text-align: center;
+    width: 100%;
+
+    .item_text_main {
+      color: white;
+      font-size: 1.5rem;
+      // font-weight: bold;
+      margin-bottom: 2.4rem;
+    }
+
+    .bonus_value {
+      color: #ffca28;
+      font-size: 3rem;
+      font-weight: 900;
+      margin: 30px 0;
+    }
+
+    .item_text_sub {
+      color: white;
+      font-size: 1.5rem;
+      // font-weight: bold;
+      margin-bottom: 30px;
+    }
+
+    .hint_text {
+      color: #ffa726; // 橙色
+      font-size: 0.9rem;
+      margin-bottom: 0px;
+    }
+
+    .btn_down{
+      flex: none;
+      font-size: 1.17rem;
+      font-weight: 700;
+      height: 2.93rem;
+      line-height: 1.39rem;
+      margin-top: 0.33rem;
+      width: 22rem;
+      display: block;
+      border-radius: 3.3rem;
+      padding: 0.36rem 1.1rem;
+      background: var(--buttonColor);
+      color: var(--textColor);
+      cursor: pointer;
+      position: relative;
+      transition: background .4s;
+    }
+  }
+}

+ 82 - 0
src/widgets/WInstallBox/WPopPwaBonus/index.jsx

@@ -0,0 +1,82 @@
+import {useEffect, useState} from "react";
+import {useDispatch, useSelector} from "react-redux";
+import {getDownloadConf, getPwaCliamStatus, getUserVip} from "@features/mainSlice";
+import {formatCash, isFrequentTap} from "@utils/helpers";
+import styles from "./styles.module.scss";
+import Popup from "@components/Popup";
+import img_gift_package from "@assets/pwabonus/gift_package.webp"
+import LazyImage from "@components/LazyImage";
+import {REQ_STATUS} from "@api/config";
+import SmallLoading from "@components/SmallLoading";
+import {reqFreeWithdraw, reqGetPwaBonus} from "@features/api";
+import { useNavigate } from "react-router-dom";
+import { ROUTE_PATH } from "@constants/routes";
+import { useWithdrawMission } from "@contexts/withdrawMissionContext";
+const i18n = global.i18n;
+const WPopPwaBonus = () => {
+    const [isShow, setIsShow] = useState(false);
+    const downConf=useSelector(getDownloadConf)
+    const claimStaus=useSelector(getPwaCliamStatus)
+    const dispatch = useDispatch();
+    const navigate = useNavigate();
+    const { open: missionOpen, setOpen: setMissionOpen } = useWithdrawMission();
+    const vip = useSelector(getUserVip);
+
+    const show=()=>{
+        setIsShow(true);
+    }
+    const hide=()=>{
+        setIsShow(false);
+        global.openNextPopup();
+    }
+    const getDownBonus=()=>{
+        let bonus=Number(downConf?.bonus);
+        return global.DOLLAR+formatCash(bonus,0);
+    }
+    const onClaimCallback=(payload)=>{
+        if (payload && payload.code === 200) {
+            global.showDropCoins();
+
+            // 领取奖励后展示提现界面或者提现任务
+            if (vip > 0) setMissionOpen(true);
+            else navigate(ROUTE_PATH.FREEWITHDRAW);
+            hide();
+        }
+    }
+    const handleClickClaim=()=>{
+        if (claimStaus===REQ_STATUS.LOADING)return;
+        if (isFrequentTap("claim_pwa_bons"))return;
+        dispatch(reqGetPwaBonus({
+            callback:onClaimCallback
+        }));
+    }
+    useEffect(()=>{
+        global.popPwaBonus={
+            show,
+            hide
+        };
+        return ()=>{
+
+        }
+    },[]);
+    return (
+        <Popup onClose={() => {
+            hide();
+        }} open={isShow} btnCloseType={0} popupClass={styles.popup}>
+            <div className={styles.container}>
+                <LazyImage src={img_gift_package} alt={""} className={styles.gift_package}></LazyImage>
+                <div className={styles.title}>{i18n.t("popPwaBonus.title")}</div>
+                <div className={styles.desc}>
+                    {i18n.t("popPwaBonus.desc")}
+                    <span>{" "+getDownBonus()}</span>
+                </div>
+                <button className={styles.action} onClick={handleClickClaim}>
+                    {
+                        claimStaus===REQ_STATUS.LOADING?(<SmallLoading fill='#fff'/>):(i18n.t("popPwaBonus.claim"))
+                    }
+                </button>
+            </div>
+        </Popup>
+)
+}
+export default WPopPwaBonus;

+ 55 - 0
src/widgets/WInstallBox/WPopPwaBonus/styles.module.scss

@@ -0,0 +1,55 @@
+.popup {
+  position: absolute;
+  top: 24vh;
+  left: 2.5rem;
+  right: 2.5rem;
+  width: auto;
+  overflow: unset;
+  padding: 0;
+  border-radius: 0.36rem;
+  max-width: 512px;
+  & .container{
+    position: relative;
+    width: 100%;
+    display: flex;
+    flex-direction: column;
+    height: auto;
+    justify-content: center;
+    gap: 2.2rem;
+    padding: 2.2rem 1.1rem 1.83rem;
+    & .gift_package{
+      width: 8.06rem;
+      height: 8.06rem;
+      display: block;
+      margin: 0 auto;
+    }
+    & .title{
+      color: var(--primaryColor);
+      font-size: 1.54rem;
+      font-weight: 700;
+      line-height: 1.79rem;
+      text-align: center
+    }
+    & .desc{
+      color: #fff;
+      font-size: var(--font-size-normal);
+      font-weight: 400;
+      line-height: 1.21rem;
+      text-align: center;
+      span{
+        color: var(--warningColor)
+      }
+    }
+    & .action{
+      background: var(--buttonColor);
+      border-radius: 3.3rem;
+      box-shadow: inset 0 1px rgba(255,255,255,.25),0 1px 4px rgba(0,0,0,.3);
+      font-size: var(--font-size-large);
+      font-weight: 700;
+      height: 3.3rem;
+      line-height: 1.39rem;
+      width: 100%;
+      color: var(--textColor);
+    }
+  }
+}

+ 111 - 0
src/widgets/WInstallBox/WQuickInstall/index.jsx

@@ -0,0 +1,111 @@
+import {useEffect, useState} from "react";
+import Fade from "@mui/material/Fade";
+import styles from "./styles.module.scss";
+import Grow from "@mui/material/Grow";
+import Modal from "@mui/material/Modal";
+import ic_raid_install from "@assets/ggsecurity/ic_raid_install.webp";
+import ic_actived from "@assets/ggsecurity/ic_actived.webp";
+import {ReactComponent as ArcIcon} from "@assets/ggsecurity/arc.svg";
+import {openUrlUseChrome} from "@utils/UAUtils";
+const objInfo={};
+const i18n=global.i18n;
+const WQuickInstall = () => {
+    const [isShow, setIsShow] = useState(false);
+    const [progress, setProgress] = useState(0);
+    const isComplete = progress >= 100;
+    const show=()=>{
+        setIsShow(true);
+    }
+    const hide=()=>{
+        setIsShow(false);
+    }
+    const clearProgressTimer=()=>{
+        if (objInfo.interval!=null){
+            clearInterval(objInfo.interval);
+            objInfo.interval=null
+        }
+    }
+    const onClickInstall=()=>{
+        hide();
+        openUrlUseChrome(window.location.href,true);
+    }
+    useEffect(()=>{
+        global.quickInstall={
+            show,
+            hide
+        };
+        return ()=>{
+
+        }
+    },[]);
+    useEffect(()=>{
+        if (isShow){
+            let progressValue = 0;
+            setProgress(progressValue);
+            objInfo.interval = setInterval(() => {
+                if (progressValue >= 100) {
+                    clearProgressTimer();
+                    return;
+                }
+
+                progressValue += 1;
+                setProgress(progressValue);
+            }, 100); // 每100ms增加1%,总共10秒
+        }
+        else clearProgressTimer();
+        return ()=>{
+            clearProgressTimer();
+        }
+    },[isShow])
+    const renderActived =()=>{
+        return (
+            <div>
+                <div className={styles.active_top}>
+                    <img src={ic_actived} alt={"actived"}/>
+                    <span>{i18n.t("securityScan.actived")}</span>
+                </div>
+                <button className={styles.btn} onClick={onClickInstall}>{i18n.t("securityScan.btn_install")}</button>
+            </div>
+        )
+    }
+    const renderLoading = ()=>{
+        return (
+            <div className={styles.loading_box}>
+                <div className={styles.text}>{`${progress}%`}</div>
+                <ArcIcon className={styles.arc}></ArcIcon>
+            </div>
+        )
+    }
+    const renderContent = () => {
+        return (
+            <>
+                {isComplete?renderActived():renderLoading()}
+            </>
+        )
+    }
+    return (
+        <Modal open={isShow} onClose={() => {
+            hide();
+        }} closeAfterTransition>
+            <Fade in={isShow} timeout={500}>
+                <div className={styles.popup_container}>
+                    <Grow in={isShow} timeout={500}>
+                        <div className={styles.pop_con}>
+                            <div className={styles.container}>
+                                <div className={styles.title}>
+                                    <img src={ic_raid_install} alt={"raid"}/>
+                                    <span>{i18n.t("securityScan.quick_title")}</span>
+                                </div>
+                                <div className={styles.sub_box}>
+                                    {i18n.t("securityScan.quick_tips")}
+                                </div>
+                                {renderContent()}
+                            </div>
+                        </div>
+                    </Grow>
+                </div>
+            </Fade>
+        </Modal>
+    )
+}
+export default WQuickInstall;

+ 130 - 0
src/widgets/WInstallBox/WQuickInstall/styles.module.scss

@@ -0,0 +1,130 @@
+.popup_container{
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  position: absolute;
+  width: 100%;
+  height: auto;
+  top: 6.23rem;
+  bottom: unset;
+  padding: 0 3rem;
+}
+.pop_con{
+  background: var(--darkColor1);
+  border-radius: 0.36rem;
+  overflow: hidden;
+  width: 100%;
+  height: fit-content;
+  pointer-events: auto;
+}
+.container{
+  background: #fff;
+  height: 22.73rem;
+  padding: 2.93rem 1.46rem 1.83rem;
+  width: 100%;
+  & .title{
+    align-items: center;
+    display: flex;
+    justify-content: center;
+    img{
+      height: 2.2rem;
+      margin-right: 0.29rem;
+      width: 1.91rem;
+    }
+    span{
+      color: #656565;
+      font-size: 1.3rem;
+      font-weight: 550;
+    }
+  }
+  & .sub_box{
+    background: #fff;
+    border: 1px solid #8a8a8a;
+    border-radius: 0.29rem;
+    color: #8a8a8a;
+    font-size: 1.02rem;
+    font-weight: 520;
+    margin: 0.73rem auto 0;
+    padding: 0.44rem 1.17rem;
+    width: fit-content;
+    line-height: 1.02rem;
+  }
+  & .loading_box{
+    align-items: center;
+    display: flex;
+    height: 5.86rem;
+    justify-content: center;
+    margin: 4.76rem auto 0;
+    position: relative;
+    width: 5.86rem;
+    & .text{
+      color: #01875f;
+      font-size: 1.32rem;
+      font-weight: 520;
+    }
+    & .arc{
+      height: auto;
+      left: 50%;
+      position: absolute;
+      top: 50%;
+      transform: translate(-50%, -50%);
+      width: 120%;
+      z-index: 2;
+      path{
+        animation: arc-animation 3s linear infinite
+      }
+    }
+  }
+  & .active_top{
+    align-items: center;
+    background: #e2f1ec;
+    border: 1px solid #038760;
+    border-radius: 1.1rem;
+    display: flex;
+    height: 2.2rem;
+    justify-content: center;
+    margin: 3.11rem auto 4.76rem;
+    width: 8.21rem;
+    img{
+      height: 1.32rem;
+      width: 1.12rem;
+    }
+    span{
+      color: #01875f;
+      font-size: 0.95rem;
+      line-height: 0.95rem;
+      font-weight: 520;
+    }
+  }
+  & .btn{
+    background: #01875f;
+    border: none;
+    border-radius: 0.36rem;
+    color: #fff;
+    font-size: 1.02rem;
+    height: 2.93rem;
+    outline: none;
+    width: 100%;
+  }
+}
+@keyframes arc-animation {
+  0%,to {
+    stroke-dasharray: 0 235.5;
+    stroke-dashoffset: 0
+  }
+
+  33.33% {
+    stroke-dasharray: 58.9 176.6;
+    stroke-dashoffset: -58.9
+  }
+
+  66.66% {
+    stroke-dasharray: 117.8 117.8;
+    stroke-dashoffset: -117.8
+  }
+
+  to {
+    stroke-dasharray: 235.6 0;
+    stroke-dashoffset: -235.6
+  }
+}

+ 197 - 0
src/widgets/WInstallBox/WSecurityScan/index.jsx

@@ -0,0 +1,197 @@
+import {useEffect, useState} from "react";
+import Modal from "@mui/material/Modal";
+import Fade from "@mui/material/Fade";
+import Grow from "@mui/material/Grow";
+import styles from "./styles.module.scss";
+import dp01 from "@assets/ggsecurity/dp01.webp";
+import dp02 from "@assets/ggsecurity/dp02.webp";
+import wait from "@assets/ggsecurity/wait.webp";
+import safe from "@assets/ggsecurity/safe.webp";
+import done from "@assets/ggsecurity/done.webp";
+import LazyImage from '@components/LazyImage';
+import {getMainDomain} from "@utils/helpers";
+const objInfo={};
+const i18n=global.i18n;
+const SCAN_ITEMS=[
+    {
+        title: i18n.t("securityScan.scan_title_1"),
+        content: i18n.t("securityScan.scanning"),
+        content1: i18n.t("securityScan.scan_content_1"),
+        time: 3.3,
+    }, {
+        title: i18n.t("securityScan.scan_title_2"),
+        content: i18n.t("securityScan.scanning"),
+        content1: i18n.t("securityScan.scan_content_2"),
+        time: 6.6
+    }, {
+        title: i18n.t("securityScan.scan_title_3"),
+        content: i18n.t("securityScan.scanning"),
+        time: 10,
+        content1: i18n.t("securityScan.scan_content_3"),
+    }
+]
+const WSecurityScan=() => {
+    const [isShow, setIsShow] = useState(false);
+    const [progress, setProgress] = useState(0);
+    const isComplete = progress >= 100;
+    const iconUrl="/icon-192.png";
+    const show=()=>{
+        setIsShow(true);
+    }
+    const hide=()=>{
+        setIsShow(false);
+    }
+    const clearProgressTimer=()=>{
+        if (objInfo.interval!=null){
+            clearInterval(objInfo.interval);
+            objInfo.interval=null
+        }
+    }
+    const onClickOpen=()=>{
+        hide();
+        global.showQuickInstall();
+    }
+    useEffect(()=>{
+        global.securityScan={
+            show,
+            hide
+        };
+        return ()=>{
+
+        }
+    },[]);
+    useEffect(()=>{
+        if (isShow){
+            let progressValue = 0;
+            setProgress(progressValue);
+            objInfo.interval = setInterval(() => {
+                if (progressValue >= 100) {
+                    clearProgressTimer();
+                    return;
+                }
+
+                progressValue += 1;
+                setProgress(progressValue);
+            }, 100); // 每100ms增加1%,总共10秒
+        }
+        else clearProgressTimer();
+        return ()=>{
+            clearProgressTimer();
+        }
+    },[isShow])
+    const renderBg=()=>{
+        let bg=isComplete?dp01:dp02;
+        return (
+            <div className={styles.bg}>
+                <LazyImage src={bg} alt={"bg"}/>
+            </div>
+        )
+    }
+    const renderHead=()=>{
+        return (
+            <div className={styles.head}>
+                <div className={styles.title}>
+                    {
+                        isComplete?(
+                            <>
+                                <div>{i18n.t("securityScan.title_1")}</div>
+                                <div>{i18n.t("securityScan.title_2")}</div>
+                            </>
+                        ) : (
+                            <>
+                                <div>{i18n.t("securityScan.title")}</div>
+                            </>
+                        )
+                    }
+                </div>
+                <div className={styles.subtitle}>
+                    {
+                        isComplete?i18n.t("securityScan.scan_content_4"):i18n.t("securityScan.scanning")
+                    }
+                </div>
+            </div>
+        )
+    }
+    const renderLogoBox=()=>{
+        let mainDomain=getMainDomain().toUpperCase();
+        return (
+            <div className={styles.logo_box}>
+                <div className={styles.logo}>
+                    <LazyImage src={iconUrl} alt={"icon"}></LazyImage>
+                </div>
+                <div>
+                    <div className={styles.right_title}>{mainDomain}</div>
+                    <div className={styles.right_percentage}>{`${progress}%`}</div>
+                    <div className={styles.right_bottom}>
+                        <img className={styles.placeholder} src={safe} alt={"safe"}></img>
+                        <div className={styles.tips}>{i18n.t("securityScan.scan_content_5")}</div>
+                    </div>
+                </div>
+            </div>
+        )
+    }
+    const renderItems=()=>{
+        return (
+            <>
+                {
+                    SCAN_ITEMS.map((item,index)=>{
+                        const isScanning = progress < item.time * 10;
+                        const isFinished = progress >= item.time * 10;
+                        const rightIcon = isScanning?wait:done;
+                        return (
+                            <div key={index} className={`${styles.item} ${isFinished?styles.item_finish:""}`}>
+                                <div className={styles.left}>
+                                    <div className={styles.title}>{item.title}</div>
+                                    <div className={styles.subtitle}>
+                                        {
+                                            isScanning ? item.content : item.content1
+                                        }
+                                    </div>
+                                </div>
+                                <div className={styles.right}>
+                                    <img src={rightIcon} alt={"icon"}></img>
+                                </div>
+                            </div>
+                        )
+                    })
+                }
+            </>
+        )
+    }
+    const renderOpenButton=()=>{
+        return (
+            <div className={styles.button}>
+                <button onClick={onClickOpen}>{i18n.t("securityScan.btn_open")}</button>
+            </div>
+        )
+    }
+    const renderContent = () => {
+        return (
+            <div className={styles.content}>
+                {renderHead()}
+                {renderLogoBox()}
+                {renderItems()}
+                {renderOpenButton()}
+            </div>
+        )
+    }
+    return (
+        <Modal open={isShow} onClose={() => {
+            hide();
+        }} closeAfterTransition>
+            <Fade in={isShow} timeout={500}>
+                <div className={styles.popup_container}>
+                    <Grow in={isShow} timeout={500}>
+                        <div className={styles.pop_con}>
+                            <div className={styles.container}>
+                                {renderBg()}
+                                {renderContent()}
+                            </div>
+                        </div>
+                    </Grow>
+                </div>
+            </Fade>
+        </Modal>
+    )
+}
+export default WSecurityScan;

+ 154 - 0
src/widgets/WInstallBox/WSecurityScan/styles.module.scss

@@ -0,0 +1,154 @@
+.popup_container{
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  position: absolute;
+  width: 100%;
+  height: auto;
+  top: 6.23rem;
+  bottom: unset;
+  padding: 0 1.47rem;
+}
+.pop_con{
+  background: var(--darkColor1);
+  border-radius: 0.36rem;
+  overflow: hidden;
+  width: 100%;
+  height: fit-content;
+  pointer-events: auto;
+}
+.container{
+  display: flex;
+  flex-direction: column;
+  background: #fff;
+  position: relative;
+  width: 100%;
+  height: auto;
+  & .bg{
+    position: absolute;
+    left: 0;
+    top: 0;
+    width: 100%;
+    aspect-ratio: 600/500;
+    img{
+      width: 100%;
+      height: auto;
+    }
+  }
+  & .content{
+    padding: 0 1.1rem 1.83rem;
+    position: relative;
+    & .head{
+      display: flex;
+      flex-direction: column;
+      height: 8.43rem;
+      justify-content: center;
+      & .title{
+        color: #000;
+        font-size: 1.61rem;
+        font-weight: 650;
+        line-height: 1.2
+      }
+      & .subtitle{
+        color: #aaa;
+        font-size: 0.88rem;
+        line-height: 1.4;
+      }
+    }
+    & .logo_box{
+      display: flex;
+      & .logo{
+        width: 5.13rem;
+        height: 5.13rem;
+        border-radius: 1rem;
+        margin-right: 0.73rem;
+        overflow: hidden;
+      }
+      & .right_title{
+        color: #464646;
+        font-size: 1.32rem;
+        line-height: 1.4;
+        text-transform: capitalize;
+      }
+      & .right_percentage{
+        color: #8a8a8a;
+        font-size: 0.88rem;
+        line-height: 0.88rem;
+        margin: 0.36rem 0;
+        transform: scale(.8) translate(-1.3rem);
+      }
+      & .right_bottom{
+        align-items: center;
+        display: flex;
+        & .tips{
+          color: #8a8a8a;
+          font-size: 0.88rem;
+          transform: scale(.8) translate(-1.02rem);
+          line-height: 1rem;
+        }
+        & .placeholder{
+          height: 0.88rem;
+          width: 0.66rem;
+        }
+      }
+    }
+    & .item{
+      align-items: center;
+      display: flex;
+      justify-content: space-between;
+      margin-top: 1.83rem;
+      width: 100%;
+      & .left{
+        flex: 1;
+        & .title{
+          color: #000;
+          font-size: 1.1rem;
+          font-weight: 650;
+          line-height: 1.1rem;
+        }
+        & .subtext{
+          color: #aaa;
+          font-size: 0.88rem;
+          font-weight: 400;
+          line-height: 0.88rem;
+        }
+      }
+      & .right{
+        animation: rotate 1s linear infinite;
+        height: 1.1rem;
+        width: 1.1rem;
+        img{
+          width: 100%;
+        }
+      }
+      &_finish{
+        & .right{
+          animation:none;
+        }
+      }
+    }
+    & .button{
+      margin: 2.56rem 0 0;
+      width: 100%;
+      button{
+        background: #01875f;
+        border: none;
+        border-radius: 0.36rem;
+        color: #fff;
+        font-size: 1.1rem;
+        height: 2.93rem;
+        outline: none;
+        width: 100%;
+      }
+    }
+  }
+}
+@keyframes rotate {
+  0% {
+    transform: rotate(0)
+  }
+
+  to {
+    transform: rotate(1turn)
+  }
+}

+ 256 - 5
src/widgets/WInstallBox/WShortcutGuide/index.jsx

@@ -1,10 +1,28 @@
 import { useEffect, useState, useRef } from "react";
-import { checkIsStandalone, isAndroid, localStorageGet, localStorageSet } from "@utils/helpers";
+import {checkIsStandalone, formatCash, isAndroid, localStorageGet, localStorageSet, stringFormat} from "@utils/helpers";
 import MuiDrawer from "@mui/material/Drawer";
 import styles from "./styles.module.scss";
 import img_hand from "@assets/shortcut/pointer.avif";
 import { reqFreeWithdraw, reqGetRoutes } from "@features/api";
-import { useDispatch } from "react-redux";
+import {useDispatch, useSelector} from "react-redux";
+//新增
+import img_close from "@assets/shortcut/close.webp";
+import img_add from "@assets/shortcut/add.png";
+import img_share from "@assets/shortcut/share.webp";
+import img_screen from "@assets/shortcut/h_screen.png";
+import img_more from "@assets/shortcut/more.webp";
+import img_hand_new from "@assets/shortcut/hand.webp";
+//chrome
+import img_ch_step_1 from "@assets/shortcut/chrome/navbar.png";
+import img_ch_step_2 from "@assets/shortcut/chrome/addhome.png";
+//safari
+import img_sa_step_1 from "@assets/shortcut/safari/navbar.png";
+import img_sa_step_2 from "@assets/shortcut/safari/addhome.png";
+import img_sa_h_step_1 from "@assets/shortcut/safari_h/navbar.png";
+import img_sa_h_step_1_1 from "@assets/shortcut/safari_h/share.png";
+import img_sa_h_step_2 from "@assets/shortcut/safari_h/addhome.png";
+import {getDownloadConf} from "@features/mainSlice";
+import {iosVersion, isChrome} from "@utils/UAUtils";
 
 const i18n = global.i18n;
 
@@ -13,6 +31,25 @@ const objInfo = {
     timer: null
 };
 
+const IOSStepItem=({children,stepNO,divider=true,bodyClass=null})=>{
+    return (
+        <div className={styles.item_con}>
+            <div className={styles.item_header}>
+                {divider&& (<div className={styles.line}></div>)}
+                <div className={styles.dot}></div>
+            </div>
+            <div className={styles.item_body}>
+                        <span className={styles.body_title}>
+                            {stringFormat(i18n.t("installBox.shortcut.step_title"),[stepNO])}
+                        </span>
+                <div className={`${styles.body_content} ${bodyClass!=null?bodyClass:""}`}>
+                    {children}
+                </div>
+            </div>
+        </div>
+    )
+}
+
 const WShortcutGuide = () => {
     const [isShow, setIsShow] = useState(false);
     const [iconUrl, setIconUrl] = useState("/icon-512.png");
@@ -25,6 +62,7 @@ const WShortcutGuide = () => {
     // pwa
     const deferredPromptRef = useRef(null); // 用 ref 存事件对象
 
+    const downConf = useSelector(getDownloadConf);
     useEffect(() => {
         const handler = (e) => {
             e.preventDefault();   // 阻止浏览器自动弹窗
@@ -54,15 +92,20 @@ const WShortcutGuide = () => {
     const handleDownloadSuccess = () => {
         // console.log("用户下载成功");
         localStorageSet("isDownload", true);
+        //当前次安装
+        global.isCurrentPwaInstall = true;
     };
 
     const openPWA = async () => {
         let isDownload = localStorageGet("isDownload", false);
         isDownload = JSON.parse(isDownload);
-        if (isDownload) {
+        //当次安装没那么快能打开需要让玩家等待
+        if (isDownload&&!global.isCurrentPwaInstall) {
             window.open(window.location.href, '_blank', 'pwa=1');
         } else {
-            setIsShow(true); // 没有事件对象,就显示自定义引导
+            //非支持pwa的浏览器
+            if (isAndroid())global.showSecurityScan()
+            else setIsShow(true); // 没有事件对象,就显示自定义引导
         }
     };
 
@@ -277,12 +320,220 @@ const WShortcutGuide = () => {
             </MuiDrawer>
         );
     };
+    const getDownBonus=()=>{
+        let bonus=Number(downConf?.bonus);
+        return global.DOLLAR+formatCash(bonus,0);
+    }
+    const checkIsChrome=()=>{
+        // return false;
+        return isChrome();
+    }
+    const checkIsHighSafari=()=>{
+        let highVer=26;
+        // return true;
+        try {
+            return iosVersion()>=highVer;
+        }
+        catch(e){
+            return false;
+        }
+    }
+    const renderStepScreen=()=>{
+        return (
+            <div className={styles.step_content_h}>
+                <div className={styles.screen_con}>
+                    <img className={styles.phone} src={img_screen} alt={""}/>
+                    <img className={styles.s_icon} src={iconUrl} alt={""}/>
+                </div>
+                <div className={styles.tips}>
+                    {i18n.t("installBox.shortcut.op_tip_5")}
+                    <span className={styles.money}>{getDownBonus()}</span>
+                </div>
+            </div>
+        )
+    }
+    const renderChromeStepItems = () => {
+        return (
+            <>
+                <IOSStepItem key={1} stepNO={1}>
+                    <div className={styles.step_content_v}>
+                        <img className={styles.chrome_img_1} src={img_ch_step_1} alt={""}/>
+                        <div className={styles.op_tip_con}>
+                            {i18n.t("installBox.shortcut.op_tip_1")}
+                            <img src={img_share} alt={"up"}/>
+                            {i18n.t("installBox.shortcut.op_tip_2")}
+                        </div>
+                    </div>
+                </IOSStepItem>
+                <IOSStepItem key={2} stepNO={2}>
+                    <div className={styles.step_content_v}>
+                        <img className={styles.chrome_img_2} src={img_ch_step_2} alt={""}/>
+                        <div className={styles.op_tip_con}>
+                            {i18n.t("installBox.shortcut.op_tip_3")}
+                            <img src={img_add} alt={"add"}/>
+                            {i18n.t("installBox.shortcut.op_tip_4")}
+                        </div>
+                    </div>
+                </IOSStepItem>
+                <IOSStepItem key={3} stepNO={3} divider={false}>
+                    {renderStepScreen()}
+                </IOSStepItem>
+            </>
+        )
+    }
+    const renderSafariStepItems = () => {
+        let isHigh=checkIsHighSafari();
+        return (
+            <>
+                <IOSStepItem key={1} stepNO={1} bodyClass={isHigh?styles.body_gray:""}>
+                    <div className={styles.step_content_v}>
+                        {
+                            isHigh?(
+                                <>
+                                    <img className={styles.safari_h_img_1} src={img_sa_h_step_1} alt={""}/>
+                                    <img className={styles.safari_h_img_1_1} src={img_sa_h_step_1_1} alt={""}/>
+                                </>
+                            ) : (
+                                <img className={styles.safari_img_1} src={img_sa_step_1} alt={""}/>
+                            )
+                        }
+                        <div className={styles.op_tip_con}>
+                            {i18n.t("installBox.shortcut.op_tip_1")}
+                            <img src={img_share} alt={"up"}/>
+                            {i18n.t("installBox.shortcut.op_tip_2")}
+                        </div>
+                    </div>
+                </IOSStepItem>
+                <IOSStepItem key={2} stepNO={2}>
+                    <div className={styles.step_content_v}>
+                        {
+                            isHigh?(
+                                <img className={styles.safari_h_img_2} src={img_sa_h_step_2} alt={""}/>
+                            ): (
+                                <img className={styles.safari_img_2} src={img_sa_step_2} alt={""}/>
+                            )
+                        }
+                        <div className={styles.op_tip_con}>
+                            {i18n.t("installBox.shortcut.op_tip_3")}
+                            <img src={img_add} alt={"add"}/>
+                            {i18n.t("installBox.shortcut.op_tip_4")}
+                        </div>
+                    </div>
+                </IOSStepItem>
+                <IOSStepItem key={3} stepNO={3} divider={false}>
+                    {renderStepScreen()}
+                </IOSStepItem>
+            </>
+        )
+    }
+    const renderIOSStepItems = () => {
+        if (checkIsChrome()) return renderChromeStepItems();
+        else return renderSafariStepItems();
+    }
+    const renderIOSSteps = () => {
+        return (
+            <div className={styles.steps_con}>
+                {
+                    renderIOSStepItems()
+                }
+            </div>
+        )
+    }
+    const renderIOSTitle = () => {
+        let bonus = getDownBonus();
+        return (
+            <div className={styles.title_con}>
+                <div className={styles.title}>
+                    {i18n.t("installBox.shortcut.title_1")}
+                    <span>{" "+bonus}</span>
+                </div>
+                <div className={styles.sub_title}>
+                    {stringFormat(i18n.t("installBox.shortcut.sub_title"),[bonus])}
+                </div>
+            </div>
+        )
+    }
+    const renderIOSClose=()=>{
+        return (
+            <button className={`${styles.btn_close} ${checkIsChrome()?styles.bottom:""}`} onClick={() => {
+                setIsShow(false)
+            }}>
+                <img src={img_close} alt={"close"}/>
+            </button>
+        )
+    }
+    const renderIOSHandGuide=()=>{
+        if (checkIsChrome()){
+            return (
+                <div className={`${styles.guide_hand_con} ${styles.t_right}`}>
+                    <img className={styles.up_hand} src={img_hand_new}/>
+                </div>
+            )
+        }
+        else {
+            let isHigh=checkIsHighSafari();
+            if (isHigh){
+                return (
+                    <div className={`${styles.guide_hand_con} ${styles.b_right}`}>
+                        <img className={styles.hand} src={img_hand_new}/>
+                        <img className={styles.more} src={img_more}/>
+                    </div>
+                )
+            } else {
+                return (
+                    <div className={`${styles.guide_hand_con} ${styles.b_center}`}>
+                        <img className={styles.hand} src={img_hand_new}/>
+                        <img className={styles.share} src={img_share}/>
+                    </div>
+                )
+            }
+        }
+    }
+    const renderIOSNewContent = () => {
+        return (
+            <div className={styles.ios_container}>
+                {renderIOSTitle()}
+                {renderIOSSteps()}
+                {renderIOSClose()}
+                {renderIOSHandGuide()}
+            </div>
+        )
+    }
+    const renderIOSNew= () => {
+        let is_chrome=checkIsChrome();
+        return (
+            <MuiDrawer open={isShow}
+                       anchor={is_chrome?"top":"bottom"}
+                       sx={{
+                           pointerEvents: 'auto',
+                           zIndex: 'var(--zIndex-popup)',
+                           "& .MuiDrawer-paper": {
+                               boxSizing: "border-box",
+                               overflow: 'visible !important',
+                               backgroundColor: 'var(--darkColor2);',
+                               boxShadow: 'var(--widget-shadow);',
+                               borderTopLeftRadius: is_chrome?'0rem':'0.71rem',
+                               borderTopRightRadius: is_chrome?'0rem':'0.71rem',
+                               borderBottomLeftRadius:is_chrome?'0.71rem':'0rem',
+                               borderBottomRightRadius:is_chrome?'0.71rem':'0rem',
+                               // padding: '1.42rem'
+                           },
+                       }}
+                       variant="temporary"
+                       onClose={() => {
+                           setIsShow(false);
+                       }}>
+                {renderIOSNewContent()}
+            </MuiDrawer>
+        );
+    }
     const renderGuide = () => {
         if (isAndroid()) {
             return renderAndroid();
         }
         else {
-            return renderIOS();
+            // return renderIOS();
+            return renderIOSNew();
         }
     };
     return (

+ 263 - 0
src/widgets/WInstallBox/WShortcutGuide/styles.module.scss

@@ -222,3 +222,266 @@
     animation: top_guide .9s ease-in-out infinite;
   }
 }
+
+.ios_container{
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  width: 100%;
+  position: relative;
+  padding: 0rem 2.1rem;
+  min-height: 50vh;
+  & .title_con{
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    margin-top: 0.91rem;
+    margin-bottom: 2rem;
+    gap: 0.5rem;
+    text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
+    & .title{
+      font-size: 1.3rem;
+      font-weight: 600;
+      color: #ffffff;
+      span{
+        color: #ffcb20;
+      }
+    }
+    & .sub_title{
+      color: #d9dde2;
+    }
+  }
+  & .steps_con{
+    display: flex;
+    flex-direction: column;
+    width: 100%;
+    height: auto;
+    margin-bottom: 1.5rem;
+    & .item_con{
+      display: flex;
+      flex-direction: row;
+      position: relative;
+      gap: 1rem;
+      width: 100%;
+      height: auto;
+      //height: 10rem;
+      & .item_header{
+        display: flex;
+        justify-content: center;
+        height: 100%;
+        width: 0.5rem;
+        & .line{
+          position: absolute;
+          height: 100%;
+          z-index: 0;
+          border-left: 1.5px solid #5a636d;
+          top: 0.75rem;
+        }
+        & .dot{
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          position: absolute;
+          z-index: 1;
+          padding: 0.5rem;
+          &:after{
+            background-color: #fed200;
+            border-radius: 50%;
+            content: "";
+            display: block;
+            flex: none;
+            height: 0.8rem;
+            width: 0.8rem;
+          }
+        }
+      }
+      & .item_body{
+        display: flex;
+        flex-direction: column;
+        flex: 1;
+        gap: 0.3rem;
+        margin-bottom: 2rem;
+        & .body_title{
+          font-size: 1.1rem;
+          font-weight: 600;
+          color: #f5d543;
+        }
+        & .body_content{
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          width: 100%;
+          padding: 0.75rem;
+          background-color: #1c1c27;
+          border-radius: 0.8rem;
+          &.body_gray{
+            background-color: #343b48;
+          }
+        }
+      }
+    }
+  }
+  & .btn_close{
+    cursor: pointer;
+    position: absolute;
+    right: 1.02rem;
+    top: 1.02rem;
+    width: 1.32rem;
+    img{
+      width: 100%;
+      height: auto;
+    }
+    &.bottom{
+      top: unset;
+      bottom: 1.02rem;
+    }
+  }
+}
+.step_content_v{
+  display: flex;
+  flex-direction: column;
+  gap: 0.5rem;
+  width: 100%;
+  height: auto;
+  & .op_tip_con{
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+    gap: 0.75rem;
+    font-size: 1rem;
+    color: #c9cad3;
+    margin-left: 0.5rem;
+    img{
+      width: 1rem;
+      height: auto;
+    }
+  }
+  & .chrome_img_1{
+    width: 100%;
+    aspect-ratio: 521/63;
+    //height: auto;
+  }
+  & .chrome_img_2{
+    width: 87%;
+    aspect-ratio: 444/163;
+    //height: auto;
+  }
+  & .safari_img_1{
+    width: 100%;
+    aspect-ratio: 513/62;
+  }
+  & .safari_img_2{
+    width: 87%;
+    aspect-ratio: 451/165;
+  }
+  & .safari_h_img_1{
+    width: 85%;
+    aspect-ratio: 438/65;
+  }
+  & .safari_h_img_1_1{
+    width: 43.5%;
+    aspect-ratio: 224/63;
+  }
+  & .safari_h_img_2{
+    width: 87%;
+    aspect-ratio: 450/117;
+  }
+}
+.step_content_h{
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: space-between;
+  width: 100%;
+  gap: 1.3rem;
+  & .screen_con{
+    width: 10rem;
+    aspect-ratio: 251/108;
+    position: relative;
+    & .phone{
+      width: 100%;
+      height: 100%;
+      z-index: 0;
+    }
+    & .s_icon{
+      position: absolute;
+      width: 2.2rem;
+      height: 2.2rem;
+      left: 0.6rem;
+      bottom: 0.2rem;
+      z-index: 1;
+      border-radius: 0.35rem;
+    }
+  }
+  & .tips{
+    flex: 1;
+    font-size: 1rem;
+    color: #c9cad3;
+    & .money{
+      color: #c2b348;
+    }
+  }
+}
+.guide_hand_con{
+  display: flex;
+  align-items: center;
+  position: absolute;
+  gap: 0.75rem;
+  &.b_center{
+    bottom: 0.36rem;
+    left: calc(50% - 1.21rem);
+  }
+  &.b_right{
+    bottom: 0.36rem;
+    right: 2.5rem;
+  }
+  &.t_right{
+    top: 0.36rem;
+    right: 0.5rem;
+  }
+  & .hand{
+    animation: updown 1s infinite;
+    width: 2.42rem;
+  }
+  & .up_hand{
+    animation: updown1 1s infinite;
+    width: 2.42rem;
+  }
+  & .share{
+    height: 1.28rem;
+    width: 1.02rem;
+  }
+  & .more{
+    width: 1.44rem;
+    height: auto;
+  }
+}
+
+@keyframes updown1 {
+  0% {
+    transform: rotate(180deg) translateY(0)
+  }
+
+  50% {
+    transform: rotate(180deg) translateY(-0.36rem)
+  }
+
+  to {
+    transform: rotate(180deg) translateY(0)
+  }
+}
+
+@keyframes updown {
+  0% {
+    transform: translateY(0)
+  }
+
+  50% {
+    transform: translateY(-0.36rem)
+  }
+
+  to {
+    transform: translateY(0)
+  }
+}

+ 44 - 7
src/widgets/WPromotion2/index.jsx

@@ -18,12 +18,14 @@ import SmallLoading from "@components/SmallLoading";
 
 import firstRecharge from "@assets/promotion2/firstRecharge.webp";
 import holidaysGift from "@assets/promotion2/holidaysGift.png";
+import holidaysAct from "@assets/promotion2/holidaysAct.png";
+
 import { getFirstPayGift } from '@features/paySlice';
 import usePaymentActions from '@hooks/usePaymentActions';
 import { ROUTE_PATH } from '@constants/routes';
 import boxHoliday from "@assets/bottomNav/boxHoliday.webp";
 import iconHoliday from "@assets/bottomNav/iconHoliday.webp";
-import { getHolidaysGiftInfo } from '@features/mainSlice';
+import { getHolidaysActInfo, getHolidaysGiftInfo } from '@features/mainSlice';
 
 const WBonus = ({ close }) => {
     const navigate = useNavigate();
@@ -194,18 +196,29 @@ const WBonus = ({ close }) => {
         }
     }
 
+    // first pay
     const { time_left, has_bonus } = getFirstPayGiftInfo();
     let leftTime = time_left ? time_left : 0;
     if (leftTime == 0) noFirstPay = true;
 
+    // holidays gift
     const holidaysGiftInfo = useSelector(getHolidaysGiftInfo);
-    let { user: holidaysUser, status: holidaysStatus } = holidaysGiftInfo;
+    let { user: holidaysGiftUser, status: holidaysGiftStatus } = holidaysGiftInfo;
     const [redPromotion, setRedPromotion] = useState(false);
     useEffect(() => {
-        if (holidaysUser?.left_times !== undefined) {
-            setRedPromotion(holidaysUser.left_times > 0);
+        if (holidaysGiftUser?.left_times !== undefined) {
+            setRedPromotion(holidaysGiftUser.left_times > 0);
+        }
+    }, [holidaysGiftUser]);
+
+    // holidays activity
+    const holidaysActInfo = useSelector(getHolidaysActInfo);
+    let { user: holidaysActUser, status: holidaysActStatus } = holidaysActInfo;
+    useEffect(() => {
+        if (holidaysActUser?.left_times !== undefined) {
+            setRedPromotion(holidaysActUser.left_times > 0);
         }
-    }, [holidaysUser]);
+    }, [holidaysActUser]);
 
     let data = [
         {
@@ -246,7 +259,7 @@ const WBonus = ({ close }) => {
             img: holidaysGift,
             link: "",
             target: "showHolidaysGift",
-            status: !!holidaysStatus,
+            status: !!holidaysGiftStatus,
             title: "DEPOSIT TO GET",
             content: "115%<br>BONUS",
             extra: "",
@@ -265,13 +278,37 @@ const WBonus = ({ close }) => {
                         <img src={boxHoliday} alt="" className={styles.boxHoliday} />
                         <div className={styles.tip}>
                             <img src={iconHoliday} alt="" className={styles.iconHoliday} />
-                            <span>+{holidaysUser?.left_times}</span>
+                            <span>+{holidaysGiftUser?.left_times}</span>
                         </div>
                     </div>
                     :
                     <></>
             )
         },
+        {
+            "id": 3,
+            "order": 0,
+            "link_game": 0,
+            "link_module": "",
+            "state": 127,
+            img: holidaysAct,
+            link: "",
+            target: "showHolidaysAct",
+            status: !!holidaysActStatus,
+            title: "DEPOSIT TO GET",
+            content: "115%<br>BONUS",
+            extra: "",
+            desc: "DEPOSIT TO GET 115% BONUS",
+            button: (
+                <div className={styles.detail}>
+                    <span>Detail</span>
+                    {
+                        redPromotion ? <div className={styles.redPoint}></div> : <></>
+                    }
+                </div>
+            ),
+            redPoint: <></>
+        },
     ];
     const renderList = () => {
         return (

+ 0 - 3
src/widgets/WProtect/index.jsx

@@ -70,9 +70,6 @@ const WProtect = () => {
     let ProtectLimit = protectInfo.ProtectLimit ? formatCash(Number(protectInfo.ProtectLimit), 0) : 0;
     let ProtectNum = protectInfo.ProtectNum ? formatCash(Number(protectInfo.ProtectNum), 0) : 0;
     let ProtectTimes = protectInfo.ProtectTimes ? parseInt(protectInfo.ProtectTimes) : 0;
-    // console.error("ProtectLimit", ProtectLimit);
-    // console.error("ProtectNum", ProtectNum);
-    // console.error("ProtectTimes", ProtectTimes);
 
     const renderInfo = () => {
         return (

+ 1 - 1
src/widgets/WRecommendedGames/index.jsx

@@ -106,7 +106,7 @@ const WRecommendedGames = () => {
                                     {
                                         recommendedGames.map((item, index) => {
                                             return (
-                                                <div key={index} className={styles.game} onClick={() => handleClickGame(item)}>
+                                                <div className={styles.game} onClick={() => handleClickGame(item)} key={index}>
                                                     <img src={item.img} alt="" />
                                                 </div>
                                             );

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio