first commit: one-api base code + SAAS plan document
26
web/berry/.gitignore
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.idea
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
8
web/berry/.prettierrc
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"bracketSpacing": true,
|
||||
"printWidth": 140,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"tabWidth": 2,
|
||||
"useTabs": false
|
||||
}
|
||||
61
web/berry/README.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# One API 前端界面
|
||||
|
||||
这个项目是 One API 的前端界面,它基于 [Berry Free React Admin Template](https://github.com/codedthemes/berry-free-react-admin-template) 进行开发。
|
||||
|
||||
## 使用的开源项目
|
||||
|
||||
使用了以下开源项目作为我们项目的一部分:
|
||||
|
||||
- [Berry Free React Admin Template](https://github.com/codedthemes/berry-free-react-admin-template)
|
||||
- [minimal-ui-kit](minimal-ui-kit)
|
||||
|
||||
## 开发说明
|
||||
|
||||
当添加新的渠道时,需要修改以下地方:
|
||||
|
||||
1. `web/berry/src/constants/ChannelConstants.js`
|
||||
|
||||
在该文件中的 `CHANNEL_OPTIONS` 添加新的渠道
|
||||
|
||||
```js
|
||||
export const CHANNEL_OPTIONS = {
|
||||
//key 为渠道ID
|
||||
1: {
|
||||
key: 1, // 渠道ID
|
||||
text: "OpenAI", // 渠道名称
|
||||
value: 1, // 渠道ID
|
||||
color: "primary", // 渠道列表显示的颜色
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
2. `web/berry/src/views/Channel/type/Config.js`
|
||||
|
||||
在该文件中的`typeConfig`添加新的渠道配置, 如果无需配置,可以不添加
|
||||
|
||||
```js
|
||||
const typeConfig = {
|
||||
// key 为渠道ID
|
||||
3: {
|
||||
inputLabel: {
|
||||
// 输入框名称 配置
|
||||
// 对应的字段名称
|
||||
base_url: "AZURE_OPENAI_ENDPOINT",
|
||||
other: "默认 API 版本",
|
||||
},
|
||||
prompt: {
|
||||
// 输入框提示 配置
|
||||
// 对应的字段名称
|
||||
base_url: "请填写AZURE_OPENAI_ENDPOINT",
|
||||
|
||||
// 注意:通过判断 `other` 是否有值来判断是否需要显示 `other` 输入框, 默认是没有值的
|
||||
other: "请输入默认API版本,例如:2024-03-01-preview",
|
||||
},
|
||||
modelGroup: "openai", // 模型组名称,这个值是给 填入渠道支持模型 按钮使用的。 填入渠道支持模型 按钮会根据这个值来获取模型组,如果填写默认是 openai
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## 许可证
|
||||
|
||||
本项目中使用的代码遵循 MIT 许可证。
|
||||
9
web/berry/jsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "commonjs",
|
||||
"baseUrl": "src"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
84
web/berry/package.json
Normal file
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"name": "one_api_web",
|
||||
"version": "1.0.0",
|
||||
"proxy": "http://127.0.0.1:3000",
|
||||
"private": true,
|
||||
"homepage": "",
|
||||
"dependencies": {
|
||||
"@emotion/cache": "^11.9.3",
|
||||
"@emotion/react": "^11.9.3",
|
||||
"@emotion/styled": "^11.9.3",
|
||||
"@mui/icons-material": "^5.8.4",
|
||||
"@mui/lab": "^5.0.0-alpha.88",
|
||||
"@mui/material": "^5.8.6",
|
||||
"@mui/system": "^5.8.6",
|
||||
"@mui/utils": "^5.8.6",
|
||||
"@mui/x-date-pickers": "^6.18.5",
|
||||
"@tabler/icons-react": "^2.44.0",
|
||||
"apexcharts": "3.35.3",
|
||||
"axios": "^0.27.2",
|
||||
"dayjs": "^1.11.10",
|
||||
"formik": "^2.2.9",
|
||||
"framer-motion": "^6.3.16",
|
||||
"history": "^5.3.0",
|
||||
"marked": "^4.1.1",
|
||||
"material-ui-popup-state": "^4.0.1",
|
||||
"notistack": "^3.0.1",
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "^18.2.0",
|
||||
"react-apexcharts": "1.4.0",
|
||||
"react-device-detect": "^2.2.2",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-perfect-scrollbar": "^1.5.8",
|
||||
"react-redux": "^8.0.2",
|
||||
"react-router": "6.3.0",
|
||||
"react-router-dom": "6.3.0",
|
||||
"react-scripts": "^5.0.1",
|
||||
"react-turnstile": "^1.1.2",
|
||||
"redux": "^4.2.0",
|
||||
"yup": "^0.32.11"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build && mv -f build ../build/berry",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app"
|
||||
]
|
||||
},
|
||||
"babel": {
|
||||
"presets": [
|
||||
"@babel/preset-react"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
"defaults",
|
||||
"not IE 11"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.21.4",
|
||||
"@babel/eslint-parser": "^7.21.3",
|
||||
"eslint": "^8.38.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-config-react-app": "^7.0.1",
|
||||
"eslint-plugin-flowtype": "^8.0.3",
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
"eslint-plugin-jsx-a11y": "^6.7.1",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-react": "^7.32.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"immutable": "^4.3.0",
|
||||
"prettier": "^2.8.7",
|
||||
"sass": "^1.53.0"
|
||||
}
|
||||
}
|
||||
BIN
web/berry/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
21
web/berry/public/index.html
Normal file
@@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<title>One API</title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<!-- Meta Tags-->
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#2296f3" />
|
||||
<meta
|
||||
name="description"
|
||||
content="OpenAI 接口聚合管理,支持多种渠道包括 Azure,可用于二次分发管理 key,仅单可执行文件,已打包好 Docker 镜像,一键部署,开箱即用"
|
||||
/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
52
web/berry/src/App.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
|
||||
import { ThemeProvider } from '@mui/material/styles';
|
||||
import { CssBaseline, StyledEngineProvider } from '@mui/material';
|
||||
import { SET_THEME } from 'store/actions';
|
||||
// routing
|
||||
import Routes from 'routes';
|
||||
|
||||
// defaultTheme
|
||||
import themes from 'themes';
|
||||
|
||||
// project imports
|
||||
import NavigationScroll from 'layout/NavigationScroll';
|
||||
|
||||
// auth
|
||||
import UserProvider from 'contexts/UserContext';
|
||||
import StatusProvider from 'contexts/StatusContext';
|
||||
import { SnackbarProvider } from 'notistack';
|
||||
|
||||
// ==============================|| APP ||============================== //
|
||||
|
||||
const App = () => {
|
||||
const dispatch = useDispatch();
|
||||
const customization = useSelector((state) => state.customization);
|
||||
|
||||
useEffect(() => {
|
||||
const storedTheme = localStorage.getItem('theme');
|
||||
if (storedTheme) {
|
||||
dispatch({ type: SET_THEME, theme: storedTheme });
|
||||
}
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<StyledEngineProvider injectFirst>
|
||||
<ThemeProvider theme={themes(customization)}>
|
||||
<CssBaseline />
|
||||
<NavigationScroll>
|
||||
<SnackbarProvider autoHideDuration={5000} maxSnack={3} anchorOrigin={{ vertical: 'top', horizontal: 'right' }}>
|
||||
<UserProvider>
|
||||
<StatusProvider>
|
||||
<Routes />
|
||||
</StatusProvider>
|
||||
</UserProvider>
|
||||
</SnackbarProvider>
|
||||
</NavigationScroll>
|
||||
</ThemeProvider>
|
||||
</StyledEngineProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
BIN
web/berry/src/assets/fonts/roboto-500.woff2
Normal file
BIN
web/berry/src/assets/fonts/roboto-700.woff2
Normal file
BIN
web/berry/src/assets/fonts/roboto-regular.woff2
Normal file
40
web/berry/src/assets/images/404.svg
Normal file
@@ -0,0 +1,40 @@
|
||||
<svg width="480" height="360" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M0 198.781c0 41.457 14.945 79.235 39.539 107.785 28.214 32.765 69.128 53.365 114.734 53.434a148.458 148.458 0 0056.495-11.036c9.051-3.699 19.182-3.274 27.948 1.107a75.774 75.774 0 0033.957 8.011c5.023 0 9.942-.495 14.7-1.434 13.581-2.67 25.94-8.99 36.089-17.94 6.379-5.627 14.548-8.456 22.898-8.446h.142c27.589 0 53.215-8.732 74.492-23.696 19.021-13.36 34.554-31.696 44.904-53.225C474.92 234.581 480 213.388 480 190.958c0-76.931-59.774-139.305-133.498-139.305-7.516 0-14.88.663-22.063 1.899C305.418 21.42 271.355 0 232.498 0a103.647 103.647 0 00-45.879 10.661c-13.24 6.487-25.011 15.705-34.641 26.939-32.697.544-62.93 11.69-87.675 30.291C25.351 97.155 0 144.882 0 198.781z"
|
||||
fill="url(#prefix__paint0_linear)" opacity=".2" />
|
||||
<g filter="url(#prefix__filter0_d)">
|
||||
<circle opacity=".15" cx="182.109" cy="97.623" r="44.623" fill="#FFC107" />
|
||||
<circle cx="182.109" cy="97.623" r="23.406" fill="url(#prefix__paint1_linear)" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M244.878 306.611c34.56 0 62.575-28.016 62.575-62.575 0-34.56-28.015-62.576-62.575-62.576-34.559 0-62.575 28.016-62.575 62.576 0 34.559 28.016 62.575 62.575 62.575zm0-23.186c21.754 0 39.389-17.635 39.389-39.389 0-21.755-17.635-39.39-39.389-39.39s-39.389 17.635-39.389 39.39c0 21.754 17.635 39.389 39.389 39.389z"
|
||||
fill="#061B64" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M174.965 264.592c0-4.133-1.492-5.625-5.637-5.625h-11.373v-66.611c0-4.476-1.492-5.637-5.638-5.637h-9.172a9.866 9.866 0 00-7.948 3.974l-55.03 68.274a11.006 11.006 0 00-1.957 6.787v5.968c0 4.145 1.492 5.637 5.625 5.637h54.676v21.707c0 4.133 1.492 5.625 5.625 5.625h8.12c4.146 0 5.638-1.492 5.638-5.625v-21.707h11.434c4.414 0 5.637-1.492 5.637-5.637v-7.13zm-72.42-5.625l35.966-44.415v44.415h-35.966zM411.607 264.592c0-4.133-1.492-5.625-5.638-5.625h-11.422v-66.611c0-4.476-1.492-5.637-5.637-5.637h-9.111a9.87 9.87 0 00-7.949 3.974l-55.03 68.274a11.011 11.011 0 00-1.981 6.787v5.968c0 4.145 1.492 5.637 5.626 5.637h54.687v21.707c0 4.133 1.492 5.625 5.626 5.625h8.12c4.145 0 5.637-1.492 5.637-5.625v-21.707h11.434c4.476 0 5.638-1.492 5.638-5.637v-7.13zm-72.42-5.625l35.965-44.415v44.415h-35.965z"
|
||||
fill="#2065D1" />
|
||||
<path opacity=".24"
|
||||
d="M425.621 117.222a8.267 8.267 0 00-9.599-8.157 11.129 11.129 0 00-9.784-5.87h-.403a13.23 13.23 0 00-20.365-14.078 13.23 13.23 0 00-5.316 14.078h-.403a11.153 11.153 0 100 22.293h38.68v-.073a8.279 8.279 0 007.19-8.193zM104.258 199.045a7.093 7.093 0 00-7.093-7.092c-.381.007-.761.039-1.138.097a9.552 9.552 0 00-8.425-5.026h-.343a11.348 11.348 0 10-22.012 0h-.342a9.564 9.564 0 100 19.114h33.177v-.061a7.107 7.107 0 006.176-7.032z"
|
||||
fill="#2065D1" />
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="prefix__paint0_linear" x1="328.81" y1="424.032" x2="505.393" y2="26.048"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#2065D1" />
|
||||
<stop offset="1" stop-color="#2065D1" stop-opacity=".01" />
|
||||
</linearGradient>
|
||||
<linearGradient id="prefix__paint1_linear" x1="135.297" y1="97.623" x2="182.109" y2="144.436"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FFE16A" />
|
||||
<stop offset="1" stop-color="#B78103" />
|
||||
</linearGradient>
|
||||
<filter id="prefix__filter0_d" x="51" y="49" width="394.621" height="277.611" filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" />
|
||||
<feOffset dx="8" dy="8" />
|
||||
<feGaussianBlur stdDeviation="6" />
|
||||
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.12 0" />
|
||||
<feBlend in2="BackgroundImageFix" result="effect1_dropShadow" />
|
||||
<feBlend in="SourceGraphic" in2="effect1_dropShadow" result="shape" />
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.9 KiB |
65
web/berry/src/assets/images/auth/auth-blue-card.svg
Normal file
|
After Width: | Height: | Size: 16 KiB |
39
web/berry/src/assets/images/auth/auth-pattern-dark.svg
Normal file
@@ -0,0 +1,39 @@
|
||||
<svg width="670" height="903" viewBox="0 0 670 903" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<mask id="mask0" mask-type="alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="670" height="903">
|
||||
<g opacity="0.2">
|
||||
<path d="M0 0H670V903H0V0Z" fill="white"/>
|
||||
</g>
|
||||
</mask>
|
||||
<g mask="url(#mask0)">
|
||||
<path d="M2030.91 374.849L426.331 1300.78" stroke="#8492C4"/>
|
||||
<path d="M426.409 -527.071L2030.72 399.311" stroke="#8492C4"/>
|
||||
<path d="M1919.22 310.39L314.731 1236.47" stroke="#8492C4"/>
|
||||
<path d="M314.731 -462.612L1919.22 463.467" stroke="#8492C4"/>
|
||||
<path d="M1807.54 245.932L203.055 1172.01" stroke="#8492C4"/>
|
||||
<path d="M203.052 -398.154L1807.54 527.925" stroke="#8492C4"/>
|
||||
<path d="M1695.87 181.473L91.3788 1107.55" stroke="#8492C4"/>
|
||||
<path d="M91.3744 -333.695L1695.86 592.384" stroke="#8492C4"/>
|
||||
<path d="M1584.19 117.014L-20.3012 1043.09" stroke="#8492C4"/>
|
||||
<path d="M-20.3044 -269.237L1584.19 656.843" stroke="#8492C4"/>
|
||||
<path d="M1472.51 52.5562L-131.98 978.636" stroke="#8492C4"/>
|
||||
<path d="M-131.983 -204.778L1472.51 721.301" stroke="#8492C4"/>
|
||||
<path d="M1360.83 -11.9023L-243.658 914.177" stroke="#8492C4"/>
|
||||
<path d="M-243.662 -140.319L1360.83 785.76" stroke="#8492C4"/>
|
||||
<path d="M1249.15 -76.3613L-355.336 849.718" stroke="#8492C4"/>
|
||||
<path d="M-355.341 -75.8608L1249.15 850.219" stroke="#8492C4"/>
|
||||
<path d="M1137.48 -140.819L-467.014 785.26" stroke="#8492C4"/>
|
||||
<path d="M-467.017 -11.4023L1137.47 914.677" stroke="#8492C4"/>
|
||||
<path d="M1025.8 -205.278L-578.692 720.801" stroke="#8492C4"/>
|
||||
<path d="M-578.693 53.0562L1025.8 979.136" stroke="#8492C4"/>
|
||||
<path d="M914.119 -269.736L-690.371 656.343" stroke="#8492C4"/>
|
||||
<path d="M-690.379 117.515L914.111 1043.59" stroke="#8492C4"/>
|
||||
<path d="M802.441 -334.195L-802.052 591.887" stroke="#8492C4"/>
|
||||
<path d="M-802.055 181.974L802.435 1108.05" stroke="#8492C4"/>
|
||||
<path d="M690.762 -398.654L-913.728 527.426" stroke="#8492C4"/>
|
||||
<path d="M-913.731 246.432L690.759 1172.51" stroke="#8492C4"/>
|
||||
<path d="M579.084 -463.112L-1025.41 462.967" stroke="#8492C4"/>
|
||||
<path d="M-1025.41 310.891L579.083 1236.97" stroke="#8492C4"/>
|
||||
<path d="M467.406 -527.571L-1136.91 398.811" stroke="#8492C4"/>
|
||||
<path d="M-1137.09 375.35L467.397 1301.43" stroke="#8492C4"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
39
web/berry/src/assets/images/auth/auth-pattern.svg
Normal file
@@ -0,0 +1,39 @@
|
||||
<svg width="670" height="903" viewBox="0 0 670 903" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<mask id="mask0" mask-type="alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="670" height="903">
|
||||
<g opacity="0.2">
|
||||
<path d="M0 0H670V903H0V0Z" fill="white"/>
|
||||
</g>
|
||||
</mask>
|
||||
<g mask="url(#mask0)">
|
||||
<path d="M2030.91 374.849L426.331 1300.78" stroke="rgba(0,0,0,0.30)"/>
|
||||
<path d="M426.409 -527.071L2030.72 399.311" stroke="rgba(0,0,0,0.30)"/>
|
||||
<path d="M1919.22 310.39L314.731 1236.47" stroke="rgba(0,0,0,0.30)"/>
|
||||
<path d="M314.731 -462.612L1919.22 463.467" stroke="rgba(0,0,0,0.30)"/>
|
||||
<path d="M1807.54 245.932L203.055 1172.01" stroke="rgba(0,0,0,0.30)"/>
|
||||
<path d="M203.052 -398.154L1807.54 527.925" stroke="rgba(0,0,0,0.30)"/>
|
||||
<path d="M1695.87 181.473L91.3788 1107.55" stroke="rgba(0,0,0,0.30)"/>
|
||||
<path d="M91.3744 -333.695L1695.86 592.384" stroke="rgba(0,0,0,0.30)"/>
|
||||
<path d="M1584.19 117.014L-20.3012 1043.09" stroke="rgba(0,0,0,0.30)"/>
|
||||
<path d="M-20.3044 -269.237L1584.19 656.843" stroke="rgba(0,0,0,0.30)"/>
|
||||
<path d="M1472.51 52.5562L-131.98 978.636" stroke="rgba(0,0,0,0.30)"/>
|
||||
<path d="M-131.983 -204.778L1472.51 721.301" stroke="rgba(0,0,0,0.30)"/>
|
||||
<path d="M1360.83 -11.9023L-243.658 914.177" stroke="rgba(0,0,0,0.30)"/>
|
||||
<path d="M-243.662 -140.319L1360.83 785.76" stroke="rgba(0,0,0,0.30)"/>
|
||||
<path d="M1249.15 -76.3613L-355.336 849.718" stroke="rgba(0,0,0,0.30)"/>
|
||||
<path d="M-355.341 -75.8608L1249.15 850.219" stroke="rgba(0,0,0,0.30)"/>
|
||||
<path d="M1137.48 -140.819L-467.014 785.26" stroke="rgba(0,0,0,0.30)"/>
|
||||
<path d="M-467.017 -11.4023L1137.47 914.677" stroke="rgba(0,0,0,0.30)"/>
|
||||
<path d="M1025.8 -205.278L-578.692 720.801" stroke="rgba(0,0,0,0.30)"/>
|
||||
<path d="M-578.693 53.0562L1025.8 979.136" stroke="rgba(0,0,0,0.30)"/>
|
||||
<path d="M914.119 -269.736L-690.371 656.343" stroke="rgba(0,0,0,0.30)"/>
|
||||
<path d="M-690.379 117.515L914.111 1043.59" stroke="rgba(0,0,0,0.30)"/>
|
||||
<path d="M802.441 -334.195L-802.052 591.887" stroke="rgba(0,0,0,0.30)"/>
|
||||
<path d="M-802.055 181.974L802.435 1108.05" stroke="rgba(0,0,0,0.30)"/>
|
||||
<path d="M690.762 -398.654L-913.728 527.426" stroke="rgba(0,0,0,0.30)"/>
|
||||
<path d="M-913.731 246.432L690.759 1172.51" stroke="rgba(0,0,0,0.30)"/>
|
||||
<path d="M579.084 -463.112L-1025.41 462.967" stroke="rgba(0,0,0,0.30)"/>
|
||||
<path d="M-1025.41 310.891L579.083 1236.97" stroke="rgba(0,0,0,0.30)"/>
|
||||
<path d="M467.406 -527.571L-1136.91 398.811" stroke="rgba(0,0,0,0.30)"/>
|
||||
<path d="M-1137.09 375.35L467.397 1301.43" stroke="rgba(0,0,0,0.30)"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
69
web/berry/src/assets/images/auth/auth-purple-card.svg
Normal file
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 272 KiB |
40
web/berry/src/assets/images/auth/auth-signup-white-card.svg
Normal file
|
After Width: | Height: | Size: 15 KiB |
5
web/berry/src/assets/images/icons/earning.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19 9H9C7.89543 9 7 9.89543 7 11V17C7 18.1046 7.89543 19 9 19H19C20.1046 19 21 18.1046 21 17V11C21 9.89543 20.1046 9 19 9Z" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M14 16C15.1046 16 16 15.1046 16 14C16 12.8954 15.1046 12 14 12C12.8954 12 12 12.8954 12 14C12 15.1046 12.8954 16 14 16Z" fill="#90CAF9"/>
|
||||
<path d="M17 9V7C17 6.46957 16.7893 5.96086 16.4142 5.58579C16.0391 5.21071 15.5304 5 15 5H5C4.46957 5 3.96086 5.21071 3.58579 5.58579C3.21071 5.96086 3 6.46957 3 7V13C3 13.5304 3.21071 14.0391 3.58579 14.4142C3.96086 14.7893 4.46957 15 5 15H7" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 794 B |
1
web/berry/src/assets/images/icons/github.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg t="1702350903010" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4215" width="32" height="32"><path d="M512 85.333333C276.266667 85.333333 85.333333 276.266667 85.333333 512a426.410667 426.410667 0 0 0 291.754667 404.821333c21.333333 3.712 29.312-9.088 29.312-20.309333 0-10.112-0.554667-43.690667-0.554667-79.445333-107.178667 19.754667-134.912-26.112-143.445333-50.133334-4.821333-12.288-25.6-50.133333-43.733333-60.288-14.933333-7.978667-36.266667-27.733333-0.554667-28.245333 33.621333-0.554667 57.6 30.933333 65.621333 43.733333 38.4 64.512 99.754667 46.378667 124.245334 35.2 3.754667-27.733333 14.933333-46.378667 27.221333-57.045333-94.933333-10.666667-194.133333-47.488-194.133333-210.688 0-46.421333 16.512-84.778667 43.733333-114.688-4.266667-10.666667-19.2-54.4 4.266667-113.066667 0 0 35.712-11.178667 117.333333 43.776a395.946667 395.946667 0 0 1 106.666667-14.421333c36.266667 0 72.533333 4.778667 106.666666 14.378667 81.578667-55.466667 117.333333-43.690667 117.333334-43.690667 23.466667 58.666667 8.533333 102.4 4.266666 113.066667 27.178667 29.866667 43.733333 67.712 43.733334 114.645333 0 163.754667-99.712 200.021333-194.645334 210.688 15.445333 13.312 28.8 38.912 28.8 78.933333 0 57.045333-0.554667 102.912-0.554666 117.333334 0 11.178667 8.021333 24.490667 29.354666 20.224A427.349333 427.349333 0 0 0 938.666667 512c0-235.733333-190.933333-426.666667-426.666667-426.666667z" fill="#000000" p-id="4216"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
5
web/berry/src/assets/images/icons/lark.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg t="1723134993089" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7682"
|
||||
width="200" height="200">
|
||||
<path d="M138.67 472.593v267.659l1.085 0.825c30.488 23.11 68.369 45.41 109.072 62.904l1.473 0.63c57.452 24.487 117.23 38.204 176.468 38.39 49.026 0.15 94.773-6.199 138.432-20.266 23.88-7.694 47.048-17.68 69.546-30.137-16.42-1.696-32.855-4.635-49.166-8.6-68.949-16.77-138.975-52.578-203.999-96.138-92.077-61.684-178.453-141.863-242.91-215.267zM597.31 182H350.745l25.776 22.626 6.472 5.7 7.9 6.985 6.658 5.915 4.791 4.275 6.144 5.509 5.17 4.665 4.297 3.899 4.876 4.451 4.077 3.748 3.337 3.085 4.594 4.276 3.238 3.034 3.21 3.026 1.917 1.814 1.633 1.553a1573.085 1573.085 0 0 1 16.368 15.806c61.384 60.116 109.769 118.745 146.283 181.615 34.096-38.136 67.506-67.95 100.368-90.075-0.08-0.224-0.158-0.452-0.233-0.681-9.674-29.434-24.133-63.778-41.697-96.235-20.272-37.46-43.21-69.797-68.284-94.664l-0.331-0.327z m149.927 506.476a42.573 42.573 0 0 1 13.201-4.727c12.04-16.686 22.616-34.537 32.12-53.8 5.835-11.824 11.243-24.101 16.582-37.512l1.185-2.999c0.593-1.509 1.184-3.032 1.776-4.572l1.184-3.099a817.09 817.09 0 0 0 2.963-7.94l0.902-2.466 1.54-4.256 1.24-3.478 0.944-2.668 1.282-3.658 1.32-3.797 1.364-3.963 1.784-5.225 10.079-29.815 1.237-3.615 1.619-4.688 1.193-3.419 1.176-3.34 1.16-3.261 0.766-2.133 1.137-3.139 0.75-2.052 0.747-2.022 1.11-2.977 0.735-1.948 0.731-1.92 0.728-1.893 1.086-2.789 1.08-2.731 0.717-1.79 0.716-1.766 0.715-1.743 0.714-1.721 1.07-2.54 0.713-1.668 0.714-1.649 0.357-0.816 0.715-1.62 0.358-0.803 0.717-1.593 0.72-1.576 0.72-1.56 0.725-1.545 0.727-1.53 0.73-1.515 0.734-1.503 0.739-1.49 0.743-1.478 0.373-0.735 0.75-1.461 0.756-1.452 0.761-1.443 0.768-1.434 0.774-1.426 0.78-1.42 0.789-1.413 0.796-1.407 0.803-1.402 0.813-1.399c0.272-0.465 0.545-0.93 0.82-1.395 6.898-11.644 14.45-22.205 22.804-31.716l0.381-0.432-0.363-0.1a167.736 167.736 0 0 0-17.333-3.808l-1.17-0.187c-19.22-3.015-38.771-2.474-59.435 2.33C766 404.728 707.642 444.655 643.04 520.58c-50.09 58.868-112.372 100.194-178.36 119.035 48.387 29.017 96.32 50.621 141.075 61.507 55.428 13.48 102.617 9.838 140.868-12.298z m40.248 75.25a229.325 229.325 0 0 1-21.023 10.522 420.333 420.333 0 0 1-20.405 18.512c-50.095 42.57-103.865 72.408-161.229 90.891-50.432 16.25-102.815 23.52-158.258 23.355-68.336-0.22-136.434-15.846-201.363-43.52-52.844-22.523-101.532-52.32-138.874-83.06a32.016 32.016 0 0 1-11.662-24.212l-0.003-364.272a663.709 663.709 0 0 1-3.155-4.546l-0.395-0.584c-12.855-19.356-7.842-45.51 11.382-58.72 19.417-13.345 45.98-8.428 59.33 10.98 49.099 71.385 141.145 170.175 245.108 248.023 60.719-0.825 122.443-27.243 174.57-74.197-34.118-63.594-81.951-122.977-145.085-184.807a1497.032 1497.032 0 0 0-16.553-15.971l-3.41-3.23-3.07-2.888-3.727-3.484-3.783-3.514-3.854-3.556-3.938-3.61-5.403-4.921-4.184-3.786-5.043-4.54-4.477-4.008-6.214-5.538-7.36-6.527-9.592-8.463-8.388-7.371-15.995-14.032-10.596-9.324-7.673-6.781-6.151-5.465-4.92-4.397-4.536-4.081-3.615-3.28-3.395-3.104-2.68-2.472-2.058-1.913-2.48-2.324-1.919-1.814-1.871-1.783-2.735-2.632-2.231-2.172-1.92-1.888a702.481 702.481 0 0 1-6.747-6.754c-19.741-20.021-5.898-53.785 21.938-54.458l0.848-0.01h341.332a32 32 0 0 1 21.426 8.232c34.675 31.26 64.87 72.453 90.762 120.299 17.457 32.258 32.066 66.007 42.695 96.357 13.332-5.523 26.586-9.793 39.771-12.858 28.861-6.71 56.686-7.48 83.844-3.22 21.406 3.358 40.992 9.48 62.014 18.392l1.835 0.783 1.842 0.796 1.861 0.816 1.894 0.839 1.94 0.868 1.996 0.903 11.45 5.265c23.934 10.99 25.088 44.57 1.966 57.177-18.753 10.224-33.09 24.764-45.475 45.42l-0.82 1.383-0.65 1.111-0.643 1.119-0.64 1.126-0.634 1.136-0.631 1.144-0.627 1.155-0.623 1.165-0.62 1.176-0.618 1.188-0.615 1.2-0.612 1.214-0.61 1.227-0.609 1.24-0.607 1.257-0.606 1.272-0.605 1.287-0.907 1.963-0.604 1.33-0.605 1.35-0.605 1.368-0.606 1.387-0.606 1.407-0.608 1.428-0.61 1.45-0.919 2.216-0.923 2.267-0.62 1.542-0.622 1.565-0.626 1.591-0.63 1.617-0.633 1.643-0.638 1.67-1.29 3.423-0.981 2.642-0.994 2.71-0.67 1.845-1.017 2.826-1.38 3.88-1.054 2.999-1.072 3.076-1.09 3.155-1.11 3.237-10.062 29.76-1.935 5.673-1.84 5.35-1.42 4.084-1.386 3.945-1.022 2.885-1.35 3.772-0.672 1.862-1.01 2.775c-8.668 23.731-17.234 44.304-27.005 64.106-11.498 23.305-24.457 44.997-39.33 65.303 0.773 15.454-6.924 30.784-21.242 39.07z"
|
||||
p-id="7683" fill="#2c2c2c"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.3 KiB |
7
web/berry/src/assets/images/icons/oidc.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<svg t="1723135116886" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="10969" width="200" height="200">
|
||||
<path d="M512 960C265 960 64 759 64 512S265 64 512 64s448 201 448 448-201 448-448 448z m0-882.6c-239.7 0-434.6 195-434.6 434.6s195 434.6 434.6 434.6 434.6-195 434.6-434.6S751.7 77.4 512 77.4z"
|
||||
p-id="10970" fill="#2c2c2c" stroke="#2c2c2c" stroke-width="60"></path>
|
||||
<path d="M197.7 512c0-78.3 31.6-98.8 87.2-98.8 56.2 0 87.2 20.5 87.2 98.8s-31 98.8-87.2 98.8c-55.7 0-87.2-20.5-87.2-98.8z m130.4 0c0-46.8-7.8-64.5-43.2-64.5-35.2 0-42.9 17.7-42.9 64.5 0 47.1 7.8 63.7 42.9 63.7 35.4 0 43.2-16.6 43.2-63.7zM409.7 415.9h42.1V608h-42.1V415.9zM653.9 512c0 74.2-37.1 96.1-93.6 96.1h-65.9V415.9h65.9c56.5 0 93.6 16.1 93.6 96.1z m-43.5 0c0-49.3-17.7-60.6-52.3-60.6h-21.6v120.7h21.6c35.4 0 52.3-13.3 52.3-60.1zM686.5 512c0-74.2 36.3-98.8 92.7-98.8 18.3 0 33.2 2.2 44.8 6.4v36.3c-11.9-4.2-26-6.6-42.1-6.6-34.6 0-49.8 15.5-49.8 62.6 0 50.1 15.2 62.6 49.3 62.6 15.8 0 30.2-2.2 44.8-7.5v36c-11.3 4.7-28.5 8-46.8 8-56.1-0.2-92.9-18.7-92.9-99z"
|
||||
p-id="10971" fill="#2c2c2c" stroke="#2c2c2c" stroke-width="20"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
1
web/berry/src/assets/images/icons/shape-avatar.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg height="62" viewBox="0 0 144 62" width="144" xmlns="http://www.w3.org/2000/svg"><path d="m111.34 23.88c-10.62-10.46-18.5-23.88-38.74-23.88h-1.2c-20.24 0-28.12 13.42-38.74 23.88-7.72 9.64-19.44 11.74-32.66 12.12v26h144v-26c-13.22-.38-24.94-2.48-32.66-12.12z" fill="#fff" fill-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 302 B |
6
web/berry/src/assets/images/icons/social-google.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.06129 13.2253L4.31871 15.9975L1.60458 16.0549C0.793457 14.5504 0.333374 12.8292 0.333374 11C0.333374 9.23119 0.763541 7.56319 1.52604 6.09448H1.52662L3.94296 6.53748L5.00146 8.93932C4.77992 9.58519 4.65917 10.2785 4.65917 11C4.65925 11.783 4.80108 12.5332 5.06129 13.2253Z" fill="#FBBB00"/>
|
||||
<path d="M21.4804 9.00732C21.6029 9.65257 21.6668 10.3189 21.6668 11C21.6668 11.7637 21.5865 12.5086 21.4335 13.2271C20.9143 15.6722 19.5575 17.8073 17.678 19.3182L17.6774 19.3177L14.6339 19.1624L14.2031 16.4734C15.4503 15.742 16.425 14.5974 16.9384 13.2271H11.2346V9.00732H17.0216H21.4804Z" fill="#518EF8"/>
|
||||
<path d="M17.6772 19.3176L17.6777 19.3182C15.8498 20.7875 13.5277 21.6666 11 21.6666C6.93783 21.6666 3.40612 19.3962 1.60449 16.0549L5.0612 13.2253C5.96199 15.6294 8.28112 17.3408 11 17.3408C12.1686 17.3408 13.2634 17.0249 14.2029 16.4734L17.6772 19.3176Z" fill="#28B446"/>
|
||||
<path d="M17.8085 2.78892L14.353 5.61792C13.3807 5.01017 12.2313 4.65908 11 4.65908C8.21963 4.65908 5.85713 6.44896 5.00146 8.93925L1.52658 6.09442H1.526C3.30125 2.67171 6.8775 0.333252 11 0.333252C13.5881 0.333252 15.9612 1.25517 17.8085 2.78892Z" fill="#F14336"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
1
web/berry/src/assets/images/icons/wechat.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg t="1702350975929" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2474" width="32" height="32"><path d="M512 1024C229.23264 1024 0 794.76736 0 512S229.23264 0 512 0s512 229.23264 512 512-229.23264 512-512 512z m212.0704-290.0992l41.5744 24.064c9.216 5.3248 14.5408 1.26976 11.83744-9.03168l-9.0112-34.4064a9.76896 9.76896 0 0 1 3.76832-10.4448C813.50656 674.75456 839.68 631.0912 839.68 582.32832c0-88.28928-85.79072-159.86688-191.61088-159.86688s-191.61088 71.5776-191.61088 159.86688c0 88.2688 85.79072 159.86688 191.61088 159.86688 22.9376 0 44.91264-3.35872 65.26976-9.5232a13.55776 13.55776 0 0 1 10.73152 1.2288z m-366.63296-116.08064a271.85152 271.85152 0 0 0 89.98912 10.89536 146.61632 146.61632 0 0 1-7.7824-47.16544c0-96.31744 94.33088-174.3872 210.71872-174.3872 4.07552 0 8.11008 0.08192 12.12416 0.28672C645.2224 315.84256 549.96992 245.76 435.03616 245.76 307.87584 245.76 204.8 331.55072 204.8 437.37088c0 58.1632 31.15008 110.2848 80.32256 145.42848 4.62848 3.31776 6.7584 9.13408 5.28384 14.62272l-10.83392 40.38656c-3.2768 12.1856 2.99008 16.95744 13.88544 10.58816l49.29536-28.79488a18.59584 18.59584 0 0 1 14.68416-1.78176z m353.73056-60.74368a26.0096 26.0096 0 1 1 0-52.0192 26.0096 26.0096 0 0 1 0 52.0192z m-126.976 0a26.0096 26.0096 0 1 1 0-52.0192 26.0096 26.0096 0 0 1 0 52.0192z m-72.66304-150.69184a30.59712 30.59712 0 1 1 0-61.19424 30.59712 30.59712 0 0 1 0 61.19424z m-153.74336 0a30.59712 30.59712 0 1 1 0-61.19424 30.59712 30.59712 0 0 1 0 61.19424z" fill="#2BD418" p-id="2475"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
BIN
web/berry/src/assets/images/invite/cover.jpg
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
web/berry/src/assets/images/invite/cwok_casual_19.webp
Normal file
|
After Width: | Height: | Size: 174 KiB |
15
web/berry/src/assets/images/logo-2.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<svg viewBox="0 0 590 265" xmlns="http://www.w3.org/2000/svg" version="1.1">
|
||||
|
||||
<g>
|
||||
<title>Layer 1</title>
|
||||
<ellipse transform="rotate(103.6 287.6 32.87)" id="svg_1" ry="28.14" rx="28.47" cy="32.87" cx="287.60001" fill="#0d161f"/>
|
||||
<path id="svg_2" d="m232.92,128.89c3.78,27.29 -1.81,55.44 -17.71,78.09a2.62,2.62 0 0 0 -0.06,2.92c1.24,1.92 2.96,5.05 5.56,4.94q5.25,-0.22 10.79,0.11a1.26,1.26 0 0 1 1.19,1.27l-0.4,42.53a1.31,1.31 0 0 1 -1.31,1.3q-16.77,-0.09 -36.53,0.01q-2.25,0.02 -3.71,-1.56q-16.02,-17.28 -31.98,-35.32c-5.13,-5.8 -10.18,-11.16 -14.86,-17.59a1.35,1.34 -31.1 0 1 0.5,-2q12.88,-6.32 22.13,-17.12q18.18,-21.23 15.08,-48.84q-2.66,-23.7 -22.4,-40.46q-23.43,-19.9 -54.88,-13.86c-4.1,0.79 -7.83,2.5 -11.72,4.12q-11.86,4.94 -20.59,14.64c-14.25,15.81 -20.07,36.4 -15.05,57.16q4.99,20.63 22.86,35.71c10.45,8.81 23.7,13.12 37.26,14.18q1.47,0.11 3.6,2.65c11.68,13.89 24.48,27.72 35.94,41.96a0.43,0.43 0 0 1 -0.21,0.68q-22.51,7.27 -47.37,5.37q-19.4,-1.47 -39.74,-11.22q-18.27,-8.75 -30.59,-21.28q-18.66,-18.98 -28.02,-43.57q-10.8,-28.4 -4.93,-58.67c1.59,-8.17 4.03,-17 7.42,-24.61q5.08,-11.38 11.61,-20.64q25.41,-36.03 68.45,-46.13q32.42,-7.61 64.23,3.92q25.31,9.17 43.2,27.31c16.85,17.09 28.91,40.01 32.24,64z" fill="#0d161f"/>
|
||||
<path id="svg_3" d="m499.47,180.61c6.45,13.53 16.44,21.75 31.96,22q11.94,0.19 22.17,-5.36q2.21,-1.2 3.93,0.69q12.56,13.78 24.89,28.47q1.21,1.44 1.44,3.13a0.95,0.95 0 0 1 -0.36,0.89c-1.62,1.23 -3.33,2.71 -5.03,3.69q-29.37,17.01 -62.47,11.31c-20.61,-3.55 -39.05,-15.24 -51.47,-32.51q-6.4,-8.89 -9.91,-17.08c-2.62,-6.12 -4.73,-13.3 -5.41,-20.08q-3.96,-39.88 22.94,-67.74c9.48,-9.81 21.15,-16.67 34.39,-19.49c16.54,-3.53 34.64,-1.83 48.77,7.1q13.92,8.79 21.13,20.4q11.07,17.84 10.48,38.92c-0.02,0.94 -0.21,1.81 -0.85,2.54q-7.73,8.77 -18.71,20.16c-1.28,1.32 -2.61,2.26 -4.51,2.23q-24.45,-0.37 -51.64,-0.41q-5.03,0 -10.84,-0.22a0.96,0.95 -11.7 0 0 -0.9,1.36zm1.12,-37.17q-0.55,1.19 -0.63,2.34q-0.08,1.01 0.94,1.03q19.01,0.25 36.98,0.01q0.5,0 0.94,-0.22q0.57,-0.28 0.44,-0.9q-2.34,-11.6 -14.11,-15.25q-3.59,-1.11 -6.44,-0.57q-13.07,2.5 -18.12,13.56z" fill="#0d161f"/>
|
||||
<path id="svg_4" d="m312.3,100.22a0.5,0.49 -22.1 0 0 0.84,0.35q2.76,-2.64 5.82,-4.31q8.45,-4.62 16.71,-6.57c15.81,-3.72 33.58,-3.2 48.2,3.95q24.49,11.98 35.05,35.76c4.66,10.5 5.44,22.96 5.5,35.35q0.21,49.99 -0.12,88q-0.03,3.06 -0.08,6.16a1.32,1.32 0 0 1 -1.33,1.3q-20.22,-0.18 -40.18,-0.23q-3.64,-0.01 -8.13,-0.44a1.06,1.05 -87.3 0 1 -0.95,-1.05q0.02,-45.49 -0.22,-92.99c-0.03,-6.25 -1.21,-13.88 -5.05,-18.95q-5.33,-7.03 -12.32,-10.18c-10.99,-4.93 -24.52,-1.84 -33.13,6.37q-10.01,9.53 -10.07,23.76q-0.11,25.46 -0.1,48.98c0,3.52 -0.06,8.31 -1.1,11.68c-4.37,14.04 -17.31,19.5 -31.04,16.77c-8.22,-1.64 -15.07,-7.75 -17.62,-15.62q-1.45,-4.49 -1.42,-10.2q0.3,-64.69 0.1,-129.86a0.47,0.47 0 0 1 0.47,-0.47l48.46,-0.35a1.56,1.55 89.4 0 1 1.56,1.54l0.15,11.25z" fill="#0d161f"/>
|
||||
<path id="svg_5" d="m265.63,344.43a2.02,2.01 76.7 0 0 -1.85,-1.15l-17.03,0.24a2.25,2.22 9.3 0 0 -2.06,1.46l-2.86,7.84a2.47,2.46 -79.1 0 1 -2.38,1.62l-6.23,-0.19q-1.19,-0.04 -0.88,-1.19q1.38,-5.23 2.81,-8.7c3.41,-8.3 6.48,-16.83 10.12,-25.35q2.96,-6.93 5.21,-14.24c0.46,-1.52 1.69,-2.64 3.37,-2.63c2.02,0 4.68,-0.78 5.7,1.58q7.68,17.74 18.16,44.75q0.96,2.46 1.48,5a0.67,0.66 84.3 0 1 -0.65,0.8l-6.05,-0.02q-2.16,-0.01 -3.1,-1.96l-3.76,-7.86zm-16.73,-10.31a0.34,0.34 0 0 0 0.32,0.47l12.85,-0.36a0.34,0.34 0 0 0 0.3,-0.48l-6.84,-14.7a0.34,0.34 0 0 0 -0.62,0.02l-6.01,15.05z" fill="#0d161f"/>
|
||||
<rect id="svg_6" rx="2.17" height="52.28" width="9.84" y="302.19" x="345.67" fill="#0d161f"/>
|
||||
<path id="svg_7" d="m303.07,338.46l-0.15,14.42q-0.01,1.55 -1.56,1.52l-5.84,-0.12q-1.79,-0.04 -1.81,-1.83c-0.24,-15.33 -0.25,-30.89 -0.27,-47.22q-0.01,-2.99 2.55,-3.06q12.47,-0.33 20.15,0.8q8.61,1.25 12.86,9.17c2.95,5.49 2.53,13.5 -1.5,18.65c-5.57,7.14 -14.88,6.62 -23.24,6.51a1.17,1.17 0 0 0 -1.19,1.16zm-0.15,-24.81l0.16,12.72a1.72,1.72 0 0 0 1.74,1.7l6.07,-0.08a10.01,7.98 -0.7 0 0 9.91,-8.1l0,-0.2a10.01,7.98 -0.7 0 0 -10.11,-7.86l-6.07,0.08a1.72,1.72 0 0 0 -1.7,1.74z" fill="#0d161f"/>
|
||||
<rect id="svg_8" rx="3.58" height="7.26" width="79.2" y="322.99" x="107" fill="#0d161f"/>
|
||||
<rect id="svg_9" rx="3.81" height="7.72" width="79.1" y="322.78" x="417.27" fill="#0d161f"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
13
web/berry/src/assets/images/logo-white.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<svg viewBox="0 0 590 360" xmlns="http://www.w3.org/2000/svg" version="1.1">
|
||||
<g>
|
||||
<ellipse transform="rotate(103.6 287.6 32.87)" id="svg_1" ry="28.14" rx="28.47" cy="32.87" cx="287.60001" fill="#fff"/>
|
||||
<path id="svg_2" d="m232.92,128.89c3.78,27.29 -1.81,55.44 -17.71,78.09a2.62,2.62 0 0 0 -0.06,2.92c1.24,1.92 2.96,5.05 5.56,4.94q5.25,-0.22 10.79,0.11a1.26,1.26 0 0 1 1.19,1.27l-0.4,42.53a1.31,1.31 0 0 1 -1.31,1.3q-16.77,-0.09 -36.53,0.01q-2.25,0.02 -3.71,-1.56q-16.02,-17.28 -31.98,-35.32c-5.13,-5.8 -10.18,-11.16 -14.86,-17.59a1.35,1.34 -31.1 0 1 0.5,-2q12.88,-6.32 22.13,-17.12q18.18,-21.23 15.08,-48.84q-2.66,-23.7 -22.4,-40.46q-23.43,-19.9 -54.88,-13.86c-4.1,0.79 -7.83,2.5 -11.72,4.12q-11.86,4.94 -20.59,14.64c-14.25,15.81 -20.07,36.4 -15.05,57.16q4.99,20.63 22.86,35.71c10.45,8.81 23.7,13.12 37.26,14.18q1.47,0.11 3.6,2.65c11.68,13.89 24.48,27.72 35.94,41.96a0.43,0.43 0 0 1 -0.21,0.68q-22.51,7.27 -47.37,5.37q-19.4,-1.47 -39.74,-11.22q-18.27,-8.75 -30.59,-21.28q-18.66,-18.98 -28.02,-43.57q-10.8,-28.4 -4.93,-58.67c1.59,-8.17 4.03,-17 7.42,-24.61q5.08,-11.38 11.61,-20.64q25.41,-36.03 68.45,-46.13q32.42,-7.61 64.23,3.92q25.31,9.17 43.2,27.31c16.85,17.09 28.91,40.01 32.24,64z" fill="#fff"/>
|
||||
<path id="svg_3" d="m499.47,180.61c6.45,13.53 16.44,21.75 31.96,22q11.94,0.19 22.17,-5.36q2.21,-1.2 3.93,0.69q12.56,13.78 24.89,28.47q1.21,1.44 1.44,3.13a0.95,0.95 0 0 1 -0.36,0.89c-1.62,1.23 -3.33,2.71 -5.03,3.69q-29.37,17.01 -62.47,11.31c-20.61,-3.55 -39.05,-15.24 -51.47,-32.51q-6.4,-8.89 -9.91,-17.08c-2.62,-6.12 -4.73,-13.3 -5.41,-20.08q-3.96,-39.88 22.94,-67.74c9.48,-9.81 21.15,-16.67 34.39,-19.49c16.54,-3.53 34.64,-1.83 48.77,7.1q13.92,8.79 21.13,20.4q11.07,17.84 10.48,38.92c-0.02,0.94 -0.21,1.81 -0.85,2.54q-7.73,8.77 -18.71,20.16c-1.28,1.32 -2.61,2.26 -4.51,2.23q-24.45,-0.37 -51.64,-0.41q-5.03,0 -10.84,-0.22a0.96,0.95 -11.7 0 0 -0.9,1.36zm1.12,-37.17q-0.55,1.19 -0.63,2.34q-0.08,1.01 0.94,1.03q19.01,0.25 36.98,0.01q0.5,0 0.94,-0.22q0.57,-0.28 0.44,-0.9q-2.34,-11.6 -14.11,-15.25q-3.59,-1.11 -6.44,-0.57q-13.07,2.5 -18.12,13.56z" fill="#fff"/>
|
||||
<path id="svg_4" d="m312.3,100.22a0.5,0.49 -22.1 0 0 0.84,0.35q2.76,-2.64 5.82,-4.31q8.45,-4.62 16.71,-6.57c15.81,-3.72 33.58,-3.2 48.2,3.95q24.49,11.98 35.05,35.76c4.66,10.5 5.44,22.96 5.5,35.35q0.21,49.99 -0.12,88q-0.03,3.06 -0.08,6.16a1.32,1.32 0 0 1 -1.33,1.3q-20.22,-0.18 -40.18,-0.23q-3.64,-0.01 -8.13,-0.44a1.06,1.05 -87.3 0 1 -0.95,-1.05q0.02,-45.49 -0.22,-92.99c-0.03,-6.25 -1.21,-13.88 -5.05,-18.95q-5.33,-7.03 -12.32,-10.18c-10.99,-4.93 -24.52,-1.84 -33.13,6.37q-10.01,9.53 -10.07,23.76q-0.11,25.46 -0.1,48.98c0,3.52 -0.06,8.31 -1.1,11.68c-4.37,14.04 -17.31,19.5 -31.04,16.77c-8.22,-1.64 -15.07,-7.75 -17.62,-15.62q-1.45,-4.49 -1.42,-10.2q0.3,-64.69 0.1,-129.86a0.47,0.47 0 0 1 0.47,-0.47l48.46,-0.35a1.56,1.55 89.4 0 1 1.56,1.54l0.15,11.25z" fill="#fff"/>
|
||||
<path id="svg_5" d="m265.63,344.43a2.02,2.01 76.7 0 0 -1.85,-1.15l-17.03,0.24a2.25,2.22 9.3 0 0 -2.06,1.46l-2.86,7.84a2.47,2.46 -79.1 0 1 -2.38,1.62l-6.23,-0.19q-1.19,-0.04 -0.88,-1.19q1.38,-5.23 2.81,-8.7c3.41,-8.3 6.48,-16.83 10.12,-25.35q2.96,-6.93 5.21,-14.24c0.46,-1.52 1.69,-2.64 3.37,-2.63c2.02,0 4.68,-0.78 5.7,1.58q7.68,17.74 18.16,44.75q0.96,2.46 1.48,5a0.67,0.66 84.3 0 1 -0.65,0.8l-6.05,-0.02q-2.16,-0.01 -3.1,-1.96l-3.76,-7.86zm-16.73,-10.31a0.34,0.34 0 0 0 0.32,0.47l12.85,-0.36a0.34,0.34 0 0 0 0.3,-0.48l-6.84,-14.7a0.34,0.34 0 0 0 -0.62,0.02l-6.01,15.05z" fill="#fff"/>
|
||||
<rect id="svg_6" rx="2.17" height="52.28" width="9.84" y="302.19" x="345.67" fill="#fff"/>
|
||||
<path id="svg_7" d="m303.07,338.46l-0.15,14.42q-0.01,1.55 -1.56,1.52l-5.84,-0.12q-1.79,-0.04 -1.81,-1.83c-0.24,-15.33 -0.25,-30.89 -0.27,-47.22q-0.01,-2.99 2.55,-3.06q12.47,-0.33 20.15,0.8q8.61,1.25 12.86,9.17c2.95,5.49 2.53,13.5 -1.5,18.65c-5.57,7.14 -14.88,6.62 -23.24,6.51a1.17,1.17 0 0 0 -1.19,1.16zm-0.15,-24.81l0.16,12.72a1.72,1.72 0 0 0 1.74,1.7l6.07,-0.08a10.01,7.98 -0.7 0 0 9.91,-8.1l0,-0.2a10.01,7.98 -0.7 0 0 -10.11,-7.86l-6.07,0.08a1.72,1.72 0 0 0 -1.7,1.74z" fill="#fff"/>
|
||||
<rect id="svg_8" rx="3.58" height="7.26" width="79.2" y="322.99" x="107" fill="#fff"/>
|
||||
<rect id="svg_9" rx="3.81" height="7.72" width="79.1" y="322.78" x="417.27" fill="#fff"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
13
web/berry/src/assets/images/logo.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<svg viewBox="0 0 590 360" xmlns="http://www.w3.org/2000/svg" version="1.1">
|
||||
<g>
|
||||
<ellipse transform="rotate(103.6 287.6 32.87)" id="svg_1" ry="28.14" rx="28.47" cy="32.87" cx="287.60001" fill="#0d161f"/>
|
||||
<path id="svg_2" d="m232.92,128.89c3.78,27.29 -1.81,55.44 -17.71,78.09a2.62,2.62 0 0 0 -0.06,2.92c1.24,1.92 2.96,5.05 5.56,4.94q5.25,-0.22 10.79,0.11a1.26,1.26 0 0 1 1.19,1.27l-0.4,42.53a1.31,1.31 0 0 1 -1.31,1.3q-16.77,-0.09 -36.53,0.01q-2.25,0.02 -3.71,-1.56q-16.02,-17.28 -31.98,-35.32c-5.13,-5.8 -10.18,-11.16 -14.86,-17.59a1.35,1.34 -31.1 0 1 0.5,-2q12.88,-6.32 22.13,-17.12q18.18,-21.23 15.08,-48.84q-2.66,-23.7 -22.4,-40.46q-23.43,-19.9 -54.88,-13.86c-4.1,0.79 -7.83,2.5 -11.72,4.12q-11.86,4.94 -20.59,14.64c-14.25,15.81 -20.07,36.4 -15.05,57.16q4.99,20.63 22.86,35.71c10.45,8.81 23.7,13.12 37.26,14.18q1.47,0.11 3.6,2.65c11.68,13.89 24.48,27.72 35.94,41.96a0.43,0.43 0 0 1 -0.21,0.68q-22.51,7.27 -47.37,5.37q-19.4,-1.47 -39.74,-11.22q-18.27,-8.75 -30.59,-21.28q-18.66,-18.98 -28.02,-43.57q-10.8,-28.4 -4.93,-58.67c1.59,-8.17 4.03,-17 7.42,-24.61q5.08,-11.38 11.61,-20.64q25.41,-36.03 68.45,-46.13q32.42,-7.61 64.23,3.92q25.31,9.17 43.2,27.31c16.85,17.09 28.91,40.01 32.24,64z" fill="#0d161f"/>
|
||||
<path id="svg_3" d="m499.47,180.61c6.45,13.53 16.44,21.75 31.96,22q11.94,0.19 22.17,-5.36q2.21,-1.2 3.93,0.69q12.56,13.78 24.89,28.47q1.21,1.44 1.44,3.13a0.95,0.95 0 0 1 -0.36,0.89c-1.62,1.23 -3.33,2.71 -5.03,3.69q-29.37,17.01 -62.47,11.31c-20.61,-3.55 -39.05,-15.24 -51.47,-32.51q-6.4,-8.89 -9.91,-17.08c-2.62,-6.12 -4.73,-13.3 -5.41,-20.08q-3.96,-39.88 22.94,-67.74c9.48,-9.81 21.15,-16.67 34.39,-19.49c16.54,-3.53 34.64,-1.83 48.77,7.1q13.92,8.79 21.13,20.4q11.07,17.84 10.48,38.92c-0.02,0.94 -0.21,1.81 -0.85,2.54q-7.73,8.77 -18.71,20.16c-1.28,1.32 -2.61,2.26 -4.51,2.23q-24.45,-0.37 -51.64,-0.41q-5.03,0 -10.84,-0.22a0.96,0.95 -11.7 0 0 -0.9,1.36zm1.12,-37.17q-0.55,1.19 -0.63,2.34q-0.08,1.01 0.94,1.03q19.01,0.25 36.98,0.01q0.5,0 0.94,-0.22q0.57,-0.28 0.44,-0.9q-2.34,-11.6 -14.11,-15.25q-3.59,-1.11 -6.44,-0.57q-13.07,2.5 -18.12,13.56z" fill="#0d161f"/>
|
||||
<path id="svg_4" d="m312.3,100.22a0.5,0.49 -22.1 0 0 0.84,0.35q2.76,-2.64 5.82,-4.31q8.45,-4.62 16.71,-6.57c15.81,-3.72 33.58,-3.2 48.2,3.95q24.49,11.98 35.05,35.76c4.66,10.5 5.44,22.96 5.5,35.35q0.21,49.99 -0.12,88q-0.03,3.06 -0.08,6.16a1.32,1.32 0 0 1 -1.33,1.3q-20.22,-0.18 -40.18,-0.23q-3.64,-0.01 -8.13,-0.44a1.06,1.05 -87.3 0 1 -0.95,-1.05q0.02,-45.49 -0.22,-92.99c-0.03,-6.25 -1.21,-13.88 -5.05,-18.95q-5.33,-7.03 -12.32,-10.18c-10.99,-4.93 -24.52,-1.84 -33.13,6.37q-10.01,9.53 -10.07,23.76q-0.11,25.46 -0.1,48.98c0,3.52 -0.06,8.31 -1.1,11.68c-4.37,14.04 -17.31,19.5 -31.04,16.77c-8.22,-1.64 -15.07,-7.75 -17.62,-15.62q-1.45,-4.49 -1.42,-10.2q0.3,-64.69 0.1,-129.86a0.47,0.47 0 0 1 0.47,-0.47l48.46,-0.35a1.56,1.55 89.4 0 1 1.56,1.54l0.15,11.25z" fill="#0d161f"/>
|
||||
<path id="svg_5" d="m265.63,344.43a2.02,2.01 76.7 0 0 -1.85,-1.15l-17.03,0.24a2.25,2.22 9.3 0 0 -2.06,1.46l-2.86,7.84a2.47,2.46 -79.1 0 1 -2.38,1.62l-6.23,-0.19q-1.19,-0.04 -0.88,-1.19q1.38,-5.23 2.81,-8.7c3.41,-8.3 6.48,-16.83 10.12,-25.35q2.96,-6.93 5.21,-14.24c0.46,-1.52 1.69,-2.64 3.37,-2.63c2.02,0 4.68,-0.78 5.7,1.58q7.68,17.74 18.16,44.75q0.96,2.46 1.48,5a0.67,0.66 84.3 0 1 -0.65,0.8l-6.05,-0.02q-2.16,-0.01 -3.1,-1.96l-3.76,-7.86zm-16.73,-10.31a0.34,0.34 0 0 0 0.32,0.47l12.85,-0.36a0.34,0.34 0 0 0 0.3,-0.48l-6.84,-14.7a0.34,0.34 0 0 0 -0.62,0.02l-6.01,15.05z" fill="#0d161f"/>
|
||||
<rect id="svg_6" rx="2.17" height="52.28" width="9.84" y="302.19" x="345.67" fill="#0d161f"/>
|
||||
<path id="svg_7" d="m303.07,338.46l-0.15,14.42q-0.01,1.55 -1.56,1.52l-5.84,-0.12q-1.79,-0.04 -1.81,-1.83c-0.24,-15.33 -0.25,-30.89 -0.27,-47.22q-0.01,-2.99 2.55,-3.06q12.47,-0.33 20.15,0.8q8.61,1.25 12.86,9.17c2.95,5.49 2.53,13.5 -1.5,18.65c-5.57,7.14 -14.88,6.62 -23.24,6.51a1.17,1.17 0 0 0 -1.19,1.16zm-0.15,-24.81l0.16,12.72a1.72,1.72 0 0 0 1.74,1.7l6.07,-0.08a10.01,7.98 -0.7 0 0 9.91,-8.1l0,-0.2a10.01,7.98 -0.7 0 0 -10.11,-7.86l-6.07,0.08a1.72,1.72 0 0 0 -1.7,1.74z" fill="#0d161f"/>
|
||||
<rect id="svg_8" rx="3.58" height="7.26" width="79.2" y="322.99" x="107" fill="#0d161f"/>
|
||||
<rect id="svg_9" rx="3.81" height="7.72" width="79.1" y="322.78" x="417.27" fill="#0d161f"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
1
web/berry/src/assets/images/users/user-round.svg
Normal file
|
After Width: | Height: | Size: 20 KiB |
167
web/berry/src/assets/scss/_themes-vars.module.scss
Normal file
@@ -0,0 +1,167 @@
|
||||
// paper & background
|
||||
$paper: #ffffff;
|
||||
|
||||
// primary
|
||||
$primaryLight: #eef2f6;
|
||||
$primaryMain: #2196f3;
|
||||
$primaryDark: #1e88e5;
|
||||
$primary200: #90caf9;
|
||||
$primary800: #1565c0;
|
||||
|
||||
// secondary
|
||||
$secondaryLight: #ede7f6;
|
||||
$secondaryMain: #673ab7;
|
||||
$secondaryDark: #5e35b1;
|
||||
$secondary200: #b39ddb;
|
||||
$secondary800: #4527a0;
|
||||
|
||||
// success Colors
|
||||
$successLight: #b9f6ca;
|
||||
$success200: #69f0ae;
|
||||
$successMain: #00e676;
|
||||
$successDark: #00c853;
|
||||
|
||||
// error
|
||||
$errorLight: #ef9a9a;
|
||||
$errorMain: #f44336;
|
||||
$errorDark: #c62828;
|
||||
|
||||
// orange
|
||||
$orangeLight: #fbe9e7;
|
||||
$orangeMain: #ffab91;
|
||||
$orangeDark: #d84315;
|
||||
|
||||
// warning
|
||||
$warningLight: #fff8e1;
|
||||
$warningMain: #ffe57f;
|
||||
$warningDark: #ffc107;
|
||||
|
||||
// grey
|
||||
$grey50: #f8fafc;
|
||||
$grey100: #eef2f6;
|
||||
$grey200: #e3e8ef;
|
||||
$grey300: #cdd5df;
|
||||
$grey500: #697586;
|
||||
$grey600: #4b5565;
|
||||
$grey700: #364152;
|
||||
$grey900: #121926;
|
||||
|
||||
$tableBackground: #f4f6f8;
|
||||
$tableBorderBottom: #f1f3f4;
|
||||
|
||||
// ==============================|| DARK THEME VARIANTS ||============================== //
|
||||
|
||||
// paper & background
|
||||
$darkBackground: #1a223f; // level 3
|
||||
$darkPaper: #111936; // level 4
|
||||
$darkDivider: rgba(227, 232, 239, 0.2);
|
||||
$darkSelectedBack : rgba(124, 77, 255, 0.15);
|
||||
|
||||
// dark 800 & 900
|
||||
$darkLevel1: #29314f; // level 1
|
||||
$darkLevel2: #212946; // level 2
|
||||
|
||||
// primary dark
|
||||
$darkPrimaryLight: #eef2f6;
|
||||
$darkPrimaryMain: #2196f3;
|
||||
$darkPrimaryDark: #1e88e5;
|
||||
$darkPrimary200: #90caf9;
|
||||
$darkPrimary800: #1565c0;
|
||||
|
||||
// secondary dark
|
||||
$darkSecondaryLight: #d1c4e9;
|
||||
$darkSecondaryMain: #7c4dff;
|
||||
$darkSecondaryDark: #651fff;
|
||||
$darkSecondary200: #b39ddb;
|
||||
$darkSecondary800: #6200ea;
|
||||
|
||||
// text variants
|
||||
$darkTextTitle: #d7dcec;
|
||||
$darkTextPrimary: #bdc8f0;
|
||||
$darkTextSecondary: #8492c4;
|
||||
|
||||
// ==============================|| JAVASCRIPT ||============================== //
|
||||
|
||||
:export {
|
||||
// paper & background
|
||||
paper: $paper;
|
||||
|
||||
// primary
|
||||
primaryLight: $primaryLight;
|
||||
primary200: $primary200;
|
||||
primaryMain: $primaryMain;
|
||||
primaryDark: $primaryDark;
|
||||
primary800: $primary800;
|
||||
|
||||
// secondary
|
||||
secondaryLight: $secondaryLight;
|
||||
secondary200: $secondary200;
|
||||
secondaryMain: $secondaryMain;
|
||||
secondaryDark: $secondaryDark;
|
||||
secondary800: $secondary800;
|
||||
|
||||
// success
|
||||
successLight: $successLight;
|
||||
success200: $success200;
|
||||
successMain: $successMain;
|
||||
successDark: $successDark;
|
||||
|
||||
// error
|
||||
errorLight: $errorLight;
|
||||
errorMain: $errorMain;
|
||||
errorDark: $errorDark;
|
||||
|
||||
// orange
|
||||
orangeLight: $orangeLight;
|
||||
orangeMain: $orangeMain;
|
||||
orangeDark: $orangeDark;
|
||||
|
||||
// warning
|
||||
warningLight: $warningLight;
|
||||
warningMain: $warningMain;
|
||||
warningDark: $warningDark;
|
||||
|
||||
// grey
|
||||
grey50: $grey50;
|
||||
grey100: $grey100;
|
||||
grey200: $grey200;
|
||||
grey300: $grey300;
|
||||
grey500: $grey500;
|
||||
grey600: $grey600;
|
||||
grey700: $grey700;
|
||||
grey900: $grey900;
|
||||
|
||||
// ==============================|| DARK THEME VARIANTS ||============================== //
|
||||
|
||||
// paper & background
|
||||
darkPaper: $darkPaper;
|
||||
darkBackground: $darkBackground;
|
||||
|
||||
// dark 800 & 900
|
||||
darkLevel1: $darkLevel1;
|
||||
darkLevel2: $darkLevel2;
|
||||
|
||||
// text variants
|
||||
darkTextTitle: $darkTextTitle;
|
||||
darkTextPrimary: $darkTextPrimary;
|
||||
darkTextSecondary: $darkTextSecondary;
|
||||
|
||||
// primary dark
|
||||
darkPrimaryLight: $darkPrimaryLight;
|
||||
darkPrimaryMain: $darkPrimaryMain;
|
||||
darkPrimaryDark: $darkPrimaryDark;
|
||||
darkPrimary200: $darkPrimary200;
|
||||
darkPrimary800: $darkPrimary800;
|
||||
|
||||
// secondary dark
|
||||
darkSecondaryLight: $darkSecondaryLight;
|
||||
darkSecondaryMain: $darkSecondaryMain;
|
||||
darkSecondaryDark: $darkSecondaryDark;
|
||||
darkSecondary200: $darkSecondary200;
|
||||
darkSecondary800: $darkSecondary800;
|
||||
|
||||
darkDivider: $darkDivider;
|
||||
darkSelectedBack: $darkSelectedBack;
|
||||
tableBackground: $tableBackground;
|
||||
tableBorderBottom: $tableBorderBottom;
|
||||
}
|
||||
32
web/berry/src/assets/scss/fonts.scss
Normal file
@@ -0,0 +1,32 @@
|
||||
|
||||
/* roboto-regular */
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: local('Roboto'), url('../fonts/roboto-regular.woff2') format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
/* roboto-500 */
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: local('Roboto'), url('../fonts/roboto-500.woff2') format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
|
||||
/* roboto-700 */
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: local('Roboto'), url('../fonts/roboto-700.woff2') format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
129
web/berry/src/assets/scss/style.scss
Normal file
@@ -0,0 +1,129 @@
|
||||
@import 'fonts.scss';
|
||||
// color variants
|
||||
@import 'themes-vars.module.scss';
|
||||
|
||||
// third-party
|
||||
@import '~react-perfect-scrollbar/dist/css/styles.css';
|
||||
|
||||
// ==============================|| LIGHT BOX ||============================== //
|
||||
.fullscreen .react-images__blanket {
|
||||
z-index: 1200;
|
||||
}
|
||||
|
||||
// ==============================|| APEXCHART ||============================== //
|
||||
|
||||
.apexcharts-legend-series .apexcharts-legend-marker {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
// ==============================|| PERFECT SCROLLBAR ||============================== //
|
||||
|
||||
.scrollbar-container {
|
||||
.ps__rail-y {
|
||||
&:hover > .ps__thumb-y,
|
||||
&:focus > .ps__thumb-y,
|
||||
&.ps--clicking .ps__thumb-y {
|
||||
background-color: $grey500;
|
||||
width: 5px;
|
||||
}
|
||||
}
|
||||
.ps__thumb-y {
|
||||
background-color: $grey500;
|
||||
border-radius: 6px;
|
||||
width: 5px;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.scrollbar-container.ps,
|
||||
.scrollbar-container > .ps {
|
||||
&.ps--active-y > .ps__rail-y {
|
||||
width: 5px;
|
||||
background-color: transparent !important;
|
||||
z-index: 999;
|
||||
&:hover,
|
||||
&.ps--clicking {
|
||||
width: 5px;
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
&.ps--scrolling-y > .ps__rail-y,
|
||||
&.ps--scrolling-x > .ps__rail-x {
|
||||
opacity: 0.4;
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
// ==============================|| ANIMATION KEYFRAMES ||============================== //
|
||||
|
||||
@keyframes wings {
|
||||
50% {
|
||||
transform: translateY(-40px);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
50% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
0%,
|
||||
20%,
|
||||
53%,
|
||||
to {
|
||||
animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
|
||||
transform: translateZ(0);
|
||||
}
|
||||
40%,
|
||||
43% {
|
||||
animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06);
|
||||
transform: translate3d(0, -5px, 0);
|
||||
}
|
||||
70% {
|
||||
animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06);
|
||||
transform: translate3d(0, -7px, 0);
|
||||
}
|
||||
80% {
|
||||
transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
|
||||
transform: translateZ(0);
|
||||
}
|
||||
90% {
|
||||
transform: translate3d(0, -2px, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideY {
|
||||
0%,
|
||||
50%,
|
||||
100% {
|
||||
transform: translateY(0px);
|
||||
}
|
||||
25% {
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
75% {
|
||||
transform: translateY(10px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideX {
|
||||
0%,
|
||||
50%,
|
||||
100% {
|
||||
transform: translateX(0px);
|
||||
}
|
||||
25% {
|
||||
transform: translateX(-10px);
|
||||
}
|
||||
75% {
|
||||
transform: translateX(10px);
|
||||
}
|
||||
}
|
||||
34
web/berry/src/config.js
Normal file
@@ -0,0 +1,34 @@
|
||||
const config = {
|
||||
// basename: only at build time to set, and Don't add '/' at end off BASENAME for breadcrumbs, also Don't put only '/' use blank('') instead,
|
||||
// like '/berry-material-react/react/default'
|
||||
basename: '/',
|
||||
defaultPath: '/panel/dashboard',
|
||||
fontFamily: `'Roboto', sans-serif, Helvetica, Arial, sans-serif`,
|
||||
borderRadius: 12,
|
||||
siteInfo: {
|
||||
chat_link: '',
|
||||
display_in_currency: true,
|
||||
email_verification: false,
|
||||
footer_html: '',
|
||||
github_client_id: '',
|
||||
github_oauth: false,
|
||||
logo: '',
|
||||
quota_per_unit: 500000,
|
||||
server_address: '',
|
||||
start_time: 0,
|
||||
system_name: 'One API',
|
||||
top_up_link: '',
|
||||
turnstile_check: false,
|
||||
turnstile_site_key: '',
|
||||
version: '',
|
||||
wechat_login: false,
|
||||
wechat_qrcode: '',
|
||||
oidc: false,
|
||||
oidc_client_id: '',
|
||||
oidc_authorization_endpoint: '',
|
||||
oidc_token_endpoint: '',
|
||||
oidc_userinfo_endpoint: '',
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
||||
278
web/berry/src/constants/ChannelConstants.js
Normal file
@@ -0,0 +1,278 @@
|
||||
export const CHANNEL_OPTIONS = {
|
||||
1: {
|
||||
key: 1,
|
||||
text: 'OpenAI',
|
||||
value: 1,
|
||||
color: 'success'
|
||||
},
|
||||
14: {
|
||||
key: 14,
|
||||
text: 'Anthropic Claude',
|
||||
value: 14,
|
||||
color: 'primary'
|
||||
},
|
||||
33: {
|
||||
key: 33,
|
||||
text: 'AWS',
|
||||
value: 33,
|
||||
color: 'primary'
|
||||
},
|
||||
37: {
|
||||
key: 37,
|
||||
text: 'Cloudflare',
|
||||
value: 37,
|
||||
color: 'success'
|
||||
},
|
||||
3: {
|
||||
key: 3,
|
||||
text: 'Azure OpenAI',
|
||||
value: 3,
|
||||
color: 'success'
|
||||
},
|
||||
11: {
|
||||
key: 11,
|
||||
text: 'Google PaLM2',
|
||||
value: 11,
|
||||
color: 'warning'
|
||||
},
|
||||
24: {
|
||||
key: 24,
|
||||
text: 'Google Gemini',
|
||||
value: 24,
|
||||
color: 'warning'
|
||||
},
|
||||
28: {
|
||||
key: 28,
|
||||
text: 'Mistral AI',
|
||||
value: 28,
|
||||
color: 'warning'
|
||||
},
|
||||
40: {
|
||||
key: 40,
|
||||
text: '字节火山引擎',
|
||||
value: 40,
|
||||
color: 'primary'
|
||||
},
|
||||
15: {
|
||||
key: 15,
|
||||
text: '百度文心千帆',
|
||||
value: 15,
|
||||
color: 'primary'
|
||||
},
|
||||
17: {
|
||||
key: 17,
|
||||
text: '阿里通义千问',
|
||||
value: 17,
|
||||
color: 'primary'
|
||||
},
|
||||
18: {
|
||||
key: 18,
|
||||
text: '讯飞星火认知',
|
||||
value: 18,
|
||||
color: 'primary'
|
||||
},
|
||||
16: {
|
||||
key: 16,
|
||||
text: '智谱 ChatGLM',
|
||||
value: 16,
|
||||
color: 'primary'
|
||||
},
|
||||
19: {
|
||||
key: 19,
|
||||
text: '360 智脑',
|
||||
value: 19,
|
||||
color: 'primary'
|
||||
},
|
||||
25: {
|
||||
key: 25,
|
||||
text: 'Moonshot AI',
|
||||
value: 25,
|
||||
color: 'primary'
|
||||
},
|
||||
23: {
|
||||
key: 23,
|
||||
text: '腾讯混元',
|
||||
value: 23,
|
||||
color: 'primary'
|
||||
},
|
||||
26: {
|
||||
key: 26,
|
||||
text: '百川大模型',
|
||||
value: 26,
|
||||
color: 'primary'
|
||||
},
|
||||
27: {
|
||||
key: 27,
|
||||
text: 'MiniMax',
|
||||
value: 27,
|
||||
color: 'primary'
|
||||
},
|
||||
29: {
|
||||
key: 29,
|
||||
text: 'Groq',
|
||||
value: 29,
|
||||
color: 'primary'
|
||||
},
|
||||
30: {
|
||||
key: 30,
|
||||
text: 'Ollama',
|
||||
value: 30,
|
||||
color: 'primary'
|
||||
},
|
||||
31: {
|
||||
key: 31,
|
||||
text: '零一万物',
|
||||
value: 31,
|
||||
color: 'primary'
|
||||
},
|
||||
32: {
|
||||
key: 32,
|
||||
text: '阶跃星辰',
|
||||
value: 32,
|
||||
color: 'primary'
|
||||
},
|
||||
34: {
|
||||
key: 34,
|
||||
text: 'Coze',
|
||||
value: 34,
|
||||
color: 'primary'
|
||||
},
|
||||
35: {
|
||||
key: 35,
|
||||
text: 'Cohere',
|
||||
value: 35,
|
||||
color: 'primary'
|
||||
},
|
||||
36: {
|
||||
key: 36,
|
||||
text: 'DeepSeek',
|
||||
value: 36,
|
||||
color: 'primary'
|
||||
},
|
||||
38: {
|
||||
key: 38,
|
||||
text: 'DeepL',
|
||||
value: 38,
|
||||
color: 'primary'
|
||||
},
|
||||
39: {
|
||||
key: 39,
|
||||
text: 'together.ai',
|
||||
value: 39,
|
||||
color: 'primary'
|
||||
},
|
||||
42: {
|
||||
key: 42,
|
||||
text: 'VertexAI',
|
||||
value: 42,
|
||||
color: 'primary'
|
||||
},
|
||||
43: {
|
||||
key: 43,
|
||||
text: 'Proxy',
|
||||
value: 43,
|
||||
color: 'primary'
|
||||
},
|
||||
44: {
|
||||
key: 44,
|
||||
text: 'SiliconFlow',
|
||||
value: 44,
|
||||
color: 'primary'
|
||||
},
|
||||
45: {
|
||||
key: 45,
|
||||
text: 'xAI',
|
||||
value: 45,
|
||||
color: 'primary'
|
||||
},
|
||||
45: {
|
||||
key: 46,
|
||||
text: 'Replicate',
|
||||
value: 46,
|
||||
color: 'primary'
|
||||
},
|
||||
41: {
|
||||
key: 41,
|
||||
text: 'Novita',
|
||||
value: 41,
|
||||
color: 'purple'
|
||||
},
|
||||
8: {
|
||||
key: 8,
|
||||
text: '自定义渠道',
|
||||
value: 8,
|
||||
color: 'error'
|
||||
},
|
||||
22: {
|
||||
key: 22,
|
||||
text: '知识库:FastGPT',
|
||||
value: 22,
|
||||
color: 'success'
|
||||
},
|
||||
21: {
|
||||
key: 21,
|
||||
text: '知识库:AI Proxy',
|
||||
value: 21,
|
||||
color: 'success'
|
||||
},
|
||||
20: {
|
||||
key: 20,
|
||||
text: 'OpenRouter',
|
||||
value: 20,
|
||||
color: 'success'
|
||||
},
|
||||
2: {
|
||||
key: 2,
|
||||
text: '代理:API2D',
|
||||
value: 2,
|
||||
color: 'success'
|
||||
},
|
||||
5: {
|
||||
key: 5,
|
||||
text: '代理:OpenAI-SB',
|
||||
value: 5,
|
||||
color: 'success'
|
||||
},
|
||||
7: {
|
||||
key: 7,
|
||||
text: '代理:OhMyGPT',
|
||||
value: 7,
|
||||
color: 'success'
|
||||
},
|
||||
10: {
|
||||
key: 10,
|
||||
text: '代理:AI Proxy',
|
||||
value: 10,
|
||||
color: 'success'
|
||||
},
|
||||
4: {
|
||||
key: 4,
|
||||
text: '代理:CloseAI',
|
||||
value: 4,
|
||||
color: 'success'
|
||||
},
|
||||
6: {
|
||||
key: 6,
|
||||
text: '代理:OpenAI Max',
|
||||
value: 6,
|
||||
color: 'success'
|
||||
},
|
||||
9: {
|
||||
key: 9,
|
||||
text: '代理:AI.LS',
|
||||
value: 9,
|
||||
color: 'success'
|
||||
},
|
||||
12: {
|
||||
key: 12,
|
||||
text: '代理:API2GPT',
|
||||
value: 12,
|
||||
color: 'success'
|
||||
},
|
||||
13: {
|
||||
key: 13,
|
||||
text: '代理:AIGC2D',
|
||||
value: 13,
|
||||
color: 'success'
|
||||
}
|
||||
};
|
||||
1
web/berry/src/constants/CommonConstants.js
Normal file
@@ -0,0 +1 @@
|
||||
export const ITEMS_PER_PAGE = 10; // this value must keep same as the one defined in backend!
|
||||
59
web/berry/src/constants/SnackbarConstants.js
Normal file
@@ -0,0 +1,59 @@
|
||||
import { closeSnackbar } from 'notistack';
|
||||
import { IconX } from '@tabler/icons-react';
|
||||
import { IconButton } from '@mui/material';
|
||||
const action = (snackbarId) => (
|
||||
<>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
closeSnackbar(snackbarId);
|
||||
}}
|
||||
>
|
||||
<IconX stroke={1.5} size="1.25rem" />
|
||||
</IconButton>
|
||||
</>
|
||||
);
|
||||
|
||||
export const snackbarConstants = {
|
||||
Common: {
|
||||
ERROR: {
|
||||
variant: 'error',
|
||||
autoHideDuration: 5000,
|
||||
preventDuplicate: true,
|
||||
action
|
||||
},
|
||||
WARNING: {
|
||||
variant: 'warning',
|
||||
autoHideDuration: 10000,
|
||||
preventDuplicate: true,
|
||||
action
|
||||
},
|
||||
SUCCESS: {
|
||||
variant: 'success',
|
||||
autoHideDuration: 1500,
|
||||
preventDuplicate: true,
|
||||
action
|
||||
},
|
||||
INFO: {
|
||||
variant: 'info',
|
||||
autoHideDuration: 3000,
|
||||
preventDuplicate: true,
|
||||
action
|
||||
},
|
||||
NOTICE: {
|
||||
variant: 'info',
|
||||
autoHideDuration: 20000,
|
||||
preventDuplicate: true,
|
||||
action
|
||||
},
|
||||
COPY: {
|
||||
variant: 'copy',
|
||||
persist: true,
|
||||
preventDuplicate: true,
|
||||
allowDownload: true,
|
||||
action
|
||||
}
|
||||
},
|
||||
Mobile: {
|
||||
anchorOrigin: { vertical: 'bottom', horizontal: 'center' }
|
||||
}
|
||||
};
|
||||
3
web/berry/src/constants/index.js
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './SnackbarConstants';
|
||||
export * from './CommonConstants';
|
||||
export * from './ChannelConstants';
|
||||
70
web/berry/src/contexts/StatusContext.js
Normal file
@@ -0,0 +1,70 @@
|
||||
import { useEffect, useCallback, createContext } from "react";
|
||||
import { API } from "utils/api";
|
||||
import { showNotice, showError } from "utils/common";
|
||||
import { SET_SITE_INFO } from "store/actions";
|
||||
import { useDispatch } from "react-redux";
|
||||
|
||||
export const LoadStatusContext = createContext();
|
||||
|
||||
// eslint-disable-next-line
|
||||
const StatusProvider = ({ children }) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const loadStatus = useCallback(async () => {
|
||||
const res = await API.get("/api/status");
|
||||
const { success, data } = res.data;
|
||||
let system_name = "";
|
||||
if (success) {
|
||||
if (!data.chat_link) {
|
||||
delete data.chat_link;
|
||||
}
|
||||
localStorage.setItem("siteInfo", JSON.stringify(data));
|
||||
localStorage.setItem("quota_per_unit", data.quota_per_unit);
|
||||
localStorage.setItem("display_in_currency", data.display_in_currency);
|
||||
dispatch({ type: SET_SITE_INFO, payload: data });
|
||||
if (
|
||||
data.version !== process.env.REACT_APP_VERSION &&
|
||||
data.version !== "v0.0.0" &&
|
||||
data.version !== "" &&
|
||||
process.env.REACT_APP_VERSION !== ""
|
||||
) {
|
||||
showNotice(
|
||||
`新版本可用:${data.version},请使用快捷键 Shift + F5 刷新页面`
|
||||
);
|
||||
}
|
||||
if (data.system_name) {
|
||||
system_name = data.system_name;
|
||||
}
|
||||
} else {
|
||||
const backupSiteInfo = localStorage.getItem("siteInfo");
|
||||
if (backupSiteInfo) {
|
||||
const data = JSON.parse(backupSiteInfo);
|
||||
if (data.system_name) {
|
||||
system_name = data.system_name;
|
||||
}
|
||||
dispatch({
|
||||
type: SET_SITE_INFO,
|
||||
payload: data,
|
||||
});
|
||||
}
|
||||
showError("无法正常连接至服务器!");
|
||||
}
|
||||
|
||||
if (system_name) {
|
||||
document.title = system_name;
|
||||
}
|
||||
}, [dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
loadStatus().then();
|
||||
}, [loadStatus]);
|
||||
|
||||
return (
|
||||
<LoadStatusContext.Provider value={loadStatus}>
|
||||
{" "}
|
||||
{children}{" "}
|
||||
</LoadStatusContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default StatusProvider;
|
||||
29
web/berry/src/contexts/UserContext.js
Normal file
@@ -0,0 +1,29 @@
|
||||
// contexts/User/index.jsx
|
||||
import React, { useEffect, useCallback, createContext, useState } from 'react';
|
||||
import { LOGIN } from 'store/actions';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
export const UserContext = createContext();
|
||||
|
||||
// eslint-disable-next-line
|
||||
const UserProvider = ({ children }) => {
|
||||
const dispatch = useDispatch();
|
||||
const [isUserLoaded, setIsUserLoaded] = useState(false);
|
||||
|
||||
const loadUser = useCallback(() => {
|
||||
let user = localStorage.getItem('user');
|
||||
if (user) {
|
||||
let data = JSON.parse(user);
|
||||
dispatch({ type: LOGIN, payload: data });
|
||||
}
|
||||
setIsUserLoaded(true);
|
||||
}, [dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
loadUser();
|
||||
}, [loadUser]);
|
||||
|
||||
return <UserContext.Provider value={{ loadUser, isUserLoaded }}> {children} </UserContext.Provider>;
|
||||
};
|
||||
|
||||
export default UserProvider;
|
||||
13
web/berry/src/hooks/useAuth.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import { isAdmin } from 'utils/common';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
const navigate = useNavigate();
|
||||
|
||||
const useAuth = () => {
|
||||
const userIsAdmin = isAdmin();
|
||||
|
||||
if (!userIsAdmin) {
|
||||
navigate('/panel/404');
|
||||
}
|
||||
};
|
||||
|
||||
export default useAuth;
|
||||
122
web/berry/src/hooks/useLogin.js
Normal file
@@ -0,0 +1,122 @@
|
||||
import { API } from 'utils/api';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { LOGIN } from 'store/actions';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { showSuccess } from 'utils/common';
|
||||
|
||||
const useLogin = () => {
|
||||
const dispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
const login = async (username, password) => {
|
||||
try {
|
||||
const res = await API.post(`/api/user/login`, {
|
||||
username,
|
||||
password
|
||||
});
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
localStorage.setItem('user', JSON.stringify(data));
|
||||
dispatch({ type: LOGIN, payload: data });
|
||||
navigate('/panel');
|
||||
}
|
||||
return { success, message };
|
||||
} catch (err) {
|
||||
// 请求失败,设置错误信息
|
||||
return { success: false, message: '' };
|
||||
}
|
||||
};
|
||||
|
||||
const githubLogin = async (code, state) => {
|
||||
try {
|
||||
const res = await API.get(`/api/oauth/github?code=${code}&state=${state}`);
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
if (message === 'bind') {
|
||||
showSuccess('绑定成功!');
|
||||
navigate('/panel');
|
||||
} else {
|
||||
dispatch({ type: LOGIN, payload: data });
|
||||
localStorage.setItem('user', JSON.stringify(data));
|
||||
showSuccess('登录成功!');
|
||||
navigate('/panel');
|
||||
}
|
||||
}
|
||||
return { success, message };
|
||||
} catch (err) {
|
||||
// 请求失败,设置错误信息
|
||||
return { success: false, message: '' };
|
||||
}
|
||||
};
|
||||
|
||||
const larkLogin = async (code, state) => {
|
||||
try {
|
||||
const res = await API.get(`/api/oauth/lark?code=${code}&state=${state}`);
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
if (message === 'bind') {
|
||||
showSuccess('绑定成功!');
|
||||
navigate('/panel');
|
||||
} else {
|
||||
dispatch({ type: LOGIN, payload: data });
|
||||
localStorage.setItem('user', JSON.stringify(data));
|
||||
showSuccess('登录成功!');
|
||||
navigate('/panel');
|
||||
}
|
||||
}
|
||||
return { success, message };
|
||||
} catch (err) {
|
||||
// 请求失败,设置错误信息
|
||||
return { success: false, message: '' };
|
||||
}
|
||||
};
|
||||
|
||||
const oidcLogin = async (code, state) => {
|
||||
try {
|
||||
const res = await API.get(`/api/oauth/oidc?code=${code}&state=${state}`);
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
if (message === 'bind') {
|
||||
showSuccess('绑定成功!');
|
||||
navigate('/panel');
|
||||
} else {
|
||||
dispatch({ type: LOGIN, payload: data });
|
||||
localStorage.setItem('user', JSON.stringify(data));
|
||||
showSuccess('登录成功!');
|
||||
navigate('/panel');
|
||||
}
|
||||
}
|
||||
return { success, message };
|
||||
} catch (err) {
|
||||
// 请求失败,设置错误信息
|
||||
return { success: false, message: '' };
|
||||
}
|
||||
}
|
||||
|
||||
const wechatLogin = async (code) => {
|
||||
try {
|
||||
const res = await API.get(`/api/oauth/wechat?code=${code}`);
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
dispatch({ type: LOGIN, payload: data });
|
||||
localStorage.setItem('user', JSON.stringify(data));
|
||||
showSuccess('登录成功!');
|
||||
navigate('/panel');
|
||||
}
|
||||
return { success, message };
|
||||
} catch (err) {
|
||||
// 请求失败,设置错误信息
|
||||
return { success: false, message: '' };
|
||||
}
|
||||
};
|
||||
|
||||
const logout = async () => {
|
||||
await API.get('/api/user/logout');
|
||||
localStorage.removeItem('user');
|
||||
dispatch({ type: LOGIN, payload: null });
|
||||
navigate('/');
|
||||
};
|
||||
|
||||
return { login, logout, githubLogin, wechatLogin, larkLogin,oidcLogin };
|
||||
};
|
||||
|
||||
export default useLogin;
|
||||
43
web/berry/src/hooks/useRegister.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import { API } from 'utils/api';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { showSuccess } from 'utils/common';
|
||||
|
||||
const useRegister = () => {
|
||||
const navigate = useNavigate();
|
||||
const register = async (input, turnstile) => {
|
||||
try {
|
||||
let affCode = localStorage.getItem('aff');
|
||||
if (affCode) {
|
||||
input = { ...input, aff_code: affCode };
|
||||
}
|
||||
const res = await API.post(`/api/user/register?turnstile=${turnstile}`, input);
|
||||
const { success, message } = res.data;
|
||||
if (success) {
|
||||
showSuccess('注册成功!');
|
||||
navigate('/login');
|
||||
}
|
||||
return { success, message };
|
||||
} catch (err) {
|
||||
// 请求失败,设置错误信息
|
||||
return { success: false, message: '' };
|
||||
}
|
||||
};
|
||||
|
||||
const sendVerificationCode = async (email, turnstile) => {
|
||||
try {
|
||||
const res = await API.get(`/api/verification?email=${email}&turnstile=${turnstile}`);
|
||||
const { success, message } = res.data;
|
||||
if (success) {
|
||||
showSuccess('验证码发送成功,请检查你的邮箱!');
|
||||
}
|
||||
return { success, message };
|
||||
} catch (err) {
|
||||
// 请求失败,设置错误信息
|
||||
return { success: false, message: '' };
|
||||
}
|
||||
};
|
||||
|
||||
return { register, sendVerificationCode };
|
||||
};
|
||||
|
||||
export default useRegister;
|
||||
18
web/berry/src/hooks/useScriptRef.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
// ==============================|| ELEMENT REFERENCE HOOKS ||============================== //
|
||||
|
||||
const useScriptRef = () => {
|
||||
const scripted = useRef(true);
|
||||
|
||||
useEffect(
|
||||
() => () => {
|
||||
scripted.current = true;
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return scripted;
|
||||
};
|
||||
|
||||
export default useScriptRef;
|
||||
31
web/berry/src/index.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
// third party
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
// project imports
|
||||
import * as serviceWorker from 'serviceWorker';
|
||||
import App from 'App';
|
||||
import { store } from 'store';
|
||||
|
||||
// style + assets
|
||||
import 'assets/scss/style.scss';
|
||||
import config from './config';
|
||||
|
||||
// ==============================|| REACT DOM RENDER ||============================== //
|
||||
|
||||
const container = document.getElementById('root');
|
||||
const root = createRoot(container); // createRoot(container!) if you use TypeScript
|
||||
root.render(
|
||||
<Provider store={store}>
|
||||
<BrowserRouter basename={config.basename}>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
</Provider>
|
||||
);
|
||||
|
||||
// If you want your app to work offline and load faster, you can change
|
||||
// unregister() to register() below. Note this comes with some pitfalls.
|
||||
// Learn more about service workers: https://bit.ly/CRA-PWA
|
||||
serviceWorker.register();
|
||||
173
web/berry/src/layout/MainLayout/Header/ProfileSection/index.js
Normal file
@@ -0,0 +1,173 @@
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
// material-ui
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import {
|
||||
Avatar,
|
||||
Chip,
|
||||
ClickAwayListener,
|
||||
List,
|
||||
ListItemButton,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
Paper,
|
||||
Popper,
|
||||
Typography
|
||||
} from '@mui/material';
|
||||
|
||||
// project imports
|
||||
import MainCard from 'ui-component/cards/MainCard';
|
||||
import Transitions from 'ui-component/extended/Transitions';
|
||||
import User1 from 'assets/images/users/user-round.svg';
|
||||
import useLogin from 'hooks/useLogin';
|
||||
|
||||
// assets
|
||||
import { IconLogout, IconSettings, IconUserScan } from '@tabler/icons-react';
|
||||
|
||||
// ==============================|| PROFILE MENU ||============================== //
|
||||
|
||||
const ProfileSection = () => {
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const customization = useSelector((state) => state.customization);
|
||||
const { logout } = useLogin();
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
/**
|
||||
* anchorRef is used on different componets and specifying one type leads to other components throwing an error
|
||||
* */
|
||||
const anchorRef = useRef(null);
|
||||
const handleLogout = async () => {
|
||||
logout();
|
||||
};
|
||||
|
||||
const handleClose = (event) => {
|
||||
if (anchorRef.current && anchorRef.current.contains(event.target)) {
|
||||
return;
|
||||
}
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const handleToggle = () => {
|
||||
setOpen((prevOpen) => !prevOpen);
|
||||
};
|
||||
|
||||
const prevOpen = useRef(open);
|
||||
useEffect(() => {
|
||||
if (prevOpen.current === true && open === false) {
|
||||
anchorRef.current.focus();
|
||||
}
|
||||
|
||||
prevOpen.current = open;
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Chip
|
||||
sx={{
|
||||
height: '48px',
|
||||
alignItems: 'center',
|
||||
borderRadius: '27px',
|
||||
transition: 'all .2s ease-in-out',
|
||||
borderColor: theme.typography.menuChip.background,
|
||||
backgroundColor: theme.typography.menuChip.background,
|
||||
'&[aria-controls="menu-list-grow"], &:hover': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
background: `${theme.palette.primary.main}!important`,
|
||||
color: theme.palette.primary.light,
|
||||
'& svg': {
|
||||
stroke: theme.palette.primary.light
|
||||
}
|
||||
},
|
||||
'& .MuiChip-label': {
|
||||
lineHeight: 0
|
||||
}
|
||||
}}
|
||||
icon={
|
||||
<Avatar
|
||||
src={User1}
|
||||
sx={{
|
||||
...theme.typography.mediumAvatar,
|
||||
margin: '8px 0 8px 8px !important',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
ref={anchorRef}
|
||||
aria-controls={open ? 'menu-list-grow' : undefined}
|
||||
aria-haspopup="true"
|
||||
color="inherit"
|
||||
/>
|
||||
}
|
||||
label={<IconSettings stroke={1.5} size="1.5rem" color={theme.palette.primary.main} />}
|
||||
variant="outlined"
|
||||
ref={anchorRef}
|
||||
aria-controls={open ? 'menu-list-grow' : undefined}
|
||||
aria-haspopup="true"
|
||||
onClick={handleToggle}
|
||||
color="primary"
|
||||
/>
|
||||
<Popper
|
||||
placement="bottom-end"
|
||||
open={open}
|
||||
anchorEl={anchorRef.current}
|
||||
role={undefined}
|
||||
transition
|
||||
disablePortal
|
||||
popperOptions={{
|
||||
modifiers: [
|
||||
{
|
||||
name: 'offset',
|
||||
options: {
|
||||
offset: [0, 14]
|
||||
}
|
||||
}
|
||||
]
|
||||
}}
|
||||
>
|
||||
{({ TransitionProps }) => (
|
||||
<Transitions in={open} {...TransitionProps}>
|
||||
<Paper>
|
||||
<ClickAwayListener onClickAway={handleClose}>
|
||||
<MainCard border={false} elevation={16} content={false} boxShadow shadow={theme.shadows[16]}>
|
||||
<List
|
||||
component="nav"
|
||||
sx={{
|
||||
width: '100%',
|
||||
maxWidth: 350,
|
||||
minWidth: 150,
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
borderRadius: '10px',
|
||||
[theme.breakpoints.down('md')]: {
|
||||
minWidth: '100%'
|
||||
},
|
||||
'& .MuiListItemButton-root': {
|
||||
mt: 0.5
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ListItemButton sx={{ borderRadius: `${customization.borderRadius}px` }} onClick={() => navigate('/panel/profile')}>
|
||||
<ListItemIcon>
|
||||
<IconUserScan stroke={1.5} size="1.3rem" />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={<Typography variant="body2">设置</Typography>} />
|
||||
</ListItemButton>
|
||||
|
||||
<ListItemButton sx={{ borderRadius: `${customization.borderRadius}px` }} onClick={handleLogout}>
|
||||
<ListItemIcon>
|
||||
<IconLogout stroke={1.5} size="1.3rem" />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={<Typography variant="body2">登出</Typography>} />
|
||||
</ListItemButton>
|
||||
</List>
|
||||
</MainCard>
|
||||
</ClickAwayListener>
|
||||
</Paper>
|
||||
</Transitions>
|
||||
)}
|
||||
</Popper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProfileSection;
|
||||
68
web/berry/src/layout/MainLayout/Header/index.js
Normal file
@@ -0,0 +1,68 @@
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
// material-ui
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { Avatar, Box, ButtonBase } from '@mui/material';
|
||||
|
||||
// project imports
|
||||
import LogoSection from '../LogoSection';
|
||||
import ProfileSection from './ProfileSection';
|
||||
import ThemeButton from 'ui-component/ThemeButton';
|
||||
|
||||
// assets
|
||||
import { IconMenu2 } from '@tabler/icons-react';
|
||||
|
||||
// ==============================|| MAIN NAVBAR / HEADER ||============================== //
|
||||
|
||||
const Header = ({ handleLeftDrawerToggle }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* logo & toggler button */}
|
||||
<Box
|
||||
sx={{
|
||||
width: 228,
|
||||
display: 'flex',
|
||||
[theme.breakpoints.down('md')]: {
|
||||
width: 'auto'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Box component="span" sx={{ display: { xs: 'none', md: 'block' }, flexGrow: 1 }}>
|
||||
<LogoSection />
|
||||
</Box>
|
||||
<ButtonBase sx={{ borderRadius: '12px', overflow: 'hidden' }}>
|
||||
<Avatar
|
||||
variant="rounded"
|
||||
sx={{
|
||||
...theme.typography.commonAvatar,
|
||||
...theme.typography.mediumAvatar,
|
||||
...theme.typography.menuButton,
|
||||
transition: 'all .2s ease-in-out',
|
||||
'&:hover': {
|
||||
background: theme.palette.secondary.dark,
|
||||
color: theme.palette.secondary.light
|
||||
}
|
||||
}}
|
||||
onClick={handleLeftDrawerToggle}
|
||||
color="inherit"
|
||||
>
|
||||
<IconMenu2 stroke={1.5} size="1.3rem" />
|
||||
</Avatar>
|
||||
</ButtonBase>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ flexGrow: 1 }} />
|
||||
<Box sx={{ flexGrow: 1 }} />
|
||||
<ThemeButton />
|
||||
<ProfileSection />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Header.propTypes = {
|
||||
handleLeftDrawerToggle: PropTypes.func
|
||||
};
|
||||
|
||||
export default Header;
|
||||
23
web/berry/src/layout/MainLayout/LogoSection/index.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
// material-ui
|
||||
import { ButtonBase } from '@mui/material';
|
||||
|
||||
// project imports
|
||||
import Logo from 'ui-component/Logo';
|
||||
import { MENU_OPEN } from 'store/actions';
|
||||
|
||||
// ==============================|| MAIN LOGO ||============================== //
|
||||
|
||||
const LogoSection = () => {
|
||||
const defaultId = useSelector((state) => state.customization.defaultId);
|
||||
const dispatch = useDispatch();
|
||||
return (
|
||||
<ButtonBase disableRipple onClick={() => dispatch({ type: MENU_OPEN, id: defaultId })} component={Link} to="/">
|
||||
<Logo />
|
||||
</ButtonBase>
|
||||
);
|
||||
};
|
||||
|
||||
export default LogoSection;
|
||||
129
web/berry/src/layout/MainLayout/Sidebar/MenuCard/index.js
Normal file
@@ -0,0 +1,129 @@
|
||||
// import PropTypes from 'prop-types';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
// material-ui
|
||||
import { styled, useTheme } from '@mui/material/styles';
|
||||
import {
|
||||
Avatar,
|
||||
Card,
|
||||
CardContent,
|
||||
// Grid,
|
||||
// LinearProgress,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemAvatar,
|
||||
ListItemText,
|
||||
Typography
|
||||
// linearProgressClasses
|
||||
} from '@mui/material';
|
||||
import User1 from 'assets/images/users/user-round.svg';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
// assets
|
||||
// import TableChartOutlinedIcon from '@mui/icons-material/TableChartOutlined';
|
||||
|
||||
// styles
|
||||
// const BorderLinearProgress = styled(LinearProgress)(({ theme }) => ({
|
||||
// height: 10,
|
||||
// borderRadius: 30,
|
||||
// [`&.${linearProgressClasses.colorPrimary}`]: {
|
||||
// backgroundColor: '#fff'
|
||||
// },
|
||||
// [`& .${linearProgressClasses.bar}`]: {
|
||||
// borderRadius: 5,
|
||||
// backgroundColor: theme.palette.primary.main
|
||||
// }
|
||||
// }));
|
||||
|
||||
const CardStyle = styled(Card)(({ theme }) => ({
|
||||
background: theme.typography.menuChip.background,
|
||||
marginBottom: '22px',
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
'&:after': {
|
||||
content: '""',
|
||||
position: 'absolute',
|
||||
width: '157px',
|
||||
height: '157px',
|
||||
background: theme.palette.primary[200],
|
||||
borderRadius: '50%',
|
||||
top: '-105px',
|
||||
right: '-96px'
|
||||
}
|
||||
}));
|
||||
|
||||
// ==============================|| PROGRESS BAR WITH LABEL ||============================== //
|
||||
|
||||
// function LinearProgressWithLabel({ value, ...others }) {
|
||||
// const theme = useTheme();
|
||||
|
||||
// return (
|
||||
// <Grid container direction="column" spacing={1} sx={{ mt: 1.5 }}>
|
||||
// <Grid item>
|
||||
// <Grid container justifyContent="space-between">
|
||||
// <Grid item>
|
||||
// <Typography variant="h6" sx={{ color: theme.palette.primary[800] }}>
|
||||
// Progress
|
||||
// </Typography>
|
||||
// </Grid>
|
||||
// <Grid item>
|
||||
// <Typography variant="h6" color="inherit">{`${Math.round(value)}%`}</Typography>
|
||||
// </Grid>
|
||||
// </Grid>
|
||||
// </Grid>
|
||||
// <Grid item>
|
||||
// <BorderLinearProgress variant="determinate" value={value} {...others} />
|
||||
// </Grid>
|
||||
// </Grid>
|
||||
// );
|
||||
// }
|
||||
|
||||
// LinearProgressWithLabel.propTypes = {
|
||||
// value: PropTypes.number
|
||||
// };
|
||||
|
||||
// ==============================|| SIDEBAR MENU Card ||============================== //
|
||||
|
||||
const MenuCard = () => {
|
||||
const theme = useTheme();
|
||||
const account = useSelector((state) => state.account);
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<CardStyle>
|
||||
<CardContent sx={{ p: 2 }}>
|
||||
<List sx={{ p: 0, m: 0 }}>
|
||||
<ListItem alignItems="flex-start" disableGutters sx={{ p: 0 }}>
|
||||
<ListItemAvatar sx={{ mt: 0 }}>
|
||||
<Avatar
|
||||
variant="rounded"
|
||||
src={User1}
|
||||
sx={{
|
||||
...theme.typography.commonAvatar,
|
||||
...theme.typography.largeAvatar,
|
||||
color: theme.palette.primary.main,
|
||||
border: 'none',
|
||||
borderColor: theme.palette.primary.main,
|
||||
background: '#fff',
|
||||
marginRight: '12px'
|
||||
}}
|
||||
onClick={() => navigate('/panel/profile')}
|
||||
></Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
sx={{ mt: 0 }}
|
||||
primary={
|
||||
<Typography variant="subtitle1" sx={{ color: theme.palette.primary[800] }}>
|
||||
{account.user?.username}
|
||||
</Typography>
|
||||
}
|
||||
secondary={<Typography variant="caption"> 欢迎回来 </Typography>}
|
||||
/>
|
||||
</ListItem>
|
||||
</List>
|
||||
</CardContent>
|
||||
</CardStyle>
|
||||
);
|
||||
};
|
||||
|
||||
export default MenuCard;
|
||||
@@ -0,0 +1,158 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useLocation, useNavigate } from 'react-router';
|
||||
|
||||
// material-ui
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { Collapse, List, ListItemButton, ListItemIcon, ListItemText, Typography } from '@mui/material';
|
||||
|
||||
// project imports
|
||||
import NavItem from '../NavItem';
|
||||
|
||||
// assets
|
||||
import FiberManualRecordIcon from '@mui/icons-material/FiberManualRecord';
|
||||
import { IconChevronDown, IconChevronUp } from '@tabler/icons-react';
|
||||
|
||||
// ==============================|| SIDEBAR MENU LIST COLLAPSE ITEMS ||============================== //
|
||||
|
||||
const NavCollapse = ({ menu, level }) => {
|
||||
const theme = useTheme();
|
||||
const customization = useSelector((state) => state.customization);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const [selected, setSelected] = useState(null);
|
||||
|
||||
const handleClick = () => {
|
||||
setOpen(!open);
|
||||
setSelected(!selected ? menu.id : null);
|
||||
if (menu?.id !== 'authentication') {
|
||||
navigate(menu.children[0]?.url);
|
||||
}
|
||||
};
|
||||
|
||||
const { pathname } = useLocation();
|
||||
const checkOpenForParent = (child, id) => {
|
||||
child.forEach((item) => {
|
||||
if (item.url === pathname) {
|
||||
setOpen(true);
|
||||
setSelected(id);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// menu collapse for sub-levels
|
||||
useEffect(() => {
|
||||
setOpen(false);
|
||||
setSelected(null);
|
||||
if (menu.children) {
|
||||
menu.children.forEach((item) => {
|
||||
if (item.children?.length) {
|
||||
checkOpenForParent(item.children, menu.id);
|
||||
}
|
||||
if (item.url === pathname) {
|
||||
setSelected(menu.id);
|
||||
setOpen(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [pathname, menu.children]);
|
||||
|
||||
// menu collapse & item
|
||||
const menus = menu.children?.map((item) => {
|
||||
switch (item.type) {
|
||||
case 'collapse':
|
||||
return <NavCollapse key={item.id} menu={item} level={level + 1} />;
|
||||
case 'item':
|
||||
return <NavItem key={item.id} item={item} level={level + 1} />;
|
||||
default:
|
||||
return (
|
||||
<Typography key={item.id} variant="h6" color="error" align="center">
|
||||
Menu Items Error
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const Icon = menu.icon;
|
||||
const menuIcon = menu.icon ? (
|
||||
<Icon strokeWidth={1.5} size="1.3rem" style={{ marginTop: 'auto', marginBottom: 'auto' }} />
|
||||
) : (
|
||||
<FiberManualRecordIcon
|
||||
sx={{
|
||||
width: selected === menu.id ? 8 : 6,
|
||||
height: selected === menu.id ? 8 : 6
|
||||
}}
|
||||
fontSize={level > 0 ? 'inherit' : 'medium'}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ListItemButton
|
||||
sx={{
|
||||
borderRadius: `${customization.borderRadius}px`,
|
||||
mb: 0.5,
|
||||
alignItems: 'flex-start',
|
||||
backgroundColor: level > 1 ? 'transparent !important' : 'inherit',
|
||||
py: level > 1 ? 1 : 1.25,
|
||||
pl: `${level * 24}px`
|
||||
}}
|
||||
selected={selected === menu.id}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<ListItemIcon sx={{ my: 'auto', minWidth: !menu.icon ? 18 : 36 }}>{menuIcon}</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={
|
||||
<Typography variant={selected === menu.id ? 'h5' : 'body1'} color="inherit" sx={{ my: 'auto' }}>
|
||||
{menu.title}
|
||||
</Typography>
|
||||
}
|
||||
secondary={
|
||||
menu.caption && (
|
||||
<Typography variant="caption" sx={{ ...theme.typography.subMenuCaption }} display="block" gutterBottom>
|
||||
{menu.caption}
|
||||
</Typography>
|
||||
)
|
||||
}
|
||||
/>
|
||||
{open ? (
|
||||
<IconChevronUp stroke={1.5} size="1rem" style={{ marginTop: 'auto', marginBottom: 'auto' }} />
|
||||
) : (
|
||||
<IconChevronDown stroke={1.5} size="1rem" style={{ marginTop: 'auto', marginBottom: 'auto' }} />
|
||||
)}
|
||||
</ListItemButton>
|
||||
<Collapse in={open} timeout="auto" unmountOnExit>
|
||||
<List
|
||||
component="div"
|
||||
disablePadding
|
||||
sx={{
|
||||
position: 'relative',
|
||||
'&:after': {
|
||||
content: "''",
|
||||
position: 'absolute',
|
||||
left: '32px',
|
||||
top: 0,
|
||||
height: '100%',
|
||||
width: '1px',
|
||||
opacity: 1,
|
||||
background: theme.palette.primary.light
|
||||
}
|
||||
}}
|
||||
>
|
||||
{menus}
|
||||
</List>
|
||||
</Collapse>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
NavCollapse.propTypes = {
|
||||
menu: PropTypes.object,
|
||||
level: PropTypes.number
|
||||
};
|
||||
|
||||
export default NavCollapse;
|
||||
@@ -0,0 +1,61 @@
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
// material-ui
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { Divider, List, Typography } from '@mui/material';
|
||||
|
||||
// project imports
|
||||
import NavItem from '../NavItem';
|
||||
import NavCollapse from '../NavCollapse';
|
||||
|
||||
// ==============================|| SIDEBAR MENU LIST GROUP ||============================== //
|
||||
|
||||
const NavGroup = ({ item }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
// menu list collapse & items
|
||||
const items = item.children?.map((menu) => {
|
||||
switch (menu.type) {
|
||||
case 'collapse':
|
||||
return <NavCollapse key={menu.id} menu={menu} level={1} />;
|
||||
case 'item':
|
||||
return <NavItem key={menu.id} item={menu} level={1} />;
|
||||
default:
|
||||
return (
|
||||
<Typography key={menu.id} variant="h6" color="error" align="center">
|
||||
Menu Items Error
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<List
|
||||
subheader={
|
||||
item.title && (
|
||||
<Typography variant="caption" sx={{ ...theme.typography.menuCaption }} display="block" gutterBottom>
|
||||
{item.title}
|
||||
{item.caption && (
|
||||
<Typography variant="caption" sx={{ ...theme.typography.subMenuCaption }} display="block" gutterBottom>
|
||||
{item.caption}
|
||||
</Typography>
|
||||
)}
|
||||
</Typography>
|
||||
)
|
||||
}
|
||||
>
|
||||
{items}
|
||||
</List>
|
||||
|
||||
{/* group divider */}
|
||||
<Divider sx={{ mt: 0.25, mb: 1.25 }} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
NavGroup.propTypes = {
|
||||
item: PropTypes.object
|
||||
};
|
||||
|
||||
export default NavGroup;
|
||||
@@ -0,0 +1,115 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { forwardRef, useEffect } from 'react';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
// material-ui
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { Avatar, Chip, ListItemButton, ListItemIcon, ListItemText, Typography, useMediaQuery } from '@mui/material';
|
||||
|
||||
// project imports
|
||||
import { MENU_OPEN, SET_MENU } from 'store/actions';
|
||||
|
||||
// assets
|
||||
import FiberManualRecordIcon from '@mui/icons-material/FiberManualRecord';
|
||||
|
||||
// ==============================|| SIDEBAR MENU LIST ITEMS ||============================== //
|
||||
|
||||
const NavItem = ({ item, level }) => {
|
||||
const theme = useTheme();
|
||||
const dispatch = useDispatch();
|
||||
const { pathname } = useLocation();
|
||||
const customization = useSelector((state) => state.customization);
|
||||
const matchesSM = useMediaQuery(theme.breakpoints.down('lg'));
|
||||
|
||||
const Icon = item.icon;
|
||||
const itemIcon = item?.icon ? (
|
||||
<Icon stroke={1.5} size="1.3rem" />
|
||||
) : (
|
||||
<FiberManualRecordIcon
|
||||
sx={{
|
||||
width: customization.isOpen.findIndex((id) => id === item?.id) > -1 ? 8 : 6,
|
||||
height: customization.isOpen.findIndex((id) => id === item?.id) > -1 ? 8 : 6
|
||||
}}
|
||||
fontSize={level > 0 ? 'inherit' : 'medium'}
|
||||
/>
|
||||
);
|
||||
|
||||
let itemTarget = '_self';
|
||||
if (item.target) {
|
||||
itemTarget = '_blank';
|
||||
}
|
||||
|
||||
let listItemProps = {
|
||||
component: forwardRef((props, ref) => <Link ref={ref} {...props} to={item.url} target={itemTarget} />)
|
||||
};
|
||||
if (item?.external) {
|
||||
listItemProps = { component: 'a', href: item.url, target: itemTarget };
|
||||
}
|
||||
|
||||
const itemHandler = (id) => {
|
||||
dispatch({ type: MENU_OPEN, id });
|
||||
if (matchesSM) dispatch({ type: SET_MENU, opened: false });
|
||||
};
|
||||
|
||||
// active menu item on page load
|
||||
useEffect(() => {
|
||||
const currentIndex = document.location.pathname
|
||||
.toString()
|
||||
.split('/')
|
||||
.findIndex((id) => id === item.id);
|
||||
if (currentIndex > -1) {
|
||||
dispatch({ type: MENU_OPEN, id: item.id });
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
}, [pathname]);
|
||||
|
||||
return (
|
||||
<ListItemButton
|
||||
{...listItemProps}
|
||||
disabled={item.disabled}
|
||||
sx={{
|
||||
borderRadius: `${customization.borderRadius}px`,
|
||||
mb: 0.5,
|
||||
alignItems: 'flex-start',
|
||||
backgroundColor: level > 1 ? 'transparent !important' : 'inherit',
|
||||
py: level > 1 ? 1 : 1.25,
|
||||
pl: `${level * 24}px`
|
||||
}}
|
||||
selected={customization.isOpen.findIndex((id) => id === item.id) > -1}
|
||||
onClick={() => itemHandler(item.id)}
|
||||
>
|
||||
<ListItemIcon sx={{ my: 'auto', minWidth: !item?.icon ? 18 : 36 }}>{itemIcon}</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={
|
||||
<Typography variant={customization.isOpen.findIndex((id) => id === item.id) > -1 ? 'h5' : 'body1'} color="inherit">
|
||||
{item.title}
|
||||
</Typography>
|
||||
}
|
||||
secondary={
|
||||
item.caption && (
|
||||
<Typography variant="caption" sx={{ ...theme.typography.subMenuCaption }} display="block" gutterBottom>
|
||||
{item.caption}
|
||||
</Typography>
|
||||
)
|
||||
}
|
||||
/>
|
||||
{item.chip && (
|
||||
<Chip
|
||||
color={item.chip.color}
|
||||
variant={item.chip.variant}
|
||||
size={item.chip.size}
|
||||
label={item.chip.label}
|
||||
avatar={item.chip.avatar && <Avatar>{item.chip.avatar}</Avatar>}
|
||||
/>
|
||||
)}
|
||||
</ListItemButton>
|
||||
);
|
||||
};
|
||||
|
||||
NavItem.propTypes = {
|
||||
item: PropTypes.object,
|
||||
level: PropTypes.number
|
||||
};
|
||||
|
||||
export default NavItem;
|
||||
36
web/berry/src/layout/MainLayout/Sidebar/MenuList/index.js
Normal file
@@ -0,0 +1,36 @@
|
||||
// material-ui
|
||||
import { Typography } from '@mui/material';
|
||||
|
||||
// project imports
|
||||
import NavGroup from './NavGroup';
|
||||
import menuItem from 'menu-items';
|
||||
import { isAdmin } from 'utils/common';
|
||||
|
||||
// ==============================|| SIDEBAR MENU LIST ||============================== //
|
||||
const MenuList = () => {
|
||||
const userIsAdmin = isAdmin();
|
||||
|
||||
return (
|
||||
<>
|
||||
{menuItem.items.map((item) => {
|
||||
if (item.type !== 'group') {
|
||||
return (
|
||||
<Typography key={item.id} variant="h6" color="error" align="center">
|
||||
Menu Items Error
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
const filteredChildren = item.children.filter((child) => !child.isAdmin || userIsAdmin);
|
||||
|
||||
if (filteredChildren.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <NavGroup key={item.id} item={{ ...item, children: filteredChildren }} />;
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default MenuList;
|
||||
106
web/berry/src/layout/MainLayout/Sidebar/index.js
Normal file
@@ -0,0 +1,106 @@
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
// material-ui
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { Box, Chip, Drawer, Stack, useMediaQuery } from '@mui/material';
|
||||
|
||||
// third-party
|
||||
import PerfectScrollbar from 'react-perfect-scrollbar';
|
||||
import { BrowserView, MobileView } from 'react-device-detect';
|
||||
|
||||
// project imports
|
||||
import MenuList from './MenuList';
|
||||
import LogoSection from '../LogoSection';
|
||||
import MenuCard from './MenuCard';
|
||||
import { drawerWidth } from 'store/constant';
|
||||
|
||||
// ==============================|| SIDEBAR DRAWER ||============================== //
|
||||
|
||||
const Sidebar = ({ drawerOpen, drawerToggle, window }) => {
|
||||
const theme = useTheme();
|
||||
const matchUpMd = useMediaQuery(theme.breakpoints.up('md'));
|
||||
|
||||
const drawer = (
|
||||
<>
|
||||
<Box sx={{ display: { xs: 'block', md: 'none' } }}>
|
||||
<Box sx={{ display: 'flex', p: 2, mx: 'auto' }}>
|
||||
<LogoSection />
|
||||
</Box>
|
||||
</Box>
|
||||
<BrowserView>
|
||||
<PerfectScrollbar
|
||||
component="div"
|
||||
style={{
|
||||
height: !matchUpMd ? 'calc(100vh - 56px)' : 'calc(100vh - 88px)',
|
||||
paddingLeft: '16px',
|
||||
paddingRight: '16px'
|
||||
}}
|
||||
>
|
||||
<MenuList />
|
||||
<MenuCard />
|
||||
<Stack direction="row" justifyContent="center" sx={{ mb: 2 }}>
|
||||
<Chip
|
||||
label={process.env.REACT_APP_VERSION || '未知版本号'}
|
||||
disabled
|
||||
chipcolor="secondary"
|
||||
size="small"
|
||||
sx={{ cursor: 'pointer' }}
|
||||
/>
|
||||
</Stack>
|
||||
</PerfectScrollbar>
|
||||
</BrowserView>
|
||||
<MobileView>
|
||||
<Box sx={{ px: 2 }}>
|
||||
<MenuList />
|
||||
<MenuCard />
|
||||
<Stack direction="row" justifyContent="center" sx={{ mb: 2 }}>
|
||||
<Chip
|
||||
label={process.env.REACT_APP_VERSION || '未知版本号'}
|
||||
disabled
|
||||
chipcolor="secondary"
|
||||
size="small"
|
||||
sx={{ cursor: 'pointer' }}
|
||||
/>
|
||||
</Stack>
|
||||
</Box>
|
||||
</MobileView>
|
||||
</>
|
||||
);
|
||||
|
||||
const container = window !== undefined ? () => window.document.body : undefined;
|
||||
|
||||
return (
|
||||
<Box component="nav" sx={{ flexShrink: { md: 0 }, width: matchUpMd ? drawerWidth : 'auto' }} aria-label="mailbox folders">
|
||||
<Drawer
|
||||
container={container}
|
||||
variant={matchUpMd ? 'persistent' : 'temporary'}
|
||||
anchor="left"
|
||||
open={drawerOpen}
|
||||
onClose={drawerToggle}
|
||||
sx={{
|
||||
'& .MuiDrawer-paper': {
|
||||
width: drawerWidth,
|
||||
background: theme.palette.background.default,
|
||||
color: theme.palette.text.primary,
|
||||
borderRight: 'none',
|
||||
[theme.breakpoints.up('md')]: {
|
||||
top: '88px'
|
||||
}
|
||||
}
|
||||
}}
|
||||
ModalProps={{ keepMounted: true }}
|
||||
color="inherit"
|
||||
>
|
||||
{drawer}
|
||||
</Drawer>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
Sidebar.propTypes = {
|
||||
drawerOpen: PropTypes.bool,
|
||||
drawerToggle: PropTypes.func,
|
||||
window: PropTypes.object
|
||||
};
|
||||
|
||||
export default Sidebar;
|
||||
103
web/berry/src/layout/MainLayout/index.js
Normal file
@@ -0,0 +1,103 @@
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
import AuthGuard from 'utils/route-guard/AuthGuard';
|
||||
|
||||
// material-ui
|
||||
import { styled, useTheme } from '@mui/material/styles';
|
||||
import { AppBar, Box, CssBaseline, Toolbar, useMediaQuery } from '@mui/material';
|
||||
import AdminContainer from 'ui-component/AdminContainer';
|
||||
|
||||
// project imports
|
||||
import Breadcrumbs from 'ui-component/extended/Breadcrumbs';
|
||||
import Header from './Header';
|
||||
import Sidebar from './Sidebar';
|
||||
import navigation from 'menu-items';
|
||||
import { drawerWidth } from 'store/constant';
|
||||
import { SET_MENU } from 'store/actions';
|
||||
|
||||
// assets
|
||||
import { IconChevronRight } from '@tabler/icons-react';
|
||||
|
||||
// styles
|
||||
const Main = styled('main', { shouldForwardProp: (prop) => prop !== 'open' })(({ theme, open }) => ({
|
||||
...theme.typography.mainContent,
|
||||
borderBottomLeftRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
transition: theme.transitions.create(
|
||||
'margin',
|
||||
open
|
||||
? {
|
||||
easing: theme.transitions.easing.easeOut,
|
||||
duration: theme.transitions.duration.enteringScreen
|
||||
}
|
||||
: {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen
|
||||
}
|
||||
),
|
||||
[theme.breakpoints.up('md')]: {
|
||||
marginLeft: open ? 0 : -(drawerWidth - 20),
|
||||
width: `calc(100% - ${drawerWidth}px)`
|
||||
},
|
||||
[theme.breakpoints.down('md')]: {
|
||||
marginLeft: '20px',
|
||||
width: `calc(100% - ${drawerWidth}px)`,
|
||||
padding: '16px'
|
||||
},
|
||||
[theme.breakpoints.down('sm')]: {
|
||||
marginLeft: '10px',
|
||||
width: `calc(100% - ${drawerWidth}px)`,
|
||||
padding: '16px',
|
||||
marginRight: '10px'
|
||||
}
|
||||
}));
|
||||
|
||||
// ==============================|| MAIN LAYOUT ||============================== //
|
||||
|
||||
const MainLayout = () => {
|
||||
const theme = useTheme();
|
||||
const matchDownMd = useMediaQuery(theme.breakpoints.down('md'));
|
||||
// Handle left drawer
|
||||
const leftDrawerOpened = useSelector((state) => state.customization.opened);
|
||||
const dispatch = useDispatch();
|
||||
const handleLeftDrawerToggle = () => {
|
||||
dispatch({ type: SET_MENU, opened: !leftDrawerOpened });
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex' }}>
|
||||
<CssBaseline />
|
||||
{/* header */}
|
||||
<AppBar
|
||||
enableColorOnDark
|
||||
position="fixed"
|
||||
color="inherit"
|
||||
elevation={0}
|
||||
sx={{
|
||||
bgcolor: theme.palette.background.default,
|
||||
transition: leftDrawerOpened ? theme.transitions.create('width') : 'none'
|
||||
}}
|
||||
>
|
||||
<Toolbar>
|
||||
<Header handleLeftDrawerToggle={handleLeftDrawerToggle} />
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
|
||||
{/* drawer */}
|
||||
<Sidebar drawerOpen={!matchDownMd ? leftDrawerOpened : !leftDrawerOpened} drawerToggle={handleLeftDrawerToggle} />
|
||||
|
||||
{/* main content */}
|
||||
<Main theme={theme} open={leftDrawerOpened}>
|
||||
{/* breadcrumb */}
|
||||
<Breadcrumbs separator={IconChevronRight} navigation={navigation} icon title rightAlign />
|
||||
<AuthGuard>
|
||||
<AdminContainer>
|
||||
<Outlet />
|
||||
</AdminContainer>
|
||||
</AuthGuard>
|
||||
</Main>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default MainLayout;
|
||||
161
web/berry/src/layout/MinimalLayout/Header/index.js
Normal file
@@ -0,0 +1,161 @@
|
||||
// material-ui
|
||||
import { useState } from 'react';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Stack,
|
||||
Popper,
|
||||
IconButton,
|
||||
List,
|
||||
ListItemButton,
|
||||
Paper,
|
||||
ListItemText,
|
||||
Typography,
|
||||
Divider,
|
||||
ClickAwayListener
|
||||
} from '@mui/material';
|
||||
import LogoSection from 'layout/MainLayout/LogoSection';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useSelector } from 'react-redux';
|
||||
import ThemeButton from 'ui-component/ThemeButton';
|
||||
import ProfileSection from 'layout/MainLayout/Header/ProfileSection';
|
||||
import { IconMenu2 } from '@tabler/icons-react';
|
||||
import Transitions from 'ui-component/extended/Transitions';
|
||||
import MainCard from 'ui-component/cards/MainCard';
|
||||
import { useMediaQuery } from '@mui/material';
|
||||
|
||||
// ==============================|| MAIN NAVBAR / HEADER ||============================== //
|
||||
|
||||
const Header = () => {
|
||||
const theme = useTheme();
|
||||
const { pathname } = useLocation();
|
||||
const account = useSelector((state) => state.account);
|
||||
const [open, setOpen] = useState(null);
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
|
||||
const handleOpenMenu = (event) => {
|
||||
setOpen(open ? null : event.currentTarget);
|
||||
};
|
||||
|
||||
const handleCloseMenu = () => {
|
||||
setOpen(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
width: 228,
|
||||
display: 'flex',
|
||||
[theme.breakpoints.down('md')]: {
|
||||
width: 'auto'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Box component="span" sx={{ flexGrow: 1 }}>
|
||||
<LogoSection />
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ flexGrow: 1 }} />
|
||||
<Box sx={{ flexGrow: 1 }} />
|
||||
<Stack spacing={2} direction="row" justifyContent="center" alignItems="center">
|
||||
{isMobile ? (
|
||||
<>
|
||||
<ThemeButton />
|
||||
<IconButton onClick={handleOpenMenu}>
|
||||
<IconMenu2 />
|
||||
</IconButton>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Button component={Link} variant="text" to="/" color={pathname === '/' ? 'primary' : 'inherit'}>
|
||||
首页
|
||||
</Button>
|
||||
<Button component={Link} variant="text" to="/about" color={pathname === '/about' ? 'primary' : 'inherit'}>
|
||||
关于
|
||||
</Button>
|
||||
<ThemeButton />
|
||||
{account.user ? (
|
||||
<>
|
||||
<Button component={Link} variant="contained" to="/panel" color="primary">
|
||||
控制台
|
||||
</Button>
|
||||
<ProfileSection />
|
||||
</>
|
||||
) : (
|
||||
<Button component={Link} variant="contained" to="/login" color="primary">
|
||||
登录
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
<Popper
|
||||
open={!!open}
|
||||
anchorEl={open}
|
||||
transition
|
||||
disablePortal
|
||||
popperOptions={{
|
||||
modifiers: [
|
||||
{
|
||||
name: 'offset',
|
||||
options: {
|
||||
offset: [0, 14]
|
||||
}
|
||||
}
|
||||
]
|
||||
}}
|
||||
style={{ width: '100vw' }}
|
||||
>
|
||||
{({ TransitionProps }) => (
|
||||
<Transitions in={open} {...TransitionProps}>
|
||||
<ClickAwayListener onClickAway={handleCloseMenu}>
|
||||
<Paper style={{ width: '100%' }}>
|
||||
<MainCard border={false} elevation={16} content={false} boxShadow shadow={theme.shadows[16]}>
|
||||
<List
|
||||
component="nav"
|
||||
sx={{
|
||||
width: '100%',
|
||||
maxWidth: '100%',
|
||||
minWidth: '100%',
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
|
||||
'& .MuiListItemButton-root': {
|
||||
mt: 0.5
|
||||
}
|
||||
}}
|
||||
onClick={handleCloseMenu}
|
||||
>
|
||||
<ListItemButton component={Link} variant="text" to="/">
|
||||
<ListItemText primary={<Typography variant="body2">首页</Typography>} />
|
||||
</ListItemButton>
|
||||
|
||||
<ListItemButton component={Link} variant="text" to="/about">
|
||||
<ListItemText primary={<Typography variant="body2">关于</Typography>} />
|
||||
</ListItemButton>
|
||||
<Divider />
|
||||
{account.user ? (
|
||||
<ListItemButton component={Link} variant="contained" to="/panel" color="primary">
|
||||
控制台
|
||||
</ListItemButton>
|
||||
) : (
|
||||
<ListItemButton component={Link} variant="contained" to="/login" color="primary">
|
||||
登录
|
||||
</ListItemButton>
|
||||
)}
|
||||
</List>
|
||||
</MainCard>
|
||||
</Paper>
|
||||
</ClickAwayListener>
|
||||
</Transitions>
|
||||
)}
|
||||
</Popper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
41
web/berry/src/layout/MinimalLayout/index.js
Normal file
@@ -0,0 +1,41 @@
|
||||
import { Outlet } from 'react-router-dom';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { AppBar, Box, CssBaseline, Toolbar, Container } from '@mui/material';
|
||||
import Header from './Header';
|
||||
import Footer from 'ui-component/Footer';
|
||||
|
||||
// ==============================|| MINIMAL LAYOUT ||============================== //
|
||||
|
||||
const MinimalLayout = () => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', minHeight: '100vh' }}>
|
||||
<CssBaseline />
|
||||
<AppBar
|
||||
enableColorOnDark
|
||||
position="fixed"
|
||||
color="inherit"
|
||||
elevation={0}
|
||||
sx={{
|
||||
bgcolor: theme.palette.background.default,
|
||||
flex: 'none'
|
||||
}}
|
||||
>
|
||||
<Container>
|
||||
<Toolbar>
|
||||
<Header />
|
||||
</Toolbar>
|
||||
</Container>
|
||||
</AppBar>
|
||||
<Box sx={{ flex: '1 1 auto', overflow: 'auto' }} marginTop={'80px'}>
|
||||
<Outlet />
|
||||
</Box>
|
||||
<Box sx={{ flex: 'none' }}>
|
||||
<Footer />
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default MinimalLayout;
|
||||
39
web/berry/src/layout/NavMotion.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
// ==============================|| ANIMATION FOR CONTENT ||============================== //
|
||||
|
||||
const NavMotion = ({ children }) => {
|
||||
const motionVariants = {
|
||||
initial: {
|
||||
opacity: 0,
|
||||
scale: 0.99
|
||||
},
|
||||
in: {
|
||||
opacity: 1,
|
||||
scale: 1
|
||||
},
|
||||
out: {
|
||||
opacity: 0,
|
||||
scale: 1.01
|
||||
}
|
||||
};
|
||||
|
||||
const motionTransition = {
|
||||
type: 'tween',
|
||||
ease: 'anticipate',
|
||||
duration: 0.4
|
||||
};
|
||||
|
||||
return (
|
||||
<motion.div initial="initial" animate="in" exit="out" variants={motionVariants} transition={motionTransition}>
|
||||
{children}
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
||||
NavMotion.propTypes = {
|
||||
children: PropTypes.node
|
||||
};
|
||||
|
||||
export default NavMotion;
|
||||
26
web/berry/src/layout/NavigationScroll.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { useEffect } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
// ==============================|| NAVIGATION SCROLL TO TOP ||============================== //
|
||||
|
||||
const NavigationScroll = ({ children }) => {
|
||||
const location = useLocation();
|
||||
const { pathname } = location;
|
||||
|
||||
useEffect(() => {
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
left: 0,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}, [pathname]);
|
||||
|
||||
return children || null;
|
||||
};
|
||||
|
||||
NavigationScroll.propTypes = {
|
||||
children: PropTypes.node
|
||||
};
|
||||
|
||||
export default NavigationScroll;
|
||||
18
web/berry/src/menu-items/index.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import panel from './panel';
|
||||
|
||||
// ==============================|| MENU ITEMS ||============================== //
|
||||
|
||||
const menuItems = {
|
||||
items: [panel],
|
||||
urlMap: {}
|
||||
};
|
||||
|
||||
// Initialize urlMap
|
||||
menuItems.urlMap = menuItems.items.reduce((map, item) => {
|
||||
item.children.forEach((child) => {
|
||||
map[child.url] = child;
|
||||
});
|
||||
return map;
|
||||
}, {});
|
||||
|
||||
export default menuItems;
|
||||
104
web/berry/src/menu-items/panel.js
Normal file
@@ -0,0 +1,104 @@
|
||||
// assets
|
||||
import {
|
||||
IconDashboard,
|
||||
IconSitemap,
|
||||
IconArticle,
|
||||
IconCoin,
|
||||
IconAdjustments,
|
||||
IconKey,
|
||||
IconGardenCart,
|
||||
IconUser,
|
||||
IconUserScan
|
||||
} from '@tabler/icons-react';
|
||||
|
||||
// constant
|
||||
const icons = { IconDashboard, IconSitemap, IconArticle, IconCoin, IconAdjustments, IconKey, IconGardenCart, IconUser, IconUserScan };
|
||||
|
||||
// ==============================|| DASHBOARD MENU ITEMS ||============================== //
|
||||
|
||||
const panel = {
|
||||
id: 'panel',
|
||||
type: 'group',
|
||||
children: [
|
||||
{
|
||||
id: 'dashboard',
|
||||
title: '总览',
|
||||
type: 'item',
|
||||
url: '/panel/dashboard',
|
||||
icon: icons.IconDashboard,
|
||||
breadcrumbs: false,
|
||||
isAdmin: false
|
||||
},
|
||||
{
|
||||
id: 'channel',
|
||||
title: '渠道',
|
||||
type: 'item',
|
||||
url: '/panel/channel',
|
||||
icon: icons.IconSitemap,
|
||||
breadcrumbs: false,
|
||||
isAdmin: true
|
||||
},
|
||||
{
|
||||
id: 'token',
|
||||
title: '令牌',
|
||||
type: 'item',
|
||||
url: '/panel/token',
|
||||
icon: icons.IconKey,
|
||||
breadcrumbs: false
|
||||
},
|
||||
{
|
||||
id: 'log',
|
||||
title: '日志',
|
||||
type: 'item',
|
||||
url: '/panel/log',
|
||||
icon: icons.IconArticle,
|
||||
breadcrumbs: false
|
||||
},
|
||||
{
|
||||
id: 'redemption',
|
||||
title: '兑换',
|
||||
type: 'item',
|
||||
url: '/panel/redemption',
|
||||
icon: icons.IconCoin,
|
||||
breadcrumbs: false,
|
||||
isAdmin: true
|
||||
},
|
||||
{
|
||||
id: 'topup',
|
||||
title: '充值',
|
||||
type: 'item',
|
||||
url: '/panel/topup',
|
||||
icon: icons.IconGardenCart,
|
||||
breadcrumbs: false
|
||||
},
|
||||
{
|
||||
id: 'user',
|
||||
title: '用户',
|
||||
type: 'item',
|
||||
url: '/panel/user',
|
||||
icon: icons.IconUser,
|
||||
breadcrumbs: false,
|
||||
isAdmin: true
|
||||
},
|
||||
{
|
||||
id: 'profile',
|
||||
title: '我的',
|
||||
type: 'item',
|
||||
url: '/panel/profile',
|
||||
icon: icons.IconUserScan,
|
||||
breadcrumbs: false,
|
||||
isAdmin: false
|
||||
},
|
||||
{
|
||||
id: 'setting',
|
||||
title: '设置',
|
||||
type: 'item',
|
||||
url: '/panel/setting',
|
||||
icon: icons.IconAdjustments,
|
||||
breadcrumbs: false,
|
||||
isAdmin: true
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export default panel;
|
||||
73
web/berry/src/routes/MainRoutes.js
Normal file
@@ -0,0 +1,73 @@
|
||||
import { lazy } from 'react';
|
||||
|
||||
// project imports
|
||||
import MainLayout from 'layout/MainLayout';
|
||||
import Loadable from 'ui-component/Loadable';
|
||||
|
||||
const Channel = Loadable(lazy(() => import('views/Channel')));
|
||||
const Log = Loadable(lazy(() => import('views/Log')));
|
||||
const Redemption = Loadable(lazy(() => import('views/Redemption')));
|
||||
const Setting = Loadable(lazy(() => import('views/Setting')));
|
||||
const Token = Loadable(lazy(() => import('views/Token')));
|
||||
const Topup = Loadable(lazy(() => import('views/Topup')));
|
||||
const User = Loadable(lazy(() => import('views/User')));
|
||||
const Profile = Loadable(lazy(() => import('views/Profile')));
|
||||
const NotFoundView = Loadable(lazy(() => import('views/Error')));
|
||||
|
||||
// dashboard routing
|
||||
const Dashboard = Loadable(lazy(() => import('views/Dashboard')));
|
||||
|
||||
// ==============================|| MAIN ROUTING ||============================== //
|
||||
|
||||
const MainRoutes = {
|
||||
path: '/panel',
|
||||
element: <MainLayout />,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
element: <Dashboard />
|
||||
},
|
||||
{
|
||||
path: 'dashboard',
|
||||
element: <Dashboard />
|
||||
},
|
||||
{
|
||||
path: 'channel',
|
||||
element: <Channel />
|
||||
},
|
||||
{
|
||||
path: 'log',
|
||||
element: <Log />
|
||||
},
|
||||
{
|
||||
path: 'redemption',
|
||||
element: <Redemption />
|
||||
},
|
||||
{
|
||||
path: 'setting',
|
||||
element: <Setting />
|
||||
},
|
||||
{
|
||||
path: 'token',
|
||||
element: <Token />
|
||||
},
|
||||
{
|
||||
path: 'topup',
|
||||
element: <Topup />
|
||||
},
|
||||
{
|
||||
path: 'user',
|
||||
element: <User />
|
||||
},
|
||||
{
|
||||
path: 'profile',
|
||||
element: <Profile />
|
||||
},
|
||||
{
|
||||
path: '404',
|
||||
element: <NotFoundView />
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export default MainRoutes;
|
||||
68
web/berry/src/routes/OtherRoutes.js
Normal file
@@ -0,0 +1,68 @@
|
||||
import { lazy } from 'react';
|
||||
|
||||
// project imports
|
||||
import Loadable from 'ui-component/Loadable';
|
||||
import MinimalLayout from 'layout/MinimalLayout';
|
||||
|
||||
// login option 3 routing
|
||||
const AuthLogin = Loadable(lazy(() => import('views/Authentication/Auth/Login')));
|
||||
const AuthRegister = Loadable(lazy(() => import('views/Authentication/Auth/Register')));
|
||||
const GitHubOAuth = Loadable(lazy(() => import('views/Authentication/Auth/GitHubOAuth')));
|
||||
const LarkOAuth = Loadable(lazy(() => import('views/Authentication/Auth/LarkOAuth')));
|
||||
const OidcOAuth = Loadable(lazy(() => import('views/Authentication/Auth/OidcOAuth')));
|
||||
const ForgetPassword = Loadable(lazy(() => import('views/Authentication/Auth/ForgetPassword')));
|
||||
const ResetPassword = Loadable(lazy(() => import('views/Authentication/Auth/ResetPassword')));
|
||||
const Home = Loadable(lazy(() => import('views/Home')));
|
||||
const About = Loadable(lazy(() => import('views/About')));
|
||||
const NotFoundView = Loadable(lazy(() => import('views/Error')));
|
||||
|
||||
// ==============================|| AUTHENTICATION ROUTING ||============================== //
|
||||
|
||||
const OtherRoutes = {
|
||||
path: '/',
|
||||
element: <MinimalLayout />,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
element: <Home />
|
||||
},
|
||||
{
|
||||
path: '/about',
|
||||
element: <About />
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
element: <AuthLogin />
|
||||
},
|
||||
{
|
||||
path: '/register',
|
||||
element: <AuthRegister />
|
||||
},
|
||||
{
|
||||
path: '/reset',
|
||||
element: <ForgetPassword />
|
||||
},
|
||||
{
|
||||
path: '/user/reset',
|
||||
element: <ResetPassword />
|
||||
},
|
||||
{
|
||||
path: '/oauth/github',
|
||||
element: <GitHubOAuth />
|
||||
},
|
||||
{
|
||||
path: '/oauth/lark',
|
||||
element: <LarkOAuth />
|
||||
},
|
||||
{
|
||||
path: 'oauth/oidc',
|
||||
element: <OidcOAuth />
|
||||
},
|
||||
{
|
||||
path: '/404',
|
||||
element: <NotFoundView />
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export default OtherRoutes;
|
||||
11
web/berry/src/routes/index.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import { useRoutes } from 'react-router-dom';
|
||||
|
||||
// routes
|
||||
import MainRoutes from './MainRoutes';
|
||||
import OtherRoutes from './OtherRoutes';
|
||||
|
||||
// ==============================|| ROUTING RENDER ||============================== //
|
||||
|
||||
export default function ThemeRoutes() {
|
||||
return useRoutes([MainRoutes, OtherRoutes]);
|
||||
}
|
||||
128
web/berry/src/serviceWorker.js
Normal file
@@ -0,0 +1,128 @@
|
||||
// This optional code is used to register a service worker.
|
||||
// register() is not called by default.
|
||||
|
||||
// This lets the app load faster on subsequent visits in production, and gives
|
||||
// it offline capabilities. However, it also means that developers (and users)
|
||||
// will only see deployed updates on subsequent visits to a page, after all the
|
||||
// existing tabs open on the page have been closed, since previously cached
|
||||
// resources are updated in the background.
|
||||
|
||||
// To learn more about the benefits of this model and instructions on how to
|
||||
// opt-in, read https://bit.ly/CRA-PWA
|
||||
|
||||
const isLocalhost = Boolean(
|
||||
window.location.hostname === 'localhost' ||
|
||||
// [::1] is the IPv6 localhost address.
|
||||
window.location.hostname === '[::1]' ||
|
||||
// 127.0.0.0/8 are considered localhost for IPv4.
|
||||
window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/)
|
||||
);
|
||||
|
||||
function registerValidSW(swUrl, config) {
|
||||
navigator.serviceWorker
|
||||
.register(swUrl)
|
||||
.then((registration) => {
|
||||
registration.onupdatefound = () => {
|
||||
const installingWorker = registration.installing;
|
||||
if (installingWorker == null) {
|
||||
return;
|
||||
}
|
||||
installingWorker.onstatechange = () => {
|
||||
if (installingWorker.state === 'installed') {
|
||||
if (navigator.serviceWorker.controller) {
|
||||
// At this point, the updated precached content has been fetched,
|
||||
// but the previous service worker will still serve the older
|
||||
// content until all client tabs are closed.
|
||||
console.log('New content is available and will be used when all tabs for this page are closed. See https://bit.ly/CRA-PWA.');
|
||||
|
||||
// Execute callback
|
||||
if (config && config.onUpdate) {
|
||||
config.onUpdate(registration);
|
||||
}
|
||||
} else {
|
||||
// At this point, everything has been precached.
|
||||
// It's the perfect time to display a
|
||||
// "Content is cached for offline use." message.
|
||||
console.log('Content is cached for offline use.');
|
||||
|
||||
// Execute callback
|
||||
if (config && config.onSuccess) {
|
||||
config.onSuccess(registration);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error during service worker registration:', error);
|
||||
});
|
||||
}
|
||||
|
||||
function checkValidServiceWorker(swUrl, config) {
|
||||
// Check if the service worker can be found. If it can't reload the page.
|
||||
fetch(swUrl, {
|
||||
headers: { 'Service-Worker': 'script' }
|
||||
})
|
||||
.then((response) => {
|
||||
// Ensure service worker exists, and that we really are getting a JS file.
|
||||
const contentType = response.headers.get('content-type');
|
||||
if (response.status === 404 || (contentType != null && contentType.indexOf('javascript') === -1)) {
|
||||
// No service worker found. Probably a different app. Reload the page.
|
||||
navigator.serviceWorker.ready.then((registration) => {
|
||||
registration.unregister().then(() => {
|
||||
window.location.reload();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// Service worker found. Proceed as normal.
|
||||
registerValidSW(swUrl, config);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
console.log('No internet connection found. App is running in offline mode.');
|
||||
});
|
||||
}
|
||||
|
||||
export function register(config) {
|
||||
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
|
||||
// The URL constructor is available in all browsers that support SW.
|
||||
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
|
||||
if (publicUrl.origin !== window.location.origin) {
|
||||
// Our service worker won't work if PUBLIC_URL is on a different origin
|
||||
// from what our page is served on. This might happen if a CDN is used to
|
||||
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
|
||||
return;
|
||||
}
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
|
||||
|
||||
if (isLocalhost) {
|
||||
// This is running on localhost. Let's check if a service worker still exists or not.
|
||||
checkValidServiceWorker(swUrl, config);
|
||||
|
||||
// Add some additional logging to localhost, pointing developers to the
|
||||
// service worker/PWA documentation.
|
||||
navigator.serviceWorker.ready.then(() => {
|
||||
console.log('This web app is being served cache-first by a service worker. To learn more, visit https://bit.ly/CRA-PWA');
|
||||
});
|
||||
} else {
|
||||
// Is not localhost. Just register service worker
|
||||
registerValidSW(swUrl, config);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function unregister() {
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.ready
|
||||
.then((registration) => {
|
||||
registration.unregister();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error.message);
|
||||
});
|
||||
}
|
||||
}
|
||||
24
web/berry/src/store/accountReducer.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import * as actionTypes from './actions';
|
||||
|
||||
export const initialState = {
|
||||
user: undefined
|
||||
};
|
||||
|
||||
const accountReducer = (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case actionTypes.LOGIN:
|
||||
return {
|
||||
...state,
|
||||
user: action.payload
|
||||
};
|
||||
case actionTypes.LOGOUT:
|
||||
return {
|
||||
...state,
|
||||
user: undefined
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default accountReducer;
|
||||
10
web/berry/src/store/actions.js
Normal file
@@ -0,0 +1,10 @@
|
||||
// action - customization reducer
|
||||
export const SET_MENU = '@customization/SET_MENU';
|
||||
export const MENU_TOGGLE = '@customization/MENU_TOGGLE';
|
||||
export const MENU_OPEN = '@customization/MENU_OPEN';
|
||||
export const SET_FONT_FAMILY = '@customization/SET_FONT_FAMILY';
|
||||
export const SET_BORDER_RADIUS = '@customization/SET_BORDER_RADIUS';
|
||||
export const SET_SITE_INFO = '@siteInfo/SET_SITE_INFO';
|
||||
export const LOGIN = '@account/LOGIN';
|
||||
export const LOGOUT = '@account/LOGOUT';
|
||||
export const SET_THEME = '@customization/SET_THEME';
|
||||
4
web/berry/src/store/constant.js
Normal file
@@ -0,0 +1,4 @@
|
||||
// theme constant
|
||||
export const gridSpacing = 3;
|
||||
export const drawerWidth = 260;
|
||||
export const appDrawerWidth = 320;
|
||||
52
web/berry/src/store/customizationReducer.js
Normal file
@@ -0,0 +1,52 @@
|
||||
// project imports
|
||||
import config from 'config';
|
||||
|
||||
// action - state management
|
||||
import * as actionTypes from './actions';
|
||||
|
||||
export const initialState = {
|
||||
isOpen: [], // for active default menu
|
||||
defaultId: 'default',
|
||||
fontFamily: config.fontFamily,
|
||||
borderRadius: config.borderRadius,
|
||||
opened: true,
|
||||
theme: 'light'
|
||||
};
|
||||
|
||||
// ==============================|| CUSTOMIZATION REDUCER ||============================== //
|
||||
|
||||
const customizationReducer = (state = initialState, action) => {
|
||||
let id;
|
||||
switch (action.type) {
|
||||
case actionTypes.MENU_OPEN:
|
||||
id = action.id;
|
||||
return {
|
||||
...state,
|
||||
isOpen: [id]
|
||||
};
|
||||
case actionTypes.SET_MENU:
|
||||
return {
|
||||
...state,
|
||||
opened: action.opened
|
||||
};
|
||||
case actionTypes.SET_FONT_FAMILY:
|
||||
return {
|
||||
...state,
|
||||
fontFamily: action.fontFamily
|
||||
};
|
||||
case actionTypes.SET_BORDER_RADIUS:
|
||||
return {
|
||||
...state,
|
||||
borderRadius: action.borderRadius
|
||||
};
|
||||
case actionTypes.SET_THEME:
|
||||
return {
|
||||
...state,
|
||||
theme: action.theme
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default customizationReducer;
|
||||
9
web/berry/src/store/index.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import { createStore } from 'redux';
|
||||
import reducer from './reducer';
|
||||
|
||||
// ==============================|| REDUX - MAIN STORE ||============================== //
|
||||
|
||||
const store = createStore(reducer);
|
||||
const persister = 'Free';
|
||||
|
||||
export { store, persister };
|
||||
16
web/berry/src/store/reducer.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import { combineReducers } from 'redux';
|
||||
|
||||
// reducer import
|
||||
import customizationReducer from './customizationReducer';
|
||||
import accountReducer from './accountReducer';
|
||||
import siteInfoReducer from './siteInfoReducer';
|
||||
|
||||
// ==============================|| COMBINE REDUCER ||============================== //
|
||||
|
||||
const reducer = combineReducers({
|
||||
customization: customizationReducer,
|
||||
account: accountReducer,
|
||||
siteInfo: siteInfoReducer
|
||||
});
|
||||
|
||||
export default reducer;
|
||||
18
web/berry/src/store/siteInfoReducer.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import config from 'config';
|
||||
import * as actionTypes from './actions';
|
||||
|
||||
export const initialState = config.siteInfo;
|
||||
|
||||
const siteInfoReducer = (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case actionTypes.SET_SITE_INFO:
|
||||
return {
|
||||
...state,
|
||||
...action.payload
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default siteInfoReducer;
|
||||
267
web/berry/src/themes/compStyleOverride.js
Normal file
@@ -0,0 +1,267 @@
|
||||
export default function componentStyleOverrides(theme) {
|
||||
const bgColor = theme.mode === 'dark' ? theme.backgroundDefault : theme.colors?.grey50;
|
||||
return {
|
||||
MuiButton: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
fontWeight: 500,
|
||||
borderRadius: '4px',
|
||||
'&.Mui-disabled': {
|
||||
color: theme.colors?.grey600
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
//MuiAutocomplete-popper MuiPopover-root
|
||||
MuiAutocomplete: {
|
||||
styleOverrides: {
|
||||
popper: {
|
||||
// 继承 MuiPopover-root
|
||||
boxShadow: '0px 5px 5px -3px rgba(0,0,0,0.2),0px 8px 10px 1px rgba(0,0,0,0.14),0px 3px 14px 2px rgba(0,0,0,0.12)',
|
||||
borderRadius: '12px',
|
||||
color: '#364152'
|
||||
},
|
||||
listbox: {
|
||||
// 继承 MuiPopover-root
|
||||
padding: '0px',
|
||||
paddingTop: '8px',
|
||||
paddingBottom: '8px'
|
||||
},
|
||||
option: {
|
||||
fontSize: '16px',
|
||||
fontWeight: '400',
|
||||
lineHeight: '1.334em',
|
||||
alignItems: 'center',
|
||||
paddingTop: '6px',
|
||||
paddingBottom: '6px',
|
||||
paddingLeft: '16px',
|
||||
paddingRight: '16px'
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiIconButton: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
color: theme.darkTextPrimary,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.colors?.grey200
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiPaper: {
|
||||
defaultProps: {
|
||||
elevation: 0
|
||||
},
|
||||
styleOverrides: {
|
||||
root: {
|
||||
backgroundImage: 'none'
|
||||
},
|
||||
rounded: {
|
||||
borderRadius: `${theme?.customization?.borderRadius}px`
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiCardHeader: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
color: theme.colors?.textDark,
|
||||
padding: '24px'
|
||||
},
|
||||
title: {
|
||||
fontSize: '1.125rem'
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiCardContent: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
padding: '24px'
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiCardActions: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
padding: '24px'
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiListItemButton: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
color: theme.darkTextPrimary,
|
||||
paddingTop: '10px',
|
||||
paddingBottom: '10px',
|
||||
'&.Mui-selected': {
|
||||
color: theme.menuSelected,
|
||||
backgroundColor: theme.menuSelectedBack,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.menuSelectedBack
|
||||
},
|
||||
'& .MuiListItemIcon-root': {
|
||||
color: theme.menuSelected
|
||||
}
|
||||
},
|
||||
'&:hover': {
|
||||
backgroundColor: theme.menuSelectedBack,
|
||||
color: theme.menuSelected,
|
||||
'& .MuiListItemIcon-root': {
|
||||
color: theme.menuSelected
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiListItemIcon: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
color: theme.darkTextPrimary,
|
||||
minWidth: '36px'
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiListItemText: {
|
||||
styleOverrides: {
|
||||
primary: {
|
||||
color: theme.textDark
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiInputBase: {
|
||||
styleOverrides: {
|
||||
input: {
|
||||
color: theme.textDark,
|
||||
'&::placeholder': {
|
||||
color: theme.darkTextSecondary,
|
||||
fontSize: '0.875rem'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiOutlinedInput: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
background: bgColor,
|
||||
borderRadius: `${theme?.customization?.borderRadius}px`,
|
||||
'& .MuiOutlinedInput-notchedOutline': {
|
||||
borderColor: theme.colors?.grey400
|
||||
},
|
||||
'&:hover $notchedOutline': {
|
||||
borderColor: theme.colors?.primaryLight
|
||||
},
|
||||
'&.MuiInputBase-multiline': {
|
||||
padding: 1
|
||||
}
|
||||
},
|
||||
input: {
|
||||
fontWeight: 500,
|
||||
background: bgColor,
|
||||
padding: '15.5px 14px',
|
||||
borderRadius: `${theme?.customization?.borderRadius}px`,
|
||||
'&.MuiInputBase-inputSizeSmall': {
|
||||
padding: '10px 14px',
|
||||
'&.MuiInputBase-inputAdornedStart': {
|
||||
paddingLeft: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
inputAdornedStart: {
|
||||
paddingLeft: 4
|
||||
},
|
||||
notchedOutline: {
|
||||
borderRadius: `${theme?.customization?.borderRadius}px`
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiSlider: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
'&.Mui-disabled': {
|
||||
color: theme.colors?.grey300
|
||||
}
|
||||
},
|
||||
mark: {
|
||||
backgroundColor: theme.paper,
|
||||
width: '4px'
|
||||
},
|
||||
valueLabel: {
|
||||
color: theme?.colors?.primaryLight
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiDivider: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
borderColor: theme.divider,
|
||||
opacity: 1
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiAvatar: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
color: theme.colors?.primaryDark,
|
||||
background: theme.colors?.primary200
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiChip: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
'&.MuiChip-deletable .MuiChip-deleteIcon': {
|
||||
color: 'inherit'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiTableCell: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
borderBottom: '1px solid ' + theme.tableBorderBottom,
|
||||
textAlign: 'center'
|
||||
},
|
||||
head: {
|
||||
color: theme.darkTextSecondary,
|
||||
backgroundColor: theme.headBackgroundColor
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiTableRow: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
'&:hover': {
|
||||
backgroundColor: theme.headBackgroundColor
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiTooltip: {
|
||||
styleOverrides: {
|
||||
tooltip: {
|
||||
color: theme.colors.paper,
|
||||
background: theme.colors?.grey700
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiCssBaseline: {
|
||||
styleOverrides: `
|
||||
.apexcharts-title-text {
|
||||
fill: ${theme.textDark} !important
|
||||
}
|
||||
.apexcharts-text {
|
||||
fill: ${theme.textDark} !important
|
||||
}
|
||||
.apexcharts-legend-text {
|
||||
color: ${theme.textDark} !important
|
||||
}
|
||||
.apexcharts-menu {
|
||||
background: ${theme.backgroundDefault} !important
|
||||
}
|
||||
.apexcharts-gridline, .apexcharts-xaxistooltip-background, .apexcharts-yaxistooltip-background {
|
||||
stroke: ${theme.divider} !important;
|
||||
}
|
||||
`
|
||||
}
|
||||
};
|
||||
}
|
||||
92
web/berry/src/themes/index.js
Normal file
@@ -0,0 +1,92 @@
|
||||
import { createTheme } from '@mui/material/styles';
|
||||
|
||||
// assets
|
||||
import colors from 'assets/scss/_themes-vars.module.scss';
|
||||
|
||||
// project imports
|
||||
import componentStyleOverrides from './compStyleOverride';
|
||||
import themePalette from './palette';
|
||||
import themeTypography from './typography';
|
||||
|
||||
/**
|
||||
* Represent theme style and structure as per Material-UI
|
||||
* @param {JsonObject} customization customization parameter object
|
||||
*/
|
||||
|
||||
export const theme = (customization) => {
|
||||
const color = colors;
|
||||
const options = customization.theme === 'light' ? GetLightOption() : GetDarkOption();
|
||||
const themeOption = {
|
||||
colors: color,
|
||||
...options,
|
||||
customization
|
||||
};
|
||||
|
||||
const themeOptions = {
|
||||
direction: 'ltr',
|
||||
palette: themePalette(themeOption),
|
||||
mixins: {
|
||||
toolbar: {
|
||||
minHeight: '48px',
|
||||
padding: '16px',
|
||||
'@media (min-width: 600px)': {
|
||||
minHeight: '48px'
|
||||
}
|
||||
}
|
||||
},
|
||||
typography: themeTypography(themeOption)
|
||||
};
|
||||
|
||||
const themes = createTheme(themeOptions);
|
||||
themes.components = componentStyleOverrides(themeOption);
|
||||
|
||||
return themes;
|
||||
};
|
||||
|
||||
export default theme;
|
||||
|
||||
function GetDarkOption() {
|
||||
const color = colors;
|
||||
return {
|
||||
mode: 'dark',
|
||||
heading: color.darkTextTitle,
|
||||
paper: color.darkLevel2,
|
||||
backgroundDefault: color.darkPaper,
|
||||
background: color.darkBackground,
|
||||
darkTextPrimary: color.darkTextPrimary,
|
||||
darkTextSecondary: color.darkTextSecondary,
|
||||
textDark: color.darkTextTitle,
|
||||
menuSelected: color.darkSecondaryMain,
|
||||
menuSelectedBack: color.darkSelectedBack,
|
||||
divider: color.darkDivider,
|
||||
borderColor: color.darkBorderColor,
|
||||
menuButton: color.darkLevel1,
|
||||
menuButtonColor: color.darkSecondaryMain,
|
||||
menuChip: color.darkLevel1,
|
||||
headBackgroundColor: color.darkBackground,
|
||||
tableBorderBottom: color.darkDivider
|
||||
};
|
||||
}
|
||||
|
||||
function GetLightOption() {
|
||||
const color = colors;
|
||||
return {
|
||||
mode: 'light',
|
||||
heading: color.grey900,
|
||||
paper: color.paper,
|
||||
backgroundDefault: color.paper,
|
||||
background: color.primaryLight,
|
||||
darkTextPrimary: color.grey700,
|
||||
darkTextSecondary: color.grey500,
|
||||
textDark: color.grey900,
|
||||
menuSelected: color.secondaryDark,
|
||||
menuSelectedBack: color.secondaryLight,
|
||||
divider: color.grey200,
|
||||
borderColor: color.grey300,
|
||||
menuButton: color.secondaryLight,
|
||||
menuButtonColor: color.secondaryDark,
|
||||
menuChip: color.primaryLight,
|
||||
headBackgroundColor: color.tableBackground,
|
||||
tableBorderBottom: color.tableBorderBottom
|
||||
};
|
||||
}
|
||||
73
web/berry/src/themes/palette.js
Normal file
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Color intention that you want to used in your theme
|
||||
* @param {JsonObject} theme Theme customization object
|
||||
*/
|
||||
|
||||
export default function themePalette(theme) {
|
||||
return {
|
||||
mode: theme.mode,
|
||||
common: {
|
||||
black: theme.colors?.darkPaper
|
||||
},
|
||||
primary: {
|
||||
light: theme.colors?.primaryLight,
|
||||
main: theme.colors?.primaryMain,
|
||||
dark: theme.colors?.primaryDark,
|
||||
200: theme.colors?.primary200,
|
||||
800: theme.colors?.primary800
|
||||
},
|
||||
secondary: {
|
||||
light: theme.colors?.secondaryLight,
|
||||
main: theme.colors?.secondaryMain,
|
||||
dark: theme.colors?.secondaryDark,
|
||||
200: theme.colors?.secondary200,
|
||||
800: theme.colors?.secondary800
|
||||
},
|
||||
error: {
|
||||
light: theme.colors?.errorLight,
|
||||
main: theme.colors?.errorMain,
|
||||
dark: theme.colors?.errorDark
|
||||
},
|
||||
orange: {
|
||||
light: theme.colors?.orangeLight,
|
||||
main: theme.colors?.orangeMain,
|
||||
dark: theme.colors?.orangeDark
|
||||
},
|
||||
warning: {
|
||||
light: theme.colors?.warningLight,
|
||||
main: theme.colors?.warningMain,
|
||||
dark: theme.colors?.warningDark
|
||||
},
|
||||
success: {
|
||||
light: theme.colors?.successLight,
|
||||
200: theme.colors?.success200,
|
||||
main: theme.colors?.successMain,
|
||||
dark: theme.colors?.successDark
|
||||
},
|
||||
grey: {
|
||||
50: theme.colors?.grey50,
|
||||
100: theme.colors?.grey100,
|
||||
500: theme.darkTextSecondary,
|
||||
600: theme.heading,
|
||||
700: theme.darkTextPrimary,
|
||||
900: theme.textDark
|
||||
},
|
||||
dark: {
|
||||
light: theme.colors?.darkTextPrimary,
|
||||
main: theme.colors?.darkLevel1,
|
||||
dark: theme.colors?.darkLevel2,
|
||||
800: theme.colors?.darkBackground,
|
||||
900: theme.colors?.darkPaper
|
||||
},
|
||||
text: {
|
||||
primary: theme.darkTextPrimary,
|
||||
secondary: theme.darkTextSecondary,
|
||||
dark: theme.textDark,
|
||||
hint: theme.colors?.grey100
|
||||
},
|
||||
background: {
|
||||
paper: theme.paper,
|
||||
default: theme.backgroundDefault
|
||||
}
|
||||
};
|
||||
}
|
||||
150
web/berry/src/themes/typography.js
Normal file
@@ -0,0 +1,150 @@
|
||||
/**
|
||||
* Typography used in theme
|
||||
* @param {JsonObject} theme theme customization object
|
||||
*/
|
||||
|
||||
export default function themeTypography(theme) {
|
||||
return {
|
||||
fontFamily: theme?.customization?.fontFamily,
|
||||
h6: {
|
||||
fontWeight: 500,
|
||||
color: theme.heading,
|
||||
fontSize: '0.75rem'
|
||||
},
|
||||
h5: {
|
||||
fontSize: '0.875rem',
|
||||
color: theme.heading,
|
||||
fontWeight: 500
|
||||
},
|
||||
h4: {
|
||||
fontSize: '1rem',
|
||||
color: theme.heading,
|
||||
fontWeight: 600
|
||||
},
|
||||
h3: {
|
||||
fontSize: '1.25rem',
|
||||
color: theme.heading,
|
||||
fontWeight: 600
|
||||
},
|
||||
h2: {
|
||||
fontSize: '1.5rem',
|
||||
color: theme.heading,
|
||||
fontWeight: 700
|
||||
},
|
||||
h1: {
|
||||
fontSize: '2.125rem',
|
||||
color: theme.heading,
|
||||
fontWeight: 700
|
||||
},
|
||||
subtitle1: {
|
||||
fontSize: '0.875rem',
|
||||
fontWeight: 500,
|
||||
color: theme.textDark
|
||||
},
|
||||
subtitle2: {
|
||||
fontSize: '0.75rem',
|
||||
fontWeight: 400,
|
||||
color: theme.darkTextSecondary
|
||||
},
|
||||
caption: {
|
||||
fontSize: '0.75rem',
|
||||
color: theme.darkTextSecondary,
|
||||
fontWeight: 400
|
||||
},
|
||||
body1: {
|
||||
fontSize: '0.875rem',
|
||||
fontWeight: 400,
|
||||
lineHeight: '1.334em'
|
||||
},
|
||||
body2: {
|
||||
letterSpacing: '0em',
|
||||
fontWeight: 400,
|
||||
lineHeight: '1.5em',
|
||||
color: theme.darkTextPrimary
|
||||
},
|
||||
button: {
|
||||
textTransform: 'capitalize'
|
||||
},
|
||||
customInput: {
|
||||
marginTop: 1,
|
||||
marginBottom: 1,
|
||||
'& > label': {
|
||||
top: 23,
|
||||
left: 0,
|
||||
color: theme.grey500,
|
||||
'&[data-shrink="false"]': {
|
||||
top: 5
|
||||
}
|
||||
},
|
||||
'& > div > input': {
|
||||
padding: '30.5px 14px 11.5px !important'
|
||||
},
|
||||
'& legend': {
|
||||
display: 'none'
|
||||
},
|
||||
'& fieldset': {
|
||||
top: 0
|
||||
}
|
||||
},
|
||||
otherInput: {
|
||||
marginTop: 1,
|
||||
marginBottom: 1
|
||||
},
|
||||
mainContent: {
|
||||
backgroundColor: theme.background,
|
||||
width: '100%',
|
||||
minHeight: 'calc(100vh - 88px)',
|
||||
flexGrow: 1,
|
||||
padding: '20px',
|
||||
marginTop: '88px',
|
||||
marginRight: '20px',
|
||||
borderRadius: `${theme?.customization?.borderRadius}px`
|
||||
},
|
||||
menuCaption: {
|
||||
fontSize: '0.875rem',
|
||||
fontWeight: 500,
|
||||
color: theme.heading,
|
||||
padding: '6px',
|
||||
textTransform: 'capitalize',
|
||||
marginTop: '10px'
|
||||
},
|
||||
subMenuCaption: {
|
||||
fontSize: '0.6875rem',
|
||||
fontWeight: 500,
|
||||
color: theme.darkTextSecondary,
|
||||
textTransform: 'capitalize'
|
||||
},
|
||||
commonAvatar: {
|
||||
cursor: 'pointer',
|
||||
borderRadius: '8px'
|
||||
},
|
||||
smallAvatar: {
|
||||
width: '22px',
|
||||
height: '22px',
|
||||
fontSize: '1rem'
|
||||
},
|
||||
mediumAvatar: {
|
||||
width: '34px',
|
||||
height: '34px',
|
||||
fontSize: '1.2rem'
|
||||
},
|
||||
largeAvatar: {
|
||||
width: '44px',
|
||||
height: '44px',
|
||||
fontSize: '1.5rem'
|
||||
},
|
||||
menuButton: {
|
||||
color: theme.menuButtonColor,
|
||||
background: theme.menuButton
|
||||
},
|
||||
menuChip: {
|
||||
background: theme.menuChip
|
||||
},
|
||||
CardWrapper: {
|
||||
backgroundColor: theme.mode === 'dark' ? theme.colors.darkLevel2 : theme.colors.primaryDark
|
||||
},
|
||||
SubCard: {
|
||||
border: theme.mode === 'dark' ? '1px solid rgba(227, 232, 239, 0.2)' : '1px solid rgb(227, 232, 239)'
|
||||
}
|
||||
};
|
||||
}
|
||||
11
web/berry/src/ui-component/AdminContainer.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import { styled } from '@mui/material/styles';
|
||||
import { Container } from '@mui/material';
|
||||
|
||||
const AdminContainer = styled(Container)(({ theme }) => ({
|
||||
[theme.breakpoints.down('md')]: {
|
||||
paddingLeft: '0px',
|
||||
paddingRight: '0px'
|
||||
}
|
||||
}));
|
||||
|
||||
export default AdminContainer;
|
||||
37
web/berry/src/ui-component/Footer.js
Normal file
@@ -0,0 +1,37 @@
|
||||
// material-ui
|
||||
import { Link, Container, Box } from '@mui/material';
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
// ==============================|| FOOTER - AUTHENTICATION 2 & 3 ||============================== //
|
||||
|
||||
const Footer = () => {
|
||||
const siteInfo = useSelector((state) => state.siteInfo);
|
||||
|
||||
return (
|
||||
<Container sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '64px' }}>
|
||||
<Box sx={{ textAlign: 'center' }}>
|
||||
{siteInfo.footer_html ? (
|
||||
<div className="custom-footer" dangerouslySetInnerHTML={{ __html: siteInfo.footer_html }}></div>
|
||||
) : (
|
||||
<>
|
||||
<Link href="https://github.com/songquanpeng/one-api" target="_blank">
|
||||
{siteInfo.system_name} {process.env.REACT_APP_VERSION}{' '}
|
||||
</Link>
|
||||
由{' '}
|
||||
<Link href="https://github.com/songquanpeng" target="_blank">
|
||||
JustSong
|
||||
</Link>{' '}
|
||||
构建,主题 berry 来自{' '}
|
||||
<Link href="https://github.com/MartialBE" target="_blank">
|
||||
MartialBE
|
||||
</Link>{' '},源代码遵循
|
||||
<Link href="https://opensource.org/licenses/mit-license.php"> MIT 协议</Link>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default Footer;
|
||||
158
web/berry/src/ui-component/Label.js
Normal file
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Label.js
|
||||
*
|
||||
* This file uses code from the Minimal UI project, available at
|
||||
* https://github.com/minimal-ui-kit/material-kit-react/blob/main/src/components/label/label.jsx
|
||||
*
|
||||
* Minimal UI is licensed under the MIT License. A copy of the license is included below:
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2021 Minimal UI (https://minimals.cc/)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
import PropTypes from 'prop-types';
|
||||
import { forwardRef } from 'react';
|
||||
|
||||
import Box from '@mui/material/Box';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { alpha, styled } from '@mui/material/styles';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
const Label = forwardRef(({ children, color = 'default', variant = 'soft', startIcon, endIcon, sx, ...other }, ref) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const iconStyles = {
|
||||
width: 16,
|
||||
height: 16,
|
||||
'& svg, img': { width: 1, height: 1, objectFit: 'cover' }
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledLabel
|
||||
ref={ref}
|
||||
component="span"
|
||||
ownerState={{ color, variant }}
|
||||
sx={{
|
||||
...(startIcon && { pl: 0.75 }),
|
||||
...(endIcon && { pr: 0.75 }),
|
||||
...sx
|
||||
}}
|
||||
theme={theme}
|
||||
{...other}
|
||||
>
|
||||
{startIcon && <Box sx={{ mr: 0.75, ...iconStyles }}> {startIcon} </Box>}
|
||||
|
||||
{children}
|
||||
|
||||
{endIcon && <Box sx={{ ml: 0.75, ...iconStyles }}> {endIcon} </Box>}
|
||||
</StyledLabel>
|
||||
);
|
||||
});
|
||||
|
||||
Label.propTypes = {
|
||||
children: PropTypes.node,
|
||||
endIcon: PropTypes.object,
|
||||
startIcon: PropTypes.object,
|
||||
sx: PropTypes.object,
|
||||
variant: PropTypes.oneOf(['filled', 'outlined', 'ghost', 'soft']),
|
||||
color: PropTypes.oneOf(['default', 'primary', 'secondary', 'info', 'success', 'warning', 'orange', 'error'])
|
||||
};
|
||||
|
||||
export default Label;
|
||||
|
||||
const StyledLabel = styled(Box)(({ theme, ownerState }) => {
|
||||
// const lightMode = theme.palette.mode === 'light';
|
||||
|
||||
const filledVariant = ownerState.variant === 'filled';
|
||||
|
||||
const outlinedVariant = ownerState.variant === 'outlined';
|
||||
|
||||
const softVariant = ownerState.variant === 'soft';
|
||||
|
||||
const ghostVariant = ownerState.variant === 'ghost';
|
||||
|
||||
const defaultStyle = {
|
||||
...(ownerState.color === 'default' && {
|
||||
// FILLED
|
||||
...(filledVariant && {
|
||||
color: theme.palette.grey[300],
|
||||
backgroundColor: theme.palette.text.primary
|
||||
}),
|
||||
// OUTLINED
|
||||
...(outlinedVariant && {
|
||||
color: theme.palette.grey[500],
|
||||
border: `2px solid ${theme.palette.grey[500]}`
|
||||
}),
|
||||
// SOFT
|
||||
...(softVariant && {
|
||||
color: theme.palette.text.secondary,
|
||||
backgroundColor: alpha(theme.palette.grey[500], 0.16)
|
||||
})
|
||||
})
|
||||
};
|
||||
|
||||
const colorStyle = {
|
||||
...(ownerState.color !== 'default' && {
|
||||
// FILLED
|
||||
...(filledVariant && {
|
||||
color: theme.palette.background.paper,
|
||||
backgroundColor: theme.palette[ownerState.color]?.main
|
||||
}),
|
||||
// OUTLINED
|
||||
...(outlinedVariant && {
|
||||
backgroundColor: 'transparent',
|
||||
color: theme.palette[ownerState.color]?.main,
|
||||
border: `2px solid ${theme.palette[ownerState.color]?.main}`
|
||||
}),
|
||||
// SOFT
|
||||
...(softVariant && {
|
||||
color: theme.palette[ownerState.color]['dark'],
|
||||
backgroundColor: alpha(theme.palette[ownerState.color]?.main, 0.16)
|
||||
}),
|
||||
// GHOST
|
||||
...(ghostVariant && {
|
||||
color: theme.palette[ownerState.color]?.main
|
||||
})
|
||||
})
|
||||
};
|
||||
|
||||
return {
|
||||
height: 24,
|
||||
minWidth: 24,
|
||||
lineHeight: 0,
|
||||
borderRadius: 6,
|
||||
cursor: 'default',
|
||||
alignItems: 'center',
|
||||
whiteSpace: 'nowrap',
|
||||
display: 'inline-flex',
|
||||
justifyContent: 'center',
|
||||
// textTransform: 'capitalize',
|
||||
padding: theme.spacing(0, 0.75),
|
||||
fontSize: theme.typography.pxToRem(12),
|
||||
fontWeight: theme.typography.fontWeightBold,
|
||||
transition: theme.transitions.create('all', {
|
||||
duration: theme.transitions.duration.shorter
|
||||
}),
|
||||
...defaultStyle,
|
||||
...colorStyle
|
||||
};
|
||||
});
|
||||
15
web/berry/src/ui-component/Loadable.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Suspense } from 'react';
|
||||
|
||||
// project imports
|
||||
import Loader from './Loader';
|
||||
|
||||
// ==============================|| LOADABLE - LAZY LOADING ||============================== //
|
||||
|
||||
const Loadable = (Component) => (props) =>
|
||||
(
|
||||
<Suspense fallback={<Loader />}>
|
||||
<Component {...props} />
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
export default Loadable;
|
||||
21
web/berry/src/ui-component/Loader.js
Normal file
@@ -0,0 +1,21 @@
|
||||
// material-ui
|
||||
import LinearProgress from '@mui/material/LinearProgress';
|
||||
import { styled } from '@mui/material/styles';
|
||||
|
||||
// styles
|
||||
const LoaderWrapper = styled('div')({
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
zIndex: 1301,
|
||||
width: '100%'
|
||||
});
|
||||
|
||||
// ==============================|| LOADER ||============================== //
|
||||
const Loader = () => (
|
||||
<LoaderWrapper>
|
||||
<LinearProgress color="primary" />
|
||||
</LoaderWrapper>
|
||||
);
|
||||
|
||||
export default Loader;
|
||||
25
web/berry/src/ui-component/Logo.js
Normal file
@@ -0,0 +1,25 @@
|
||||
// material-ui
|
||||
import logoLight from 'assets/images/logo.svg';
|
||||
import logoDark from 'assets/images/logo-white.svg';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
|
||||
/**
|
||||
* if you want to use image instead of <svg> uncomment following.
|
||||
*
|
||||
* import logoDark from 'assets/images/logo-dark.svg';
|
||||
* import logo from 'assets/images/logo.svg';
|
||||
*
|
||||
*/
|
||||
|
||||
// ==============================|| LOGO SVG ||============================== //
|
||||
|
||||
const Logo = () => {
|
||||
const siteInfo = useSelector((state) => state.siteInfo);
|
||||
const theme = useTheme();
|
||||
const logo = theme.palette.mode === 'light' ? logoLight : logoDark;
|
||||
|
||||
return <img src={siteInfo.logo || logo} alt={siteInfo.system_name} height="50" />;
|
||||
};
|
||||
|
||||
export default Logo;
|
||||
31
web/berry/src/ui-component/SvgColor.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { forwardRef } from 'react';
|
||||
|
||||
import Box from '@mui/material/Box';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
const SvgColor = forwardRef(({ src, sx, ...other }, ref) => (
|
||||
<Box
|
||||
component="span"
|
||||
className="svg-color"
|
||||
ref={ref}
|
||||
sx={{
|
||||
width: 24,
|
||||
height: 24,
|
||||
display: 'inline-block',
|
||||
bgcolor: 'currentColor',
|
||||
mask: `url(${src}) no-repeat center / contain`,
|
||||
WebkitMask: `url(${src}) no-repeat center / contain`,
|
||||
...sx
|
||||
}}
|
||||
{...other}
|
||||
/>
|
||||
));
|
||||
|
||||
SvgColor.propTypes = {
|
||||
src: PropTypes.string,
|
||||
sx: PropTypes.object
|
||||
};
|
||||
|
||||
export default SvgColor;
|
||||
37
web/berry/src/ui-component/Switch.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import { styled } from '@mui/material/styles';
|
||||
import Switch from '@mui/material/Switch';
|
||||
|
||||
const TableSwitch = styled(Switch)(({ theme }) => ({
|
||||
padding: 8,
|
||||
'& .MuiSwitch-track': {
|
||||
borderRadius: 22 / 2,
|
||||
'&:before, &:after': {
|
||||
content: '""',
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
transform: 'translateY(-50%)',
|
||||
width: 16,
|
||||
height: 16
|
||||
},
|
||||
'&:before': {
|
||||
backgroundImage: `url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" viewBox="0 0 24 24"><path fill="${encodeURIComponent(
|
||||
theme.palette.getContrastText(theme.palette.primary.main)
|
||||
)}" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"/></svg>')`,
|
||||
left: 12
|
||||
},
|
||||
'&:after': {
|
||||
backgroundImage: `url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" viewBox="0 0 24 24"><path fill="${encodeURIComponent(
|
||||
theme.palette.getContrastText(theme.palette.primary.main)
|
||||
)}" d="M19,13H5V11H19V13Z" /></svg>')`,
|
||||
right: 12
|
||||
}
|
||||
},
|
||||
'& .MuiSwitch-thumb': {
|
||||
boxShadow: 'none',
|
||||
width: 16,
|
||||
height: 16,
|
||||
margin: 2
|
||||
}
|
||||
}));
|
||||
|
||||
export default TableSwitch;
|
||||
47
web/berry/src/ui-component/TableToolBar.js
Normal file
@@ -0,0 +1,47 @@
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Toolbar from '@mui/material/Toolbar';
|
||||
import OutlinedInput from '@mui/material/OutlinedInput';
|
||||
import InputAdornment from '@mui/material/InputAdornment';
|
||||
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { IconSearch } from '@tabler/icons-react';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export default function TableToolBar({ filterName, handleFilterName, placeholder }) {
|
||||
const theme = useTheme();
|
||||
const grey500 = theme.palette.grey[500];
|
||||
|
||||
return (
|
||||
<Toolbar
|
||||
sx={{
|
||||
height: 80,
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
p: (theme) => theme.spacing(0, 1, 0, 3)
|
||||
}}
|
||||
>
|
||||
<OutlinedInput
|
||||
id="keyword"
|
||||
sx={{
|
||||
minWidth: '100%'
|
||||
}}
|
||||
value={filterName}
|
||||
onChange={handleFilterName}
|
||||
placeholder={placeholder}
|
||||
startAdornment={
|
||||
<InputAdornment position="start">
|
||||
<IconSearch stroke={1.5} size="20px" color={grey500} />
|
||||
</InputAdornment>
|
||||
}
|
||||
/>
|
||||
</Toolbar>
|
||||
);
|
||||
}
|
||||
|
||||
TableToolBar.propTypes = {
|
||||
filterName: PropTypes.string,
|
||||
handleFilterName: PropTypes.func,
|
||||
placeholder: PropTypes.string
|
||||
};
|
||||
50
web/berry/src/ui-component/ThemeButton.js
Normal file
@@ -0,0 +1,50 @@
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { SET_THEME } from 'store/actions';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { Avatar, Box, ButtonBase } from '@mui/material';
|
||||
import { IconSun, IconMoon } from '@tabler/icons-react';
|
||||
|
||||
export default function ThemeButton() {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const defaultTheme = useSelector((state) => state.customization.theme);
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
ml: 2,
|
||||
mr: 3,
|
||||
[theme.breakpoints.down('md')]: {
|
||||
mr: 2
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ButtonBase sx={{ borderRadius: '12px' }}>
|
||||
<Avatar
|
||||
variant="rounded"
|
||||
sx={{
|
||||
...theme.typography.commonAvatar,
|
||||
...theme.typography.mediumAvatar,
|
||||
transition: 'all .2s ease-in-out',
|
||||
borderColor: theme.typography.menuChip.background,
|
||||
backgroundColor: theme.typography.menuChip.background,
|
||||
'&[aria-controls="menu-list-grow"],&:hover': {
|
||||
background: theme.palette.secondary.dark,
|
||||
color: theme.palette.secondary.light
|
||||
}
|
||||
}}
|
||||
onClick={() => {
|
||||
let theme = defaultTheme === 'light' ? 'dark' : 'light';
|
||||
dispatch({ type: SET_THEME, theme: theme });
|
||||
localStorage.setItem('theme', theme);
|
||||
}}
|
||||
color="inherit"
|
||||
>
|
||||
{defaultTheme === 'light' ? <IconSun stroke={1.5} size="1.3rem" /> : <IconMoon stroke={1.5} size="1.3rem" />}
|
||||
</Avatar>
|
||||
</ButtonBase>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
55
web/berry/src/ui-component/cards/CardSecondaryAction.js
Normal file
@@ -0,0 +1,55 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { ButtonBase, Link, Tooltip } from '@mui/material';
|
||||
|
||||
// project imports
|
||||
import Avatar from '../extended/Avatar';
|
||||
|
||||
// ==============================|| CARD SECONDARY ACTION ||============================== //
|
||||
|
||||
const CardSecondaryAction = ({ title, link, icon }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Tooltip title={title || 'Reference'} placement="left">
|
||||
<ButtonBase disableRipple>
|
||||
{!icon && (
|
||||
<Avatar component={Link} href={link} target="_blank" alt="MUI Logo" size="badge" color="primary" outline>
|
||||
<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clipPath="url(#clip0)">
|
||||
<path d="M100 260.9V131L212.5 195.95V239.25L137.5 195.95V282.55L100 260.9Z" fill={theme.palette.primary[800]} />
|
||||
<path
|
||||
d="M212.5 195.95L325 131V260.9L250 304.2L212.5 282.55L287.5 239.25V195.95L212.5 239.25V195.95Z"
|
||||
fill={theme.palette.primary.main}
|
||||
/>
|
||||
<path d="M212.5 282.55V325.85L287.5 369.15V325.85L212.5 282.55Z" fill={theme.palette.primary[800]} />
|
||||
<path
|
||||
d="M287.5 369.15L400 304.2V217.6L362.5 239.25V282.55L287.5 325.85V369.15ZM362.5 195.95V152.65L400 131V174.3L362.5 195.95Z"
|
||||
fill={theme.palette.primary.main}
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0">
|
||||
<rect width="300" height="238.3" fill="white" transform="translate(100 131)" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
</Avatar>
|
||||
)}
|
||||
{icon && (
|
||||
<Avatar component={Link} href={link} target="_blank" size="badge" color="primary" outline>
|
||||
{icon}
|
||||
</Avatar>
|
||||
)}
|
||||
</ButtonBase>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
CardSecondaryAction.propTypes = {
|
||||
icon: PropTypes.node,
|
||||
link: PropTypes.string,
|
||||
title: PropTypes.string
|
||||
};
|
||||
|
||||
export default CardSecondaryAction;
|
||||
80
web/berry/src/ui-component/cards/MainCard.js
Normal file
@@ -0,0 +1,80 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { forwardRef } from 'react';
|
||||
|
||||
// material-ui
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { Card, CardContent, CardHeader, Divider, Typography } from '@mui/material';
|
||||
|
||||
// constant
|
||||
const headerSX = {
|
||||
'& .MuiCardHeader-action': { mr: 0 }
|
||||
};
|
||||
|
||||
// ==============================|| CUSTOM MAIN CARD ||============================== //
|
||||
|
||||
const MainCard = forwardRef(
|
||||
(
|
||||
{
|
||||
border = false,
|
||||
boxShadow,
|
||||
children,
|
||||
content = true,
|
||||
contentClass = '',
|
||||
contentSX = {},
|
||||
darkTitle,
|
||||
secondary,
|
||||
shadow,
|
||||
sx = {},
|
||||
title,
|
||||
...others
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Card
|
||||
ref={ref}
|
||||
{...others}
|
||||
sx={{
|
||||
border: border ? '1px solid' : 'none',
|
||||
borderColor: theme.palette.primary[200] + 25,
|
||||
':hover': {
|
||||
boxShadow: boxShadow ? shadow || '0 2px 14px 0 rgb(32 40 45 / 8%)' : 'inherit'
|
||||
},
|
||||
...sx
|
||||
}}
|
||||
>
|
||||
{/* card header and action */}
|
||||
{title && <CardHeader sx={headerSX} title={darkTitle ? <Typography variant="h3">{title}</Typography> : title} action={secondary} />}
|
||||
|
||||
{/* content & header divider */}
|
||||
{title && <Divider />}
|
||||
|
||||
{/* card content */}
|
||||
{content && (
|
||||
<CardContent sx={contentSX} className={contentClass}>
|
||||
{children}
|
||||
</CardContent>
|
||||
)}
|
||||
{!content && children}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
MainCard.propTypes = {
|
||||
border: PropTypes.bool,
|
||||
boxShadow: PropTypes.bool,
|
||||
children: PropTypes.node,
|
||||
content: PropTypes.bool,
|
||||
contentClass: PropTypes.string,
|
||||
contentSX: PropTypes.object,
|
||||
darkTitle: PropTypes.bool,
|
||||
secondary: PropTypes.oneOfType([PropTypes.node, PropTypes.string, PropTypes.object]),
|
||||
shadow: PropTypes.string,
|
||||
sx: PropTypes.object,
|
||||
title: PropTypes.oneOfType([PropTypes.node, PropTypes.string, PropTypes.object])
|
||||
};
|
||||
|
||||
export default MainCard;
|
||||
32
web/berry/src/ui-component/cards/Skeleton/EarningCard.js
Normal file
@@ -0,0 +1,32 @@
|
||||
// material-ui
|
||||
import { Card, CardContent, Grid } from '@mui/material';
|
||||
import Skeleton from '@mui/material/Skeleton';
|
||||
|
||||
// ==============================|| SKELETON - EARNING CARD ||============================== //
|
||||
|
||||
const EarningCard = () => (
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Grid container direction="column">
|
||||
<Grid item>
|
||||
<Grid container justifyContent="space-between">
|
||||
<Grid item>
|
||||
<Skeleton variant="rectangular" width={44} height={44} />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Skeleton variant="rectangular" width={34} height={34} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Skeleton variant="rectangular" sx={{ my: 2 }} height={40} />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Skeleton variant="rectangular" height={30} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
|
||||
export default EarningCard;
|
||||
@@ -0,0 +1,8 @@
|
||||
// material-ui
|
||||
import Skeleton from '@mui/material/Skeleton';
|
||||
|
||||
// ==============================|| SKELETON IMAGE CARD ||============================== //
|
||||
|
||||
const ImagePlaceholder = ({ ...others }) => <Skeleton variant="rectangular" {...others} animation="wave" />;
|
||||
|
||||
export default ImagePlaceholder;
|
||||
155
web/berry/src/ui-component/cards/Skeleton/PopularCard.js
Normal file
@@ -0,0 +1,155 @@
|
||||
// material-ui
|
||||
import { Card, CardContent, Grid } from '@mui/material';
|
||||
import Skeleton from '@mui/material/Skeleton';
|
||||
|
||||
// project imports
|
||||
import { gridSpacing } from 'store/constant';
|
||||
|
||||
// ==============================|| SKELETON - POPULAR CARD ||============================== //
|
||||
|
||||
const PopularCard = () => (
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Grid container spacing={gridSpacing}>
|
||||
<Grid item xs={12}>
|
||||
<Grid container alignItems="center" justifyContent="space-between" spacing={gridSpacing}>
|
||||
<Grid item xs zeroMinWidth>
|
||||
<Skeleton variant="rectangular" height={20} />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Skeleton variant="rectangular" height={20} width={20} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Skeleton variant="rectangular" height={150} />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Grid container spacing={1}>
|
||||
<Grid item xs={12}>
|
||||
<Grid container alignItems="center" spacing={gridSpacing} justifyContent="space-between">
|
||||
<Grid item xs={6}>
|
||||
<Skeleton variant="rectangular" height={20} />
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Grid container alignItems="center" spacing={gridSpacing} justifyContent="space-between">
|
||||
<Grid item xs zeroMinWidth>
|
||||
<Skeleton variant="rectangular" height={20} />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Skeleton variant="rectangular" height={16} width={16} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Skeleton variant="rectangular" height={20} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Grid container spacing={1}>
|
||||
<Grid item xs={12}>
|
||||
<Grid container alignItems="center" spacing={gridSpacing} justifyContent="space-between">
|
||||
<Grid item xs={6}>
|
||||
<Skeleton variant="rectangular" height={20} />
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Grid container alignItems="center" spacing={gridSpacing} justifyContent="space-between">
|
||||
<Grid item xs zeroMinWidth>
|
||||
<Skeleton variant="rectangular" height={20} />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Skeleton variant="rectangular" height={16} width={16} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Skeleton variant="rectangular" height={20} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Grid container spacing={1}>
|
||||
<Grid item xs={12}>
|
||||
<Grid container alignItems="center" spacing={gridSpacing} justifyContent="space-between">
|
||||
<Grid item xs={6}>
|
||||
<Skeleton variant="rectangular" height={20} />
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Grid container alignItems="center" spacing={gridSpacing} justifyContent="space-between">
|
||||
<Grid item xs zeroMinWidth>
|
||||
<Skeleton variant="rectangular" height={20} />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Skeleton variant="rectangular" height={16} width={16} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Skeleton variant="rectangular" height={20} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Grid container spacing={1}>
|
||||
<Grid item xs={12}>
|
||||
<Grid container alignItems="center" spacing={gridSpacing} justifyContent="space-between">
|
||||
<Grid item xs={6}>
|
||||
<Skeleton variant="rectangular" height={20} />
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Grid container alignItems="center" spacing={gridSpacing} justifyContent="space-between">
|
||||
<Grid item xs zeroMinWidth>
|
||||
<Skeleton variant="rectangular" height={20} />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Skeleton variant="rectangular" height={16} width={16} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Skeleton variant="rectangular" height={20} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Grid container spacing={1}>
|
||||
<Grid item xs={12}>
|
||||
<Grid container alignItems="center" spacing={gridSpacing} justifyContent="space-between">
|
||||
<Grid item xs={6}>
|
||||
<Skeleton variant="rectangular" height={20} />
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Grid container alignItems="center" spacing={gridSpacing} justifyContent="space-between">
|
||||
<Grid item xs zeroMinWidth>
|
||||
<Skeleton variant="rectangular" height={20} />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Skeleton variant="rectangular" height={16} width={16} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Skeleton variant="rectangular" height={20} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</CardContent>
|
||||
<CardContent sx={{ p: 1.25, display: 'flex', pt: 0, justifyContent: 'center' }}>
|
||||
<Skeleton variant="rectangular" height={25} width={75} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
|
||||
export default PopularCard;
|
||||
@@ -0,0 +1,44 @@
|
||||
// material-ui
|
||||
import { CardContent, Grid, Skeleton, Stack } from '@mui/material';
|
||||
|
||||
// project import
|
||||
import MainCard from '../MainCard';
|
||||
|
||||
// ===========================|| SKELETON TOTAL GROWTH BAR CHART ||=========================== //
|
||||
|
||||
const ProductPlaceholder = () => (
|
||||
<MainCard content={false} boxShadow>
|
||||
<Skeleton variant="rectangular" height={220} />
|
||||
<CardContent sx={{ p: 2 }}>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<Skeleton variant="rectangular" height={20} />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Skeleton variant="rectangular" height={45} />
|
||||
</Grid>
|
||||
<Grid item xs={12} sx={{ pt: '8px !important' }}>
|
||||
<Stack direction="row" alignItems="center" spacing={1}>
|
||||
<Skeleton variant="rectangular" height={20} width={90} />
|
||||
<Skeleton variant="rectangular" height={20} width={38} />
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="center">
|
||||
<Grid container spacing={1}>
|
||||
<Grid item>
|
||||
<Skeleton variant="rectangular" height={20} width={40} />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Skeleton variant="rectangular" height={17} width={20} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Skeleton variant="rectangular" height={32} width={47} />
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</CardContent>
|
||||
</MainCard>
|
||||
);
|
||||
|
||||
export default ProductPlaceholder;
|
||||
@@ -0,0 +1,39 @@
|
||||
// material-ui
|
||||
import { Card, CardContent, Grid } from '@mui/material';
|
||||
import Skeleton from '@mui/material/Skeleton';
|
||||
|
||||
// project imports
|
||||
import { gridSpacing } from 'store/constant';
|
||||
|
||||
// ==============================|| SKELETON TOTAL GROWTH BAR CHART ||============================== //
|
||||
|
||||
const TotalGrowthBarChart = () => (
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Grid container spacing={gridSpacing}>
|
||||
<Grid item xs={12}>
|
||||
<Grid container alignItems="center" justifyContent="space-between" spacing={gridSpacing}>
|
||||
<Grid item xs zeroMinWidth>
|
||||
<Grid container spacing={1}>
|
||||
<Grid item xs={12}>
|
||||
<Skeleton variant="text" />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Skeleton variant="rectangular" height={20} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Skeleton variant="rectangular" height={50} width={80} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Skeleton variant="rectangular" height={530} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
|
||||
export default TotalGrowthBarChart;
|
||||
19
web/berry/src/ui-component/cards/Skeleton/TotalIncomeCard.js
Normal file
@@ -0,0 +1,19 @@
|
||||
// material-ui
|
||||
import { Card, List, ListItem, ListItemAvatar, ListItemText, Skeleton } from '@mui/material';
|
||||
|
||||
// ==============================|| SKELETON - TOTAL INCOME DARK/LIGHT CARD ||============================== //
|
||||
|
||||
const TotalIncomeCard = () => (
|
||||
<Card sx={{ p: 2 }}>
|
||||
<List sx={{ py: 0 }}>
|
||||
<ListItem alignItems="center" disableGutters sx={{ py: 0 }}>
|
||||
<ListItemAvatar>
|
||||
<Skeleton variant="rectangular" width={44} height={44} />
|
||||
</ListItemAvatar>
|
||||
<ListItemText sx={{ py: 0 }} primary={<Skeleton variant="rectangular" height={20} />} secondary={<Skeleton variant="text" />} />
|
||||
</ListItem>
|
||||
</List>
|
||||
</Card>
|
||||
);
|
||||
|
||||
export default TotalIncomeCard;
|
||||
72
web/berry/src/ui-component/cards/SubCard.js
Normal file
@@ -0,0 +1,72 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { forwardRef } from 'react';
|
||||
|
||||
// material-ui
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { Card, CardContent, CardHeader, Divider, Typography } from '@mui/material';
|
||||
|
||||
// ==============================|| CUSTOM SUB CARD ||============================== //
|
||||
|
||||
const SubCard = forwardRef(
|
||||
({ children, content, contentClass, darkTitle, secondary, sx = {}, contentSX = {}, title, subTitle, ...others }, ref) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Card
|
||||
ref={ref}
|
||||
sx={{
|
||||
border: theme.typography.SubCard.border,
|
||||
':hover': {
|
||||
boxShadow: '0 2px 14px 0 rgb(32 40 45 / 8%)'
|
||||
},
|
||||
...sx
|
||||
}}
|
||||
{...others}
|
||||
>
|
||||
{/* card header and action */}
|
||||
{!darkTitle && title && (
|
||||
<CardHeader sx={{ p: 2.5 }} title={<Typography variant="h5">{title}</Typography>} action={secondary} subheader={subTitle} />
|
||||
)}
|
||||
{darkTitle && title && (
|
||||
<CardHeader sx={{ p: 2.5 }} title={<Typography variant="h4">{title}</Typography>} action={secondary} subheader={subTitle} />
|
||||
)}
|
||||
|
||||
{/* content & header divider */}
|
||||
{title && (
|
||||
<Divider
|
||||
sx={{
|
||||
opacity: 1
|
||||
// borderColor: theme.palette.primary.light
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* card content */}
|
||||
{content && (
|
||||
<CardContent sx={{ p: 2.5, ...contentSX }} className={contentClass || ''}>
|
||||
{children}
|
||||
</CardContent>
|
||||
)}
|
||||
{!content && children}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
SubCard.propTypes = {
|
||||
children: PropTypes.node,
|
||||
content: PropTypes.bool,
|
||||
contentClass: PropTypes.string,
|
||||
darkTitle: PropTypes.bool,
|
||||
secondary: PropTypes.oneOfType([PropTypes.node, PropTypes.string, PropTypes.object]),
|
||||
sx: PropTypes.object,
|
||||
contentSX: PropTypes.object,
|
||||
title: PropTypes.oneOfType([PropTypes.node, PropTypes.string, PropTypes.object]),
|
||||
subTitle: PropTypes.oneOfType([PropTypes.node, PropTypes.string, PropTypes.object])
|
||||
};
|
||||
|
||||
SubCard.defaultProps = {
|
||||
content: true
|
||||
};
|
||||
|
||||
export default SubCard;
|
||||
121
web/berry/src/ui-component/cards/UserCard.js
Normal file
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* UserCard.js
|
||||
*
|
||||
* This file uses code from the Minimal UI project, available at
|
||||
* https://github.com/minimal-ui-kit/material-kit-react/blob/main/src/sections/blog/post-card.jsx
|
||||
*
|
||||
* Minimal UI is licensed under the MIT License. A copy of the license is included below:
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2021 Minimal UI (https://minimals.cc/)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
import { Box, Avatar } from '@mui/material';
|
||||
import { alpha } from '@mui/material/styles';
|
||||
import Card from '@mui/material/Card';
|
||||
import shapeAvatar from 'assets/images/icons/shape-avatar.svg';
|
||||
import coverAvatar from 'assets/images/invite/cover.jpg';
|
||||
import userAvatar from 'assets/images/users/user-round.svg';
|
||||
import SvgColor from 'ui-component/SvgColor';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export default function UserCard({ children }) {
|
||||
const renderShape = (
|
||||
<SvgColor
|
||||
color="paper"
|
||||
src={shapeAvatar}
|
||||
sx={{
|
||||
width: '100%',
|
||||
height: 62,
|
||||
zIndex: 10,
|
||||
bottom: -26,
|
||||
position: 'absolute',
|
||||
color: 'background.paper'
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const renderAvatar = (
|
||||
<Avatar
|
||||
src={userAvatar}
|
||||
sx={{
|
||||
zIndex: 11,
|
||||
width: 64,
|
||||
height: 64,
|
||||
position: 'absolute',
|
||||
alignItems: 'center',
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: (theme) => theme.spacing(-4)
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const renderCover = (
|
||||
<Box
|
||||
component="img"
|
||||
src={coverAvatar}
|
||||
sx={{
|
||||
top: 0,
|
||||
width: 1,
|
||||
height: 1,
|
||||
objectFit: 'cover',
|
||||
position: 'absolute'
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<Box
|
||||
sx={{
|
||||
position: 'relative',
|
||||
'&:after': {
|
||||
top: 0,
|
||||
content: "''",
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
position: 'absolute',
|
||||
bgcolor: (theme) => alpha(theme.palette.primary.main, 0.42)
|
||||
},
|
||||
pt: {
|
||||
xs: 'calc(100% / 3)',
|
||||
sm: 'calc(100% / 4.66)'
|
||||
}
|
||||
}}
|
||||
>
|
||||
{renderShape}
|
||||
{renderAvatar}
|
||||
{renderCover}
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
p: (theme) => theme.spacing(4, 3, 3, 3)
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
92
web/berry/src/ui-component/extended/AnimateButton.js
Normal file
@@ -0,0 +1,92 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { forwardRef } from 'react';
|
||||
// third-party
|
||||
import { motion, useCycle } from 'framer-motion';
|
||||
|
||||
// ==============================|| ANIMATION BUTTON ||============================== //
|
||||
|
||||
const AnimateButton = forwardRef(({ children, type, direction, offset, scale }, ref) => {
|
||||
let offset1;
|
||||
let offset2;
|
||||
switch (direction) {
|
||||
case 'up':
|
||||
case 'left':
|
||||
offset1 = offset;
|
||||
offset2 = 0;
|
||||
break;
|
||||
case 'right':
|
||||
case 'down':
|
||||
default:
|
||||
offset1 = 0;
|
||||
offset2 = offset;
|
||||
break;
|
||||
}
|
||||
|
||||
const [x, cycleX] = useCycle(offset1, offset2);
|
||||
const [y, cycleY] = useCycle(offset1, offset2);
|
||||
|
||||
switch (type) {
|
||||
case 'rotate':
|
||||
return (
|
||||
<motion.div
|
||||
ref={ref}
|
||||
animate={{ rotate: 360 }}
|
||||
transition={{
|
||||
repeat: Infinity,
|
||||
repeatType: 'loop',
|
||||
duration: 2,
|
||||
repeatDelay: 0
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
);
|
||||
case 'slide':
|
||||
if (direction === 'up' || direction === 'down') {
|
||||
return (
|
||||
<motion.div ref={ref} animate={{ y: y !== undefined ? y : '' }} onHoverEnd={() => cycleY()} onHoverStart={() => cycleY()}>
|
||||
{children}
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<motion.div ref={ref} animate={{ x: x !== undefined ? x : '' }} onHoverEnd={() => cycleX()} onHoverStart={() => cycleX()}>
|
||||
{children}
|
||||
</motion.div>
|
||||
);
|
||||
|
||||
case 'scale':
|
||||
default:
|
||||
if (typeof scale === 'number') {
|
||||
scale = {
|
||||
hover: scale,
|
||||
tap: scale
|
||||
};
|
||||
}
|
||||
return (
|
||||
<motion.div ref={ref} whileHover={{ scale: scale?.hover }} whileTap={{ scale: scale?.tap }}>
|
||||
{children}
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
AnimateButton.propTypes = {
|
||||
children: PropTypes.node,
|
||||
offset: PropTypes.number,
|
||||
type: PropTypes.oneOf(['slide', 'scale', 'rotate']),
|
||||
direction: PropTypes.oneOf(['up', 'down', 'left', 'right']),
|
||||
scale: PropTypes.oneOfType([PropTypes.number, PropTypes.object])
|
||||
};
|
||||
|
||||
AnimateButton.defaultProps = {
|
||||
type: 'scale',
|
||||
offset: 10,
|
||||
direction: 'right',
|
||||
scale: {
|
||||
hover: 1,
|
||||
tap: 0.9
|
||||
}
|
||||
};
|
||||
|
||||
export default AnimateButton;
|
||||
72
web/berry/src/ui-component/extended/Avatar.js
Normal file
@@ -0,0 +1,72 @@
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
// material-ui
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import MuiAvatar from '@mui/material/Avatar';
|
||||
|
||||
// ==============================|| AVATAR ||============================== //
|
||||
|
||||
const Avatar = ({ color, outline, size, sx, ...others }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const colorSX = color && !outline && { color: theme.palette.background.paper, bgcolor: `${color}.main` };
|
||||
const outlineSX = outline && {
|
||||
color: color ? `${color}.main` : `primary.main`,
|
||||
bgcolor: theme.palette.background.paper,
|
||||
border: '2px solid',
|
||||
borderColor: color ? `${color}.main` : `primary.main`
|
||||
};
|
||||
let sizeSX = {};
|
||||
switch (size) {
|
||||
case 'badge':
|
||||
sizeSX = {
|
||||
width: theme.spacing(3.5),
|
||||
height: theme.spacing(3.5)
|
||||
};
|
||||
break;
|
||||
case 'xs':
|
||||
sizeSX = {
|
||||
width: theme.spacing(4.25),
|
||||
height: theme.spacing(4.25)
|
||||
};
|
||||
break;
|
||||
case 'sm':
|
||||
sizeSX = {
|
||||
width: theme.spacing(5),
|
||||
height: theme.spacing(5)
|
||||
};
|
||||
break;
|
||||
case 'lg':
|
||||
sizeSX = {
|
||||
width: theme.spacing(9),
|
||||
height: theme.spacing(9)
|
||||
};
|
||||
break;
|
||||
case 'xl':
|
||||
sizeSX = {
|
||||
width: theme.spacing(10.25),
|
||||
height: theme.spacing(10.25)
|
||||
};
|
||||
break;
|
||||
case 'md':
|
||||
sizeSX = {
|
||||
width: theme.spacing(7.5),
|
||||
height: theme.spacing(7.5)
|
||||
};
|
||||
break;
|
||||
default:
|
||||
sizeSX = {};
|
||||
}
|
||||
|
||||
return <MuiAvatar sx={{ ...colorSX, ...outlineSX, ...sizeSX, ...sx }} {...others} />;
|
||||
};
|
||||
|
||||
Avatar.propTypes = {
|
||||
className: PropTypes.string,
|
||||
color: PropTypes.string,
|
||||
outline: PropTypes.bool,
|
||||
size: PropTypes.string,
|
||||
sx: PropTypes.object
|
||||
};
|
||||
|
||||
export default Avatar;
|
||||
187
web/berry/src/ui-component/extended/Breadcrumbs.js
Normal file
@@ -0,0 +1,187 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
// material-ui
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { Box, Card, Divider, Grid, Typography } from '@mui/material';
|
||||
import MuiBreadcrumbs from '@mui/material/Breadcrumbs';
|
||||
|
||||
// project imports
|
||||
import config from 'config';
|
||||
import { gridSpacing } from 'store/constant';
|
||||
|
||||
// assets
|
||||
import { IconTallymark1 } from '@tabler/icons-react';
|
||||
import AccountTreeTwoToneIcon from '@mui/icons-material/AccountTreeTwoTone';
|
||||
import HomeIcon from '@mui/icons-material/Home';
|
||||
import HomeTwoToneIcon from '@mui/icons-material/HomeTwoTone';
|
||||
|
||||
const linkSX = {
|
||||
display: 'flex',
|
||||
color: 'grey.900',
|
||||
textDecoration: 'none',
|
||||
alignContent: 'center',
|
||||
alignItems: 'center'
|
||||
};
|
||||
|
||||
// ==============================|| BREADCRUMBS ||============================== //
|
||||
|
||||
const Breadcrumbs = ({ card, divider, icon, icons, maxItems, navigation, rightAlign, separator, title, titleBottom, ...others }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const iconStyle = {
|
||||
marginRight: theme.spacing(0.75),
|
||||
marginTop: `-${theme.spacing(0.25)}`,
|
||||
width: '1rem',
|
||||
height: '1rem',
|
||||
color: theme.palette.secondary.main
|
||||
};
|
||||
|
||||
const [main, setMain] = useState();
|
||||
const [item, setItem] = useState();
|
||||
|
||||
// set active item state
|
||||
const getCollapse = (menu) => {
|
||||
if (menu.children) {
|
||||
menu.children.filter((collapse) => {
|
||||
if (collapse.type && collapse.type === 'collapse') {
|
||||
getCollapse(collapse);
|
||||
} else if (collapse.type && collapse.type === 'item') {
|
||||
if (document.location.pathname === config.basename + collapse.url) {
|
||||
setMain(menu);
|
||||
setItem(collapse);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
navigation?.items?.map((menu) => {
|
||||
if (menu.type && menu.type === 'group') {
|
||||
getCollapse(menu);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
// item separator
|
||||
const SeparatorIcon = separator;
|
||||
const separatorIcon = separator ? <SeparatorIcon stroke={1.5} size="1rem" /> : <IconTallymark1 stroke={1.5} size="1rem" />;
|
||||
|
||||
let mainContent;
|
||||
let itemContent;
|
||||
let breadcrumbContent = <Typography />;
|
||||
let itemTitle = '';
|
||||
let CollapseIcon;
|
||||
let ItemIcon;
|
||||
|
||||
// collapse item
|
||||
if (main && main.type === 'collapse') {
|
||||
CollapseIcon = main.icon ? main.icon : AccountTreeTwoToneIcon;
|
||||
mainContent = (
|
||||
<Typography component={Link} to="#" variant="subtitle1" sx={linkSX}>
|
||||
{icons && <CollapseIcon style={iconStyle} />}
|
||||
{main.title}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
// items
|
||||
if (item && item.type === 'item') {
|
||||
itemTitle = item.title;
|
||||
|
||||
ItemIcon = item.icon ? item.icon : AccountTreeTwoToneIcon;
|
||||
itemContent = (
|
||||
<Typography
|
||||
variant="subtitle1"
|
||||
sx={{
|
||||
display: 'flex',
|
||||
textDecoration: 'none',
|
||||
alignContent: 'center',
|
||||
alignItems: 'center',
|
||||
color: 'grey.500'
|
||||
}}
|
||||
>
|
||||
{icons && <ItemIcon style={iconStyle} />}
|
||||
{itemTitle}
|
||||
</Typography>
|
||||
);
|
||||
|
||||
// main
|
||||
if (item.breadcrumbs !== false) {
|
||||
breadcrumbContent = (
|
||||
<Card
|
||||
sx={{
|
||||
marginBottom: card === false ? 0 : theme.spacing(gridSpacing),
|
||||
border: card === false ? 'none' : '1px solid',
|
||||
borderColor: theme.palette.primary[200] + 75,
|
||||
background: card === false ? 'transparent' : theme.palette.background.default
|
||||
}}
|
||||
{...others}
|
||||
>
|
||||
<Box sx={{ p: 2, pl: card === false ? 0 : 2 }}>
|
||||
<Grid
|
||||
container
|
||||
direction={rightAlign ? 'row' : 'column'}
|
||||
justifyContent={rightAlign ? 'space-between' : 'flex-start'}
|
||||
alignItems={rightAlign ? 'center' : 'flex-start'}
|
||||
spacing={1}
|
||||
>
|
||||
{title && !titleBottom && (
|
||||
<Grid item>
|
||||
<Typography variant="h3" sx={{ fontWeight: 500 }}>
|
||||
{item.title}
|
||||
</Typography>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item>
|
||||
<MuiBreadcrumbs
|
||||
sx={{ '& .MuiBreadcrumbs-separator': { width: 16, ml: 1.25, mr: 1.25 } }}
|
||||
aria-label="breadcrumb"
|
||||
maxItems={maxItems || 8}
|
||||
separator={separatorIcon}
|
||||
>
|
||||
<Typography component={Link} to="/" color="inherit" variant="subtitle1" sx={linkSX}>
|
||||
{icons && <HomeTwoToneIcon sx={iconStyle} />}
|
||||
{icon && <HomeIcon sx={{ ...iconStyle, mr: 0 }} />}
|
||||
{!icon && 'Dashboard'}
|
||||
</Typography>
|
||||
{mainContent}
|
||||
{itemContent}
|
||||
</MuiBreadcrumbs>
|
||||
</Grid>
|
||||
{title && titleBottom && (
|
||||
<Grid item>
|
||||
<Typography variant="h3" sx={{ fontWeight: 500 }}>
|
||||
{item.title}
|
||||
</Typography>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</Box>
|
||||
{card === false && divider !== false && <Divider sx={{ borderColor: theme.palette.primary.main, mb: gridSpacing }} />}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return breadcrumbContent;
|
||||
};
|
||||
|
||||
Breadcrumbs.propTypes = {
|
||||
card: PropTypes.bool,
|
||||
divider: PropTypes.bool,
|
||||
icon: PropTypes.bool,
|
||||
icons: PropTypes.bool,
|
||||
maxItems: PropTypes.number,
|
||||
navigation: PropTypes.object,
|
||||
rightAlign: PropTypes.bool,
|
||||
separator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
|
||||
title: PropTypes.bool,
|
||||
titleBottom: PropTypes.bool
|
||||
};
|
||||
|
||||
export default Breadcrumbs;
|
||||