PageLayout.jsx 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. /*
  2. Copyright (C) 2025 QuantumNous
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU Affero General Public License as
  5. published by the Free Software Foundation, either version 3 of the
  6. License, or (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU Affero General Public License for more details.
  11. You should have received a copy of the GNU Affero General Public License
  12. along with this program. If not, see <https://www.gnu.org/licenses/>.
  13. For commercial licensing, please contact support@quantumnous.com
  14. */
  15. import HeaderBar from './headerbar';
  16. import { Layout } from '@douyinfe/semi-ui';
  17. import SiderBar from './SiderBar';
  18. import App from '../../App';
  19. import FooterBar from './Footer';
  20. import { ToastContainer } from 'react-toastify';
  21. import React, { useContext, useEffect, useState } from 'react';
  22. import { useIsMobile } from '../../hooks/common/useIsMobile';
  23. import { useSidebarCollapsed } from '../../hooks/common/useSidebarCollapsed';
  24. import { useTranslation } from 'react-i18next';
  25. import {
  26. API,
  27. getLogo,
  28. getSystemName,
  29. showError,
  30. setStatusData,
  31. } from '../../helpers';
  32. import { UserContext } from '../../context/User';
  33. import { StatusContext } from '../../context/Status';
  34. import { useLocation } from 'react-router-dom';
  35. const { Sider, Content, Header } = Layout;
  36. const PageLayout = () => {
  37. const [, userDispatch] = useContext(UserContext);
  38. const [, statusDispatch] = useContext(StatusContext);
  39. const isMobile = useIsMobile();
  40. const [collapsed, , setCollapsed] = useSidebarCollapsed();
  41. const [drawerOpen, setDrawerOpen] = useState(false);
  42. const { i18n } = useTranslation();
  43. const location = useLocation();
  44. const cardProPages = [
  45. '/console/channel',
  46. '/console/log',
  47. '/console/redemption',
  48. '/console/user',
  49. '/console/token',
  50. '/console/midjourney',
  51. '/console/task',
  52. '/console/models',
  53. '/pricing',
  54. ];
  55. const shouldHideFooter = cardProPages.includes(location.pathname);
  56. const shouldInnerPadding =
  57. location.pathname.includes('/console') &&
  58. !location.pathname.startsWith('/console/chat') &&
  59. location.pathname !== '/console/playground';
  60. const isConsoleRoute = location.pathname.startsWith('/console');
  61. const showSider = isConsoleRoute && (!isMobile || drawerOpen);
  62. useEffect(() => {
  63. if (isMobile && drawerOpen && collapsed) {
  64. setCollapsed(false);
  65. }
  66. }, [isMobile, drawerOpen, collapsed, setCollapsed]);
  67. const loadUser = () => {
  68. let user = localStorage.getItem('user');
  69. if (user) {
  70. let data = JSON.parse(user);
  71. userDispatch({ type: 'login', payload: data });
  72. }
  73. };
  74. const loadStatus = async () => {
  75. try {
  76. const res = await API.get('/api/status');
  77. const { success, data } = res.data;
  78. if (success) {
  79. statusDispatch({ type: 'set', payload: data });
  80. setStatusData(data);
  81. } else {
  82. showError('Unable to connect to server');
  83. }
  84. } catch (error) {
  85. showError('Failed to load status');
  86. }
  87. };
  88. useEffect(() => {
  89. loadUser();
  90. loadStatus().catch(console.error);
  91. let systemName = getSystemName();
  92. if (systemName) {
  93. document.title = systemName;
  94. }
  95. let logo = getLogo();
  96. if (logo) {
  97. let linkElement = document.querySelector("link[rel~='icon']");
  98. if (linkElement) {
  99. linkElement.href = logo;
  100. }
  101. }
  102. const savedLang = localStorage.getItem('i18nextLng');
  103. if (savedLang) {
  104. i18n.changeLanguage(savedLang);
  105. }
  106. }, [i18n]);
  107. return (
  108. <Layout
  109. style={{
  110. height: '100vh',
  111. display: 'flex',
  112. flexDirection: 'column',
  113. overflow: isMobile ? 'visible' : 'hidden',
  114. }}
  115. >
  116. <Header
  117. style={{
  118. padding: 0,
  119. height: 'auto',
  120. lineHeight: 'normal',
  121. position: 'fixed',
  122. width: '100%',
  123. top: 0,
  124. zIndex: 100,
  125. }}
  126. >
  127. <HeaderBar
  128. onMobileMenuToggle={() => setDrawerOpen((prev) => !prev)}
  129. drawerOpen={drawerOpen}
  130. />
  131. </Header>
  132. <Layout
  133. style={{
  134. overflow: isMobile ? 'visible' : 'auto',
  135. display: 'flex',
  136. flexDirection: 'column',
  137. }}
  138. >
  139. {showSider && (
  140. <Sider
  141. style={{
  142. position: 'fixed',
  143. left: 0,
  144. top: '64px',
  145. zIndex: 99,
  146. border: 'none',
  147. paddingRight: '0',
  148. height: 'calc(100vh - 64px)',
  149. width: 'var(--sidebar-current-width)',
  150. }}
  151. >
  152. <SiderBar
  153. onNavigate={() => {
  154. if (isMobile) setDrawerOpen(false);
  155. }}
  156. />
  157. </Sider>
  158. )}
  159. <Layout
  160. style={{
  161. marginLeft: isMobile
  162. ? '0'
  163. : showSider
  164. ? 'var(--sidebar-current-width)'
  165. : '0',
  166. flex: '1 1 auto',
  167. display: 'flex',
  168. flexDirection: 'column',
  169. }}
  170. >
  171. <Content
  172. style={{
  173. flex: '1 0 auto',
  174. overflowY: isMobile ? 'visible' : 'hidden',
  175. WebkitOverflowScrolling: 'touch',
  176. padding: shouldInnerPadding ? (isMobile ? '5px' : '24px') : '0',
  177. position: 'relative',
  178. }}
  179. >
  180. <App />
  181. </Content>
  182. {!shouldHideFooter && (
  183. <Layout.Footer
  184. style={{
  185. flex: '0 0 auto',
  186. width: '100%',
  187. }}
  188. >
  189. <FooterBar />
  190. </Layout.Footer>
  191. )}
  192. </Layout>
  193. </Layout>
  194. <ToastContainer />
  195. </Layout>
  196. );
  197. };
  198. export default PageLayout;