index.jsx 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588
  1. // styled components
  2. import {
  3. Link,
  4. SingleLink,
  5. StyledAccordion,
  6. StyledAccordionDetails,
  7. StyledAccordionSummary,
  8. StyledBonus,
  9. StyledButton,
  10. StyledThemeTabs,
  11. StyledThemeTab,
  12. StyledAct,
  13. StyledRefer,
  14. StyledBlock,
  15. StyledIconLink
  16. }
  17. from './styles';
  18. // components
  19. import Logo from '@components/Logo';
  20. import {NavLink, useLocation,useNavigate} from 'react-router-dom';
  21. // hooks
  22. import {useSidebar} from '@contexts/sidebarContext';
  23. import {useWindowSize} from 'react-use';
  24. import {Fragment, useEffect, useState} from 'react';
  25. // constants
  26. import LINKS from '@constants/links';
  27. import MuiDrawer from '@mui/material/Drawer';
  28. // import styled from 'styled-components/macro';
  29. import { styled, useTheme } from '@mui/material/styles';
  30. import SidebarTrigger from "@ui/SidebarTrigger";
  31. import {ReactComponent as CheckInIcon} from "@assets/singlesvg/chekin.svg";
  32. // import {useThemeProvider} from '@contexts/themeContext';
  33. import symbol from '@assets/symbol-defs.svg';
  34. import MTooltip from "@components/MTooltip";
  35. import Tabs from "@mui/material/Tabs";
  36. import Tab from "@mui/material/Tab";
  37. import Button from "@mui/material/Button";
  38. import TabScrollButton from "@mui/material/TabScrollButton";
  39. import ToggleButtonGroup from "@mui/material/ToggleButtonGroup";
  40. import {useThemeProvider} from "@contexts/themeContext";
  41. import questsimg from "@assets/activity/quests.png";
  42. import spin_1 from "@assets/activity/spin_1.png";
  43. import spin_2 from "@assets/activity/spin_2.png";
  44. import spin_3 from "@assets/activity/spin_3.png";
  45. import imgrefer from "@assets/activity/rio_refer.png";
  46. import imgrefer_f from "@assets/activity/rio_refer_f.png";
  47. import {useDispatch, useSelector} from "react-redux";
  48. import {
  49. allNavs,
  50. getBlockColumn,
  51. getRoute,
  52. getRouteStyles,
  53. isFullRoute,
  54. isLogined,
  55. logout,
  56. nBlocks
  57. } from "@features/mainSlice";
  58. import {
  59. ROUTE_PATH,
  60. ROUTE_TYPE_OUTLINK,
  61. ROUTE_TYPE_POP,
  62. ROUTE_TYPE_POP_OR_FULL,
  63. SIDE_TYPE_GRID,
  64. SIDE_TYPE_NOMAL,
  65. SIDE_TYPE_SPECIAL
  66. } from "@constants/routes";
  67. import SvgIcon from "@components/SvgIcon";
  68. import {reqChangeInfo, reqLogout} from "@features/api";
  69. import {APP_THEME} from "@constants/app";
  70. const drawWidth = 250;
  71. const drawMobieWidth=250;
  72. const i18n=global.i18n;
  73. const openedMixin = (theme,width) => ({
  74. width: width,
  75. transition: theme.transitions.create('width', {
  76. easing: theme.transitions.easing.sharp,
  77. duration: theme.transitions.duration.enteringScreen,
  78. }),
  79. // overflowX: 'hidden',
  80. overflow:'visible !important',
  81. backgroundColor:'var(--standard);',
  82. boxShadow:'var(--widget-shadow);',
  83. // position:'relative',
  84. });
  85. const closedMixin = (theme) => ({
  86. transition: theme.transitions.create('width', {
  87. easing: theme.transitions.easing.sharp,
  88. duration: theme.transitions.duration.leavingScreen,
  89. }),
  90. // overflowX: 'hidden',
  91. //spin显示
  92. overflow:'visible !important',
  93. width: `calc(${theme.spacing(7)} + 1px)`,
  94. [theme.breakpoints.up('sm')]: {
  95. width: `calc(${theme.spacing(8)} + 1px)`,
  96. },
  97. backgroundColor:'var(--standard);',
  98. boxShadow:'var(--widget-shadow);',
  99. // position:'absolute',
  100. });
  101. const Drawer = styled(MuiDrawer, { shouldForwardProp: (prop) => prop !== 'open' })(
  102. ({ theme, open}) => ({
  103. width: drawWidth,
  104. flexShrink: 0,
  105. whiteSpace: 'nowrap',
  106. boxSizing: 'border-box',
  107. pointerEvents: 'auto',
  108. // overflowX:'hidden',
  109. ...(open && {
  110. ...openedMixin(theme),
  111. '& .MuiDrawer-paper': openedMixin(theme,drawWidth),
  112. }),
  113. ...(!open && {
  114. ...closedMixin(theme),
  115. '& .MuiDrawer-paper': closedMixin(theme),
  116. }),
  117. }),
  118. );
  119. const DrawerHeader = styled('div')(({ theme }) => ({
  120. display: 'flex',
  121. alignItems: 'center',
  122. padding: '0 10px',
  123. gap:'12px',
  124. // backgroundColor:'#000',
  125. boxShadow:'var(--widget-shadow)',
  126. position:'relative',
  127. '& .logo_con':{
  128. width:'80%'
  129. },
  130. '& .logo_con_m':{
  131. width:'80%'
  132. },
  133. // necessary for content to be below app bar
  134. ...theme.mixins.toolbar,
  135. }));
  136. const Sidebar = () => {
  137. // const theme = useTheme();
  138. const {open, setOpen} = useSidebar();
  139. const [expanded, setExpanded] = useState("");
  140. const {pathname,hash} = useLocation();
  141. const navigate=useNavigate();
  142. const {width} = useWindowSize();
  143. const {theme, toggleTheme, setIsShowLang, isMobile} = useThemeProvider();
  144. const [isActHovered, setIsActHovered] = useState(false);
  145. const visiblecls=isActHovered?"overflow-visible":"";
  146. const navBlocks=useSelector(nBlocks);
  147. const currRoute = useSelector(getRoute(pathname));
  148. const blocksColumn=useSelector(getBlockColumn);
  149. const routeStyles=useSelector(getRouteStyles);
  150. const isLogin=useSelector(isLogined);
  151. const dispatch = useDispatch();
  152. const handleActEnter = () => {
  153. if (open)return;
  154. setIsActHovered(true);
  155. };
  156. const handleActLeave = () => {
  157. if (open)return;
  158. setIsActHovered(false);
  159. };
  160. // manually handle accordion expansion
  161. const handleChange = (panel,path) => (event, isExpanded) => {
  162. if (isExpanded)navigate(path);
  163. // setExpanded(isExpanded ? panel : false);
  164. if (!isExpanded){
  165. let arr=panel.split("_");
  166. let s=arr.length-2;
  167. if (s<0)panel="";
  168. else
  169. {
  170. arr.splice(s,1);
  171. panel=arr.join("_");
  172. }
  173. }
  174. setExpanded(panel);
  175. }
  176. const checkNavOpen=()=>{
  177. if (currRoute==null)return;
  178. let lv=currRoute.lv;
  179. setExpanded(lv);
  180. }
  181. const getolstyles=(styleID)=>{
  182. let styles=routeStyles[styleID];
  183. if (styles==null){
  184. styles=routeStyles[1];
  185. }
  186. let style={};
  187. if (styles!=null){
  188. style=styles.style;
  189. }
  190. let thstyles=style[theme];
  191. if (thstyles==null)thstyles={};
  192. return thstyles;
  193. }
  194. // collapse opened accordion on route change when the drawer is temporary
  195. useEffect(() => {
  196. checkNavOpen();
  197. }, [pathname]);
  198. const checkCkey=(lv)=>{
  199. if (expanded==null)return false;
  200. else return expanded.startsWith(lv);
  201. }
  202. const checkPath=(link)=>{
  203. let lv=currRoute.lv;
  204. return lv===link.lv;
  205. }
  206. const renderIconLink=(link,olstyle,lIcon=null)=>{
  207. let ispop=link.type===ROUTE_TYPE_POP||link.type===ROUTE_TYPE_POP_OR_FULL||link.type===ROUTE_TYPE_OUTLINK;
  208. let linkIcon=(<SvgIcon className={"icon"} herf={link.icon}/>);
  209. if (lIcon!=null)linkIcon=lIcon;
  210. let target=link.type===ROUTE_TYPE_OUTLINK?"_blank":"";
  211. let linkA=ispop?(
  212. <a href={link.path} target={target}>
  213. {linkIcon}
  214. </a>
  215. ):(
  216. <NavLink to={link.path}>
  217. {linkIcon}
  218. </NavLink>
  219. );
  220. return (
  221. <MTooltip disableHoverListener={open} title={i18n.t(link.title)} arrow placement="right">
  222. <StyledIconLink olstyle={olstyle}>
  223. {linkA}
  224. </StyledIconLink>
  225. </MTooltip>
  226. );
  227. }
  228. const renderLink=(link)=>{
  229. let pages=link.subs||link.pages;
  230. let styles=getolstyles(link.style);
  231. let linkItem=open?(
  232. <StyledAccordion olstyle={styles} defaultExpanded={true} square={true} key={link.lv} expanded={checkCkey(link.lv)} onChange={handleChange(`${link.lv}`,link.path)}>
  233. <StyledAccordionSummary olstyle={styles}>
  234. <Link olstyle={styles} className={`${checkPath(link) ? 'active' : ''} h4`}>
  235. <SvgIcon className={"icon"} herf={link.icon}/>
  236. {i18n.t(link.title)}
  237. </Link>
  238. <SvgIcon className={"icon-chevron-down"} herf={'icon_Arrow'}/>
  239. </StyledAccordionSummary>
  240. <StyledAccordionDetails olstyle={styles}>
  241. {
  242. pages.map((page,pindex)=>{
  243. let subs=page.subs;
  244. if (subs==null||subs.length==0)return renderRLink(page);
  245. else
  246. {
  247. return renderLink(page);
  248. }
  249. })
  250. }
  251. </StyledAccordionDetails>
  252. </StyledAccordion>
  253. ):renderIconLink(link,styles);
  254. return linkItem
  255. }
  256. const renderRLink=(link)=>{
  257. let isActive=checkPath(link);
  258. let ispop=link.type===ROUTE_TYPE_POP||link.type===ROUTE_TYPE_POP_OR_FULL||link.type===ROUTE_TYPE_OUTLINK;
  259. let clname=`${isActive?'active':''} h4`;
  260. let linktxt=(
  261. <>
  262. <SvgIcon className={"icon"} herf={link.icon}/>
  263. <span>{i18n.t(`${link.title}`)}</span>
  264. </>
  265. )
  266. let target=link.type===ROUTE_TYPE_OUTLINK?"_blank":"";
  267. return ispop?(<a className={clname} target={target} href={link.path}>{linktxt}</a>):(<NavLink className={clname} to={link.path}>{linktxt}</NavLink>)
  268. }
  269. const renderSingleLink=(link)=>{
  270. let styles=getolstyles(link.style);
  271. return open?(
  272. <SingleLink olstyle={styles}>
  273. {
  274. renderRLink(link)
  275. }
  276. </SingleLink>
  277. ):renderIconLink(link,styles)
  278. }
  279. const renderLinkNav=(link)=>{
  280. let linkItem=(<></>);
  281. let subs=link.subs;
  282. let len=subs.length;
  283. if (len===0&&link.plv===""){
  284. linkItem=renderSingleLink(link);
  285. }
  286. else
  287. {
  288. linkItem=renderLink(link);
  289. }
  290. return linkItem;
  291. }
  292. const renderBlock=(block,bkey)=>{
  293. if (block==null)return (<Fragment key={`b_empty_${bkey}`}/>);
  294. let first=block[0];
  295. if (first==null)return (<Fragment key={`b_first_${bkey}`}/>);
  296. let blockID=first.block;
  297. let column=1;
  298. if (blocksColumn!=null){
  299. let blockC=blocksColumn[blockID];
  300. if (blockC!=null)column=blockC.column;
  301. }
  302. return (
  303. <Fragment key={`b_${blockID||bkey}`}>
  304. <StyledBlock open={open} column={column}>
  305. {
  306. block.map((link, index) => {
  307. return (<Fragment key={link.id||link.path||index}>{renderLinkNav(link)}</Fragment>)
  308. })
  309. }
  310. </StyledBlock>
  311. {renderLine()}
  312. </Fragment>
  313. )
  314. }
  315. const renderLinkBlocks=()=>{
  316. return (
  317. <>
  318. {
  319. navBlocks.map((block,index)=>{
  320. return renderBlock(block,index);
  321. })
  322. }
  323. </>
  324. )
  325. }
  326. const renderBonus=()=>{
  327. let styles=getolstyles(0);
  328. const ICON_SIZE={width:'1.5rem',height:'1.5rem',flex:'none'};
  329. // 桌面端用 hash 路由(#/promotion / #/bonus),由 RouteContainer 渲染成
  330. // popup-big 弹窗,当前页面 URL/内容不变;移动端继续走 /promotion / /bonus 全屏页。
  331. const desktop = !isMobile();
  332. const promotionPath = desktop ? "#/promotion" : ROUTE_PATH.PROMOTION;
  333. const bonusPath = desktop ? "#/bonus" : ROUTE_PATH.BONUS;
  334. const items=[
  335. {title:'links.promotion', icon:'icon_Giftbox', path:promotionPath, color:'#3bc117'},
  336. {title:'links.bonus', icon:'icon_Gift', path:bonusPath, color:'#b891ff'},
  337. {title:'links.share_rewards', icon:null, path:ROUTE_PATH.CASH_WHEEL, spin:true},
  338. ];
  339. const isActive=(p)=>pathname===p||(p && pathname.startsWith(p) && p!=='/');
  340. const spinIcon=(
  341. <span className="icon" style={{position:'relative',width:'1.7rem',height:'1.7rem',display:'inline-flex',alignItems:'center',justifyContent:'center',flex:'none',overflow:'visible',maxHeight:'none',maxWidth:'none'}}>
  342. <img src={spin_1} alt="" style={{position:'absolute',width:'1.55rem',height:'1.55rem',zIndex:1}}/>
  343. <img src={spin_2} alt="" style={{position:'absolute',width:'1.6rem',height:'1.6rem',zIndex:2,animation:'rotate 2s infinite linear'}}/>
  344. <img src={spin_3} alt="" style={{position:'absolute',width:'0.9rem',height:'auto',zIndex:3,marginTop:'-0.75em'}}/>
  345. </span>
  346. );
  347. const renderRow=(it)=>{
  348. const iconNode=it.spin?spinIcon:(
  349. <SvgIcon className={"icon"} herf={it.icon} style={{...ICON_SIZE, fill: it.color}}/>
  350. );
  351. const link={type:0,title:it.title,icon:it.icon||'icon_Share',path:it.path};
  352. if (!open){
  353. return (<span key={it.title}>{renderIconLink(link,styles,iconNode)}</span>);
  354. }
  355. return (
  356. <SingleLink key={it.title} olstyle={styles}>
  357. <NavLink className={`${isActive(it.path)?'active':''} h4`} to={it.path} style={{padding:'0.7rem 0.825rem',gap:'0.75rem'}}>
  358. {iconNode}
  359. <span style={{fontSize:'0.95rem',fontWeight:600,lineHeight:'1.2rem',whiteSpace:'nowrap',overflow:'hidden',textOverflow:'ellipsis'}}>
  360. {i18n.t(it.title)}
  361. </span>
  362. </NavLink>
  363. </SingleLink>
  364. );
  365. };
  366. return (
  367. <StyledBlock open={open}>
  368. <StyledBonus className="d-flex flex-column g-6" open={open}>
  369. {items.map(renderRow)}
  370. </StyledBonus>
  371. </StyledBlock>
  372. );
  373. }
  374. const renderLogout=()=>{
  375. return (
  376. <>
  377. {
  378. isLogin?(
  379. <StyledButton open={open} className="h4" onClick={()=>{
  380. dispatch(reqLogout());
  381. setOpen(false);
  382. }}>
  383. <SvgIcon className={"icon"} herf={"icon_Logout"}/>
  384. {open?i18n.t("links.log_out"):""}
  385. </StyledButton>
  386. ):(<></>)
  387. }
  388. </>
  389. )
  390. }
  391. const renderLanguage=()=>{
  392. return (
  393. <StyledBlock open={open}>
  394. <MTooltip disableHoverListener={open} title={i18n.t("quickbar.language")} arrow placement="right">
  395. <StyledButton open={open} className="h4" onClick={()=>{
  396. if (typeof setIsShowLang === "function") setIsShowLang(true);
  397. setOpen(false);
  398. }}>
  399. <SvgIcon className={"icon"} herf={"icon_Language"}/>
  400. {open?i18n.t("quickbar.language"):""}
  401. </StyledButton>
  402. </MTooltip>
  403. </StyledBlock>
  404. )
  405. }
  406. const renderSuport=()=>{
  407. return (
  408. <StyledBlock open={open}>
  409. {/*{renderLogout()}*/}
  410. <MTooltip disableHoverListener={open} title={i18n.t("links.live_support")} arrow placement="right">
  411. <StyledButton open={open} className="h4" fill={"#3bc117"} onClick={()=>{
  412. if (global.showCustomerService) global.showCustomerService();
  413. }}>
  414. <SvgIcon className={"icon"} herf={"icon_Support"}/>
  415. {open?i18n.t("links.live_support"):""}
  416. </StyledButton>
  417. </MTooltip>
  418. </StyledBlock>
  419. )
  420. }
  421. const handleTheme = (event, value) => {
  422. if (value){
  423. toggleTheme();
  424. }
  425. if (!isLogin)return;
  426. dispatch(reqChangeInfo({
  427. ThemeColor:value
  428. }));
  429. // console.log(value)
  430. }
  431. const renderThemeTabs=()=>{
  432. if (open){
  433. return (
  434. <StyledBlock open={open} >
  435. <StyledThemeTabs sx={{
  436. marginTop:'16px',
  437. }} className={theme} exclusive value={theme} onChange={handleTheme}>
  438. <div className="theme-bg"/>
  439. <StyledThemeTab value={APP_THEME.DARK}>
  440. <SvgIcon className={"icon"} herf={"icon_Dark"}/>
  441. {i18n.t("theme.dark")}
  442. </StyledThemeTab>
  443. <StyledThemeTab value={APP_THEME.LIGHT}>
  444. <SvgIcon className={"icon"} herf={"icon_Light"}/>
  445. {i18n.t("theme.light")}
  446. </StyledThemeTab>
  447. </StyledThemeTabs>
  448. </StyledBlock>
  449. )
  450. }
  451. else return(<></>);
  452. }
  453. const renderLine=(key="1")=>{
  454. // if (open)return(<></>);
  455. // else
  456. // {
  457. // return (
  458. // <div className="fold-line"></div>
  459. // )
  460. // }
  461. return (
  462. <div className="fold-line"></div>
  463. )
  464. }
  465. const renderBanner=()=>{
  466. let refimg=open?imgrefer:imgrefer_f;
  467. return (
  468. <StyledBlock open={open}>
  469. <MTooltip disableHoverListener={open} title={i18n.t("refer.title")} arrow placement="right">
  470. <StyledRefer open={open}>
  471. <img alt={"refer"} src={refimg}/>
  472. {
  473. open?(
  474. <div className={"ref-right"}>
  475. <div className={"t"}>{i18n.t("refer.title")}</div>
  476. <div className={"s"}>{i18n.t("refer.des")}</div>
  477. </div>
  478. ):(<></>)
  479. }
  480. </StyledRefer>
  481. </MTooltip>
  482. </StyledBlock>
  483. )
  484. }
  485. const renderContent=(showLanguage=false)=>{
  486. return (
  487. <>
  488. {renderBonus()}
  489. {/*{renderBanner()}*/}
  490. {renderLine()}
  491. {renderLinkBlocks()}
  492. {renderSuport()}
  493. {showLanguage?renderLanguage():null}
  494. {renderThemeTabs()}
  495. </>
  496. )
  497. }
  498. const renderDesktop=()=>{
  499. return (
  500. <Drawer variant="permanent" open={open} sx={{
  501. }}>
  502. <DrawerHeader>
  503. <SidebarTrigger/>
  504. {
  505. open?(
  506. <div className={'logo_con'}>
  507. <Logo/>
  508. </div>
  509. ):<></>
  510. }
  511. </DrawerHeader>
  512. <div className={`d-flex flex-column sidebar-nav ${visiblecls}`}>
  513. <div className={`d-flex flex-column g-4 ${visiblecls}`}>
  514. {
  515. renderContent()
  516. }
  517. </div>
  518. </div>
  519. </Drawer>
  520. )
  521. }
  522. const renderMobile=()=>{
  523. return (
  524. <MuiDrawer open={open} sx={{
  525. pointerEvents: 'auto',
  526. "& .MuiDrawer-paper": {
  527. width: drawMobieWidth,
  528. boxSizing: "border-box",
  529. overflow:'visible !important',
  530. backgroundColor:'var(--standard);',
  531. boxShadow:'var(--widget-shadow);',
  532. },
  533. "& .MuiModal-backdrop":{
  534. backgroundColor:'rgba(0,0,0,0)',
  535. }
  536. }} variant="temporary" onClose={()=>{
  537. setOpen(false);
  538. }}>
  539. <DrawerHeader>
  540. <SidebarTrigger/>
  541. {
  542. open?(
  543. <div className={'logo_con_m'}>
  544. <Logo/>
  545. </div>
  546. ):<></>
  547. }
  548. </DrawerHeader>
  549. <div className={`d-flex flex-column sidebar-nav ${visiblecls}`}>
  550. <div className={`d-flex flex-column g-4 ${visiblecls}`}>
  551. {
  552. renderContent(true)
  553. }
  554. </div>
  555. </div>
  556. </MuiDrawer>
  557. )
  558. }
  559. return (
  560. <>
  561. {
  562. isMobile()?(renderMobile()):renderDesktop()
  563. }
  564. </>
  565. )
  566. }
  567. export default Sidebar