index.jsx 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637
  1. import styles from './styles.module.scss';
  2. // components
  3. import SidebarTrigger from '@ui/SidebarTrigger';
  4. import Headroom from 'react-headroom';
  5. import Logo from '@components/Logo';
  6. import AppBar from "@mui/material/AppBar";
  7. import {useSidebar} from "@contexts/sidebarContext";
  8. import {useThemeProvider} from "@contexts/themeContext";
  9. import {useEffect, useRef, useState} from 'react';
  10. import styled from 'styled-components/macro';
  11. import {Tab, Tabs} from "@mui/material";
  12. // import LINKS from "@constants/links";
  13. import Button from "@mui/material/Button";
  14. import {NavLink, useLocation, useNavigate} from "react-router-dom";
  15. import ToggleButtonGroup from "@mui/material/ToggleButtonGroup";
  16. import ToggleButton from "@mui/material/ToggleButton";
  17. import symbol from "@assets/symbol-defs.svg";
  18. import symbol_m from "@assets/symbol-mobile.svg";
  19. import {string} from "prop-types";
  20. import IconButton from "@mui/material/IconButton";
  21. import {animated,easings} from "@react-spring/web";
  22. import {useSpring} from "@react-spring/core";
  23. import {useWindowSize} from "react-use";
  24. import ResizeObserver from "resize-observer-polyfill";
  25. import {useSelector} from "react-redux";
  26. import {
  27. allTopNavs,
  28. getRoute,
  29. getRouteChain,
  30. getShowInstall,
  31. getUserInfo,
  32. isLogin,
  33. isLogined
  34. } from "@features/mainSlice";
  35. import {getMailNew} from "@features/mailSlice";
  36. import LazyImage from "@components/LazyImage";
  37. import {numFormatter, sleep} from "@utils/helpers";
  38. import {getAvatarUrl} from "@utils/avatar";
  39. import {ROUTE_PATH} from "@constants/routes";
  40. import {toast} from "react-toastify";
  41. import LazyImgix from "@components/Imgix/LazyImgix";
  42. const i18n=global.i18n;
  43. const StyledToggleGroup= styled(ToggleButtonGroup)`
  44. border-radius: 0px !important;
  45. gap: 12px;
  46. // provider 数量较多时不要换行 / 不要把右侧 SignIn 区挤掉,
  47. // 给一个横向滚动兜底(一般场景宽度够用就不会出现滚动条)。
  48. flex-wrap: nowrap;
  49. overflow-x: auto;
  50. max-width: 100%;
  51. scrollbar-width: thin;
  52. &::-webkit-scrollbar { height: 4px; }
  53. `;
  54. const StyledToggle=styled(ToggleButton)`
  55. gap: 12px;
  56. text-transform:none !important;
  57. border: none !important;
  58. border-radius: var(--border-radius) !important;
  59. padding: 0px !important;
  60. flex: 0 0 auto;
  61. white-space: nowrap;
  62. &:hover, &:focus ,&.Mui-selected, &.active {
  63. background-color: var(--nav-item-hover) !important;
  64. a {
  65. color: var(--header);
  66. font-weight: 600;
  67. .icon {
  68. fill:${props => props.fill};
  69. }
  70. }
  71. }
  72. a {
  73. display: flex;
  74. align-items: center;
  75. color: var(--text);
  76. padding: 12px !important;
  77. transition: all var(--transition);
  78. font-family: var(--heading-font);
  79. gap: 8px;
  80. .icon {
  81. display: flex;
  82. align-items: center;
  83. justify-content: center;
  84. width: 18px;
  85. height: 18px;
  86. fill: var(--text);
  87. }
  88. }
  89. `;
  90. const DesktopNavToggle=()=>{
  91. const LINKS=useSelector(allTopNavs);
  92. // 之前固定 slice(0,4) 让顶部导航最多只显示 4 项(Home/Hot Games/PGSoft/PragmaticPlay),
  93. // 后面的 provider 全部被截掉。导航栏右侧本身还有大量空白,直接展示全部即可。
  94. const links=LINKS;
  95. const {pathname} = useLocation();
  96. const [alignment, setAlignment] = useState(pathname);
  97. const currRoute=useSelector(getRoute(pathname));
  98. const getRootRoute=(pathname)=>{
  99. let route="";
  100. if (currRoute){
  101. let clv=currRoute.lv;
  102. if (clv==null)return route;
  103. for (let i = 0; i < LINKS.length; i++) {
  104. let link=LINKS[i];
  105. let lv=link.lv;
  106. if (clv.startsWith(lv)){
  107. route=link.path;
  108. break;
  109. }
  110. }
  111. }
  112. return route;
  113. }
  114. useEffect(() => {
  115. setAlignment(getRootRoute(pathname));
  116. }, [pathname]);
  117. const handleChange = (event, newAlignment) => {
  118. // setAlignment(newAlignment);
  119. };
  120. return (
  121. <StyledToggleGroup
  122. value={alignment}
  123. exclusive
  124. onChange={handleChange}
  125. >
  126. {
  127. links.map((link, index) => (
  128. <StyledToggle value={link.path} fill={link.fill.split(";").pop()} key={link.title}>
  129. <NavLink className="h4" to={link.path}>
  130. <svg className={"icon"}>
  131. <use xlinkHref={`${symbol}#${link.icon}`} />
  132. </svg>
  133. {i18n.t(link.title)}
  134. </NavLink>
  135. </StyledToggle>
  136. ))
  137. }
  138. </StyledToggleGroup>
  139. )
  140. }
  141. const DesktopSignInUp=()=>{
  142. const navigate=useNavigate();
  143. const isLogin=useSelector(isLogined);
  144. const {setIsShowLang} = useThemeProvider();
  145. return (
  146. <div className="d-flex align-items-center justify-content-end">
  147. {/*<IconButton aria-label="search">*/}
  148. {/* <svg className={"ls-icon"} style={{width:'20px',height:'20px'}}>*/}
  149. {/* <use xlinkHref={`${symbol}#icon_Search`} />*/}
  150. {/* </svg>*/}
  151. {/*</IconButton>*/}
  152. {
  153. !isLogin?(
  154. <>
  155. <Button className={`m-button ${styles.btn_sign_in}`} onClick={()=>{
  156. navigate(ROUTE_PATH.SIGN_IN);
  157. }}>{i18n.t('sign_in')}</Button>
  158. <Button className={`${styles.btn_sign_up}`} onClick={()=>{
  159. navigate(ROUTE_PATH.SIGN_UP);
  160. }}>{i18n.t('sign_up')}</Button>
  161. </>
  162. ):(
  163. <>
  164. </>
  165. )
  166. }
  167. {/*<Button className="m-button">*/}
  168. {/* <svg className={"ls-icon"} style={{width:'20px',height:'20px'}}>*/}
  169. {/* <use xlinkHref={`${symbol}#icon_Chat`} />*/}
  170. {/* </svg>*/}
  171. {/* <div className="chat-green-dont"></div>*/}
  172. {/*</Button>*/}
  173. <Button className="m-button" style={{marginLeft: '0.5rem',marginRight:'0.5rem'}} onClick={()=>{
  174. setIsShowLang(true);
  175. }}>
  176. <svg className={"ls-icon"} style={{width:'20px',height:'20px'}}>
  177. <use xlinkHref={`${symbol}#icon_Language`} />
  178. </svg>
  179. </Button>
  180. {
  181. isLogin?(<UserInfo />):(<></>)
  182. }
  183. </div>
  184. )
  185. }
  186. const DesktopNav = () => {
  187. const {open} = useSidebar();
  188. const navstyle=open?styles.nav_open:"";
  189. return (
  190. <div
  191. className={`d-flex align-items-center justify-content-between ${styles.header} ${navstyle}`}>
  192. <DesktopNavToggle />
  193. <DesktopSignInUp/>
  194. </div>
  195. );
  196. }
  197. const MobileSignInUp=()=>{
  198. const navigate=useNavigate();
  199. const isLogin=useSelector(isLogined);
  200. return (
  201. <div className="d-flex align-items-center justify-content-end">
  202. {
  203. !isLogin?(
  204. <>
  205. <Button className={`m-button ${styles.btn_sign_in} ${styles.btn_sign_in_mobile}`} onClick={()=>{
  206. navigate(ROUTE_PATH.SIGN_IN);
  207. }}>{i18n.t('sign_in')}</Button>
  208. <Button className={`${styles.btn_sign_up} ${styles.btn_sign_up_mobile}`} onClick={()=>{
  209. navigate(ROUTE_PATH.SIGN_UP);
  210. }}>{i18n.t('sign_up')}</Button>
  211. </>
  212. ):(
  213. <></>
  214. )
  215. }
  216. {/*<Button className="m-button">*/}
  217. {/* <svg className={"ls-icon"} style={{width:'1.4rem',height:'1.4rem'}}>*/}
  218. {/* <use xlinkHref={`${symbol}#icon_Language`} />*/}
  219. {/* </svg>*/}
  220. {/*</Button>*/}
  221. </div>
  222. );
  223. }
  224. const MobileNav=()=>{
  225. return (
  226. <div
  227. className={`d-flex align-items-center justify-content-between ${styles.header} ${styles.header_mobile}`}>
  228. <div className={`d-flex flex-row flex-1`} style={{maxWidth:"55%"}}>
  229. <SidebarTrigger/>
  230. <Logo size="sm"/>
  231. </div>
  232. <MobileSignInUp/>
  233. <UserInfo />
  234. </div>
  235. )
  236. }
  237. const UserInfo=()=>{
  238. const isLogin=useSelector(isLogined);
  239. const user=useSelector(getUserInfo);
  240. const mailNew=useSelector(getMailNew);
  241. const navigate=useNavigate();
  242. const stop=(e)=>{e.stopPropagation();};
  243. return (
  244. <>
  245. {
  246. isLogin?(
  247. <div className={`d-flex align-items-center ${styles.user_info}`} onClick={()=>{
  248. navigate(ROUTE_PATH.SETTINGS);
  249. }}>
  250. <div className={styles.info}>
  251. <div className={styles.score}>{global.DOLLAR+" "+numFormatter(user.Score)}</div>
  252. </div>
  253. <NavLink to={ROUTE_PATH.PAY} className={styles.deposit_btn} onClick={stop}>
  254. <svg className={styles.deposit_icon}>
  255. <use xlinkHref={`${symbol}#icon_plus`}/>
  256. </svg>
  257. </NavLink>
  258. <NavLink to={ROUTE_PATH.MESSAGE} className={styles.mail_btn} onClick={stop}>
  259. <svg className={styles.mail_icon}>
  260. <use xlinkHref={`${symbol}#icon_Mail`}/>
  261. </svg>
  262. {mailNew>0&&(<span className={styles.mail_badge}>{mailNew>99?'99+':mailNew}</span>)}
  263. </NavLink>
  264. <LazyImgix className={styles.avatar} src={getAvatarUrl(user.FaceID)} alt={"Head"} width={80}/>
  265. </div>
  266. ):(<></>)
  267. }
  268. </>
  269. )
  270. }
  271. const PageHeader = () => {
  272. const LINKS=useSelector(allTopNavs);
  273. const {initHeaderHeight,installOpen} = useThemeProvider();
  274. const {pathname} = useLocation();
  275. const navigate=useNavigate();
  276. const barRef = useRef(null);
  277. const currRoute=useSelector(getRoute(pathname));
  278. const chain=useSelector(getRouteChain(pathname));
  279. const {isMobile,showHeader,setShowHeader,showMHeader, setShowMHeader} = useThemeProvider();
  280. // const [showMHeader, setShowMHeader] = useState(false);
  281. const isLogin=useSelector(isLogined);
  282. let lastscroll=0;
  283. const {open} = useSidebar();
  284. const navstyle=open?styles.nav_open:"";
  285. const isShowInstall=useSelector(getShowInstall);
  286. const checkNavActive=(lv)=>{
  287. if (currRoute&&currRoute.lv)return currRoute.lv.startsWith(lv);
  288. else return false;
  289. }
  290. const getHeaderHeight = () => {
  291. // console.log(document.querySelector('div.progressier-install-banner'))
  292. const header = document.querySelector('#appbar');
  293. let offset = 0;
  294. if (barRef){
  295. const headerRoom=document.querySelector('#headerRoom');
  296. if (headerRoom){
  297. if (!barRef.showHeader){
  298. offset=headerRoom.clientHeight;
  299. const hstyle = window.getComputedStyle(headerRoom);
  300. if (hstyle!=null){
  301. let paddingBottom=hstyle.paddingBottom;
  302. paddingBottom=Number(paddingBottom.replace("px",""));
  303. offset=offset-paddingBottom;
  304. }
  305. }
  306. }
  307. }
  308. if (header!=null)return `${(header.offsetHeight-offset) || 0}px`
  309. else return '0px';
  310. }
  311. const handleScroll=()=>{
  312. if (!barRef.showHeader)return;
  313. let scrollTop = document.documentElement.scrollTop; //滚动条滚动高度
  314. let dt=scrollTop-lastscroll;
  315. lastscroll=scrollTop;
  316. let show=false;
  317. if (dt>20){
  318. show=false;
  319. setShowMHeader(show);
  320. }
  321. else if (dt<-20){
  322. show=true;
  323. setShowMHeader(show);
  324. }
  325. if (scrollTop<20){
  326. setShowMHeader(true);
  327. }
  328. }
  329. const changeHeaderHeight=()=>{
  330. document.documentElement.style.setProperty('--header-active', getHeaderHeight());
  331. initHeaderHeight(getHeaderHeight());
  332. }
  333. const resizeObserver = new ResizeObserver(entries => {
  334. for (let entry of entries) {
  335. // 获取目标元素的高度
  336. const { height } = entry.contentRect;
  337. // 可在这里处理高度变化的逻辑
  338. changeHeaderHeight();
  339. }
  340. });
  341. const getProgressierInstallBanner=(params,callback)=>{
  342. let banner=new window.NewProgressierInstallBanner(
  343. params,
  344. callback
  345. )
  346. banner.remove = async ()=>{
  347. if (banner.deny && !banner.data.preview) {
  348. banner.deny()
  349. }
  350. banner.element.classList.remove("in");
  351. // await progressier.utils.wait(1e3);
  352. banner.element.remove()
  353. }
  354. banner.make=()=>{
  355. document.querySelectorAll("." + banner.className).forEach(function(e) {
  356. e.remove()
  357. });
  358. let installBanner=document.querySelector("#installBanner");
  359. if (installBanner){
  360. banner.element = window.progressier.utils.node("div", banner.className, {
  361. parent: installBanner
  362. });
  363. banner.element.classList.add(styles.install_banner);
  364. }
  365. }
  366. banner.init = async function() {
  367. window.progressier.utils.styling(banner.styling, banner.className);
  368. let installBanner=document.querySelector("#installBanner");
  369. if (installBanner==null){return}
  370. banner.make();
  371. banner.render()
  372. }
  373. window.install_banner=banner;
  374. return banner;
  375. }
  376. // const reProgressierInstallBanner=()=>{
  377. // if (window.NewProgressierInstallBanner!=null)return;
  378. // let ProgressierInstallBanner=window.ProgressierInstallBanner;
  379. // window.NewProgressierInstallBanner=ProgressierInstallBanner;
  380. // window.ProgressierInstallBanner=function (e, t) {
  381. // this.remove = async function() {
  382. //
  383. // };
  384. // this.render = async function() {
  385. //
  386. // };
  387. // this.make = function() {
  388. //
  389. // };
  390. // this.init = async function() {
  391. //
  392. // }
  393. // }
  394. // }
  395. const checkInstall=(l)=>{
  396. //route控制
  397. if (!isShowInstall){
  398. return false;
  399. }
  400. if (!installOpen){
  401. return false;
  402. }
  403. if (l.parent.detection.isStandalone()) {
  404. return false;
  405. }
  406. return true;
  407. }
  408. const startListening=async ()=>{
  409. if (window.progressier==null)return ;
  410. let l=window.progressier.banners;
  411. await l.parent.data.waitForData();
  412. l.data = l.parent.data.params;
  413. if (!l.reinstallTimer) {
  414. l.reinstallTimer = setInterval(function() {
  415. // window.alert(window.progressier["native"].justInstalled)
  416. // if (!l.showInstall()) {
  417. // return
  418. // }
  419. if (!checkInstall(l)){
  420. clearInterval(l.reinstallTimer)
  421. l.reinstallTimer=null;
  422. return;
  423. }
  424. // window.alert("222")
  425. // if (l.showed) {
  426. // clearInterval(l.reinstallTimer)
  427. // l.reinstallTimer=null;
  428. // return
  429. // }
  430. // window.alert("3333")
  431. l.showed = true;
  432. // new window.NewProgressierInstallBanner({
  433. // icon: l.icon(),
  434. // color: l.data.buttonColor,
  435. // btnColor: l.data.buttonTextColor,
  436. // text: l.data.installBannerPitch
  437. // },function() {
  438. // l.deny(l.installCookie)
  439. // }
  440. // ).init();
  441. let InstallBanner=getProgressierInstallBanner(
  442. {
  443. icon: l.icon(),
  444. color: l.data.buttonColor,
  445. btnColor: l.data.buttonTextColor,
  446. text: l.data.installBannerPitch
  447. },()=>{
  448. l.deny(l.installCookie)
  449. }
  450. ).init();
  451. clearInterval(l.reinstallTimer)
  452. }, l.interval)
  453. }
  454. else
  455. {
  456. clearInterval(l.reinstallTimer)
  457. l.reinstallTimer=null;
  458. }
  459. }
  460. const checkProgressier=async ()=>{
  461. // return ;
  462. if (window.progressier){
  463. // reProgressierInstallBanner();
  464. // let l=window.progressier.banners;
  465. // await l.parent.data.waitForData();
  466. // await checkWaitForData();
  467. // let data=l.data;
  468. // if (data){
  469. // if (window.NewProgressierInstallBanner){
  470. // let InstallBanner=getProgressierInstallBanner(
  471. // {
  472. // icon: l.icon(),
  473. // color: l.data.buttonColor,
  474. // btnColor: l.data.buttonTextColor,
  475. // text: l.data.installBannerPitch
  476. // }
  477. // ).init();
  478. // }
  479. // }
  480. startListening();
  481. }
  482. }
  483. const stopListener=()=>{
  484. if (window.progressier==null)return;
  485. let l=window.progressier.banners;
  486. if(l){
  487. if (l.reinstallTimer!=null){
  488. clearInterval(l.reinstallTimer);
  489. l.reinstallTimer=null;
  490. }
  491. }
  492. }
  493. const checkInstallOpen=()=>{
  494. if (installOpen&&isShowInstall){
  495. checkProgressier();
  496. }
  497. else
  498. {
  499. if (window.install_banner){
  500. window.install_banner.remove();
  501. }
  502. }
  503. }
  504. useEffect(() => {
  505. // 这里的代码会在组件挂载时执行
  506. // console.log('Component mounted');
  507. window.addEventListener('scroll', handleScroll);
  508. if (barRef.current) {
  509. resizeObserver.observe(barRef.current);
  510. }
  511. // testBefore();
  512. // checkProgressier();
  513. return () => {
  514. // 可选的清除函数,当组件卸载时执行
  515. // console.log('Component unmounted');
  516. window.removeEventListener('scroll', handleScroll);
  517. if (barRef.current) {
  518. resizeObserver.unobserve(barRef.current);
  519. }
  520. // stopListener();
  521. };
  522. }, []);
  523. // }, [pathname]);
  524. useEffect(()=>{
  525. setShowMHeader(showHeader);
  526. barRef.showHeader=showHeader;
  527. changeHeaderHeight();
  528. },[showHeader]);
  529. useEffect(()=>{
  530. let len=chain.length;
  531. let isShowH=len<=1;
  532. if (LINKS===null||LINKS.length<=1){
  533. isShowH=false;
  534. }
  535. if (pathname.startsWith(ROUTE_PATH.SPORTS)){
  536. isShowH=true;
  537. }
  538. setShowHeader(isShowH);
  539. },[pathname]);
  540. useEffect(()=>{
  541. checkInstallOpen();
  542. return ()=>{
  543. stopListener();
  544. }
  545. },[installOpen,isShowInstall])
  546. const headerAni=useSpring({
  547. from: {transform: 'translateY(-100%) translateZ(0px)'},
  548. transform:showMHeader?'translateY(0%) translateZ(0px)':'translateY(-100%) translateZ(0px)',
  549. config: {
  550. easing: easings.easeOutBack,
  551. duration:300
  552. },
  553. });
  554. const changePath=(path)=>{
  555. navigate(path);
  556. }
  557. const renderHeaderRoom=()=>{
  558. const active=styles.active;
  559. return (
  560. <animated.div id={'headerRoom'} className={`${styles.header_room}`} style={headerAni}>
  561. <div className={`${styles['mobile-top-navigate-list']}`}>
  562. <button className={`${styles['mb-top-navigate-item']} ${pathname==='/'?active:''}`} onClick={()=>{
  563. changePath('/');
  564. }}>
  565. <svg className={"icon"}>
  566. <use xlinkHref={`${symbol_m}#icon_Home`} />
  567. </svg>
  568. <span>{i18n.t("links.home")}</span>
  569. </button>
  570. {
  571. LINKS.map((link, index) => (
  572. <button key={index} className={`${styles['mb-top-navigate-item']} ${checkNavActive(link.lv)?active:''}`} onClick={()=>{
  573. changePath(link.path);
  574. }}>
  575. <svg className={"icon"}>
  576. <use xlinkHref={`${symbol_m}#${link.icon}`} />
  577. </svg>
  578. <span>{i18n.t(link.title)}</span>
  579. </button>
  580. ))
  581. }
  582. </div>
  583. </animated.div>
  584. );
  585. }
  586. const renderMobileInstall=()=>{
  587. return (
  588. <div id={'installBanner'} className={styles.header_install}></div>
  589. )
  590. }
  591. const renderMobile=()=>{
  592. return (
  593. <>
  594. {/* {renderMobileInstall()} */}
  595. <MobileNav />
  596. {/* {renderHeaderRoom()} */}
  597. </>
  598. )
  599. }
  600. const renderDesktopInstall=()=>{
  601. return (
  602. <div id={'installBanner'} className={`${styles.header_install} ${styles.header_install_desk} ${navstyle}`}></div>
  603. )
  604. }
  605. const renderDesktop=()=>{
  606. return (
  607. <>
  608. {/*{renderDesktopInstall()}*/}
  609. <DesktopNav/>
  610. </>
  611. );
  612. }
  613. const isHome = pathname === "/" || pathname === "";
  614. if (isMobile() && !isHome) {
  615. return (<div id={'appbar'} ref={barRef} className={`${styles.appbar} ${styles.mobile} ${styles.hide_header}`}/>);
  616. }
  617. return (
  618. <div id={'appbar'} ref={barRef} className={`${styles.appbar} ${isMobile()?styles.mobile:''} ${isMobile()&&!showHeader?styles.hide_header:''}`}>
  619. {
  620. isMobile()?renderMobile():renderDesktop()
  621. }
  622. </div>
  623. )
  624. }
  625. export default PageHeader