├── .eslintrc
├── .gitignore
├── .prettierrc
├── CHANGELOG.md
├── README.md
├── jsconfig.json
├── package-lock.json
├── package.json
├── public
├── _redirects
├── favicon
│ ├── android-chrome-192x192.png
│ ├── android-chrome-512x512.png
│ ├── apple-touch-icon.png
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ └── favicon.ico
├── index.html
├── manifest.json
└── static
│ ├── icons
│ ├── ic_flag_de.svg
│ ├── ic_flag_en.svg
│ ├── ic_flag_fr.svg
│ ├── ic_notification_chat.svg
│ ├── ic_notification_mail.svg
│ ├── ic_notification_package.svg
│ ├── ic_notification_shipping.svg
│ └── shape-avatar.svg
│ ├── illustrations
│ ├── BTC_Logo.svg
│ ├── illustration_404.svg
│ └── illustration_BTC.png
│ ├── log.svg
│ └── logo.svg
├── src
├── App.js
├── assets
│ └── avatar_default.jpg
├── components
│ ├── @material-extend
│ │ ├── MHidden.js
│ │ └── index.js
│ ├── ColorManyPicker.js
│ ├── ColorPreview.js
│ ├── Label.js
│ ├── Logo.js
│ ├── MenuPopover.js
│ ├── NavSection.js
│ ├── Page.js
│ ├── ScrollToTop.js
│ ├── Scrollbar.js
│ ├── SearchNotFound.js
│ ├── SvgIconStyle.js
│ ├── _dashboard
│ │ ├── SummaryBox.jsx
│ │ ├── app
│ │ │ ├── AppCurrentSubject.js
│ │ │ ├── AppTasks.js
│ │ │ ├── AppTrafficBySite.js
│ │ │ ├── LastOrders.js
│ │ │ ├── PerformanceOverview.js
│ │ │ ├── PositionsRepartition.js
│ │ │ ├── TopPerformers.js
│ │ │ └── index.js
│ │ └── order
│ │ │ ├── OrderListHead.js
│ │ │ ├── OrderListToolbar.js
│ │ │ ├── OrderMoreMenu.js
│ │ │ └── index.js
│ ├── animate
│ │ ├── MotionContainer.js
│ │ ├── index.js
│ │ └── variants
│ │ │ ├── Wrap.js
│ │ │ ├── bounce
│ │ │ ├── BounceIn.js
│ │ │ ├── BounceOut.js
│ │ │ └── index.js
│ │ │ └── index.js
│ └── charts
│ │ ├── BaseOptionChart.js
│ │ └── index.js
├── config
│ └── api-keys.example.js
├── index.js
├── layouts
│ ├── LogoOnlyLayout.js
│ └── dashboard
│ │ ├── AccountPopover.js
│ │ ├── BTCprice.jsx
│ │ ├── DashboardNavbar.js
│ │ ├── DashboardSidebar.js
│ │ ├── SidebarConfig.js
│ │ ├── Summary.jsx
│ │ └── index.js
├── pages
│ ├── DashboardApp.js
│ ├── OrderHistory.js
│ └── Page404.js
├── recoil
│ └── atoms
│ │ ├── accountAtom.js
│ │ ├── incomesAtom.js
│ │ ├── index.js
│ │ ├── tradesAtom.js
│ │ └── updateTimeAtom.js
├── reportWebVitals.js
├── routes.js
├── serviceWorker.js
├── services
│ ├── accountServices.js
│ ├── incomeServices.js
│ ├── tradesServices.js
│ └── utils.js
├── setupTests.js
├── theme
│ ├── breakpoints.js
│ ├── globalStyles.js
│ ├── index.js
│ ├── overrides
│ │ ├── Autocomplete.js
│ │ ├── Backdrop.js
│ │ ├── Button.js
│ │ ├── Card.js
│ │ ├── IconButton.js
│ │ ├── Input.js
│ │ ├── Lists.js
│ │ ├── Paper.js
│ │ ├── Tooltip.js
│ │ ├── Typography.js
│ │ └── index.js
│ ├── palette.js
│ ├── shadows.js
│ ├── shape.js
│ └── typography.js
└── utils
│ ├── formatNumber.js
│ ├── formatTime.js
│ └── functions.js
└── yarn.lock
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "env": {
4 | "browser": true,
5 | "node": true
6 | },
7 | "extends": [
8 | "airbnb",
9 | "prettier",
10 | "plugin:jsx-a11y/recommended",
11 | "plugin:react-hooks/recommended"
12 | ],
13 | "plugins": ["prettier", "react", "react-hooks"],
14 | "parser": "@babel/eslint-parser",
15 | "parserOptions": {
16 | "ecmaVersion": 8,
17 | "requireConfigFile": false,
18 | "ecmaFeatures": {
19 | "experimentalObjectRestSpread": true,
20 | "impliedStrict": true
21 | }
22 | },
23 | "rules": {
24 | "import": 0,
25 | "max-len": 0,
26 | "no-alert": 0,
27 | "no-shadow": 0,
28 | "no-console": 0,
29 | "comma-dangle": 0,
30 | "import/no-cycle": 0,
31 | "react/prop-types": 0,
32 | "no-return-assign": 0,
33 | "consistent-return": 1,
34 | "no-param-reassign": 0,
35 | "react/display-name": 0,
36 | "no-use-before-define": 0,
37 | "no-underscore-dangle": 0,
38 | "react/button-has-type": 1,
39 | "react/no-children-prop": 0,
40 | "react/forbid-prop-types": 0,
41 | "jsx-a11y/anchor-is-valid": 0,
42 | "react/react-in-jsx-scope": 0,
43 | "react/no-array-index-key": 0,
44 | "react/no-unused-prop-types": 1,
45 | "react/require-default-props": 0,
46 | "react/no-unescaped-entities": 0,
47 | "import/prefer-default-export": 0,
48 | "react/jsx-props-no-spreading": 0,
49 | "react/jsx-filename-extension": 0,
50 | "react/destructuring-assignment": 0,
51 | "import/no-extraneous-dependencies": 0,
52 | "react/jsx-key": 1,
53 | "react-hooks/rules-of-hooks": 2,
54 | "no-unused-vars": [
55 | 1,
56 | {
57 | "ignoreRestSiblings": false
58 | }
59 | ]
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | /.env
17 | /.env.local
18 | /.env.development.local
19 | /.env.test.local
20 | /.env.production.local
21 |
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 |
26 | .eslintcache
27 |
28 | # config
29 | /src/config/api-keys.js
30 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 100,
3 | "singleQuote": true,
4 | "trailingComma": "none",
5 | "tabWidth": 2
6 | }
7 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ### v1.2.0
2 |
3 | ###### Sep 18, 2021
4 |
5 | - Support MIU v5.0.0 official release
6 | - Upgrade some dependencies to the latest versions
7 |
8 | ###### Aug 18, 2021
9 |
10 | - Update `src/theme/typography.js`
11 | - Upgrade some dependencies to the latest versions
12 |
13 | ---
14 |
15 | ### v1.1.0
16 |
17 | ###### Jul 23, 2021
18 |
19 | - Support MUI v5.0.0-beta.1
20 | - Upgrade some dependencies to the latest versions
21 |
22 | ---
23 |
24 | ### v1.0.0
25 |
26 | ###### Jun 28, 2021
27 |
28 | Initial release.
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Trading Dashboard
2 |
3 |
4 |
5 | ## Getting started
6 |
7 | - Clone this repository.
8 | - Create a fresh new API on Binance, with only read rights.
9 | - In the config folder, edit the `api-keys.example.js` into `api-keys.js` and put your new api key/secret.
10 | - From the root, run `yarn install` or `npm install` (one single time)
11 | - From the root, run `yarn start` or `npm start`
12 |
13 | Currently only Binance and Futures are supported.
14 |
15 | ## API weight usage
16 |
17 | - Account: Fetching account information cost `5` weight
18 | - Trades: The initial fetch cost `7 days * 8 chunks * 5 weight = 280 weight`. After the initial fetch, it will cost max `8 chunks * 5 weight = 40 weight / minute`
19 | - Income: The initial fetch cost `7 days * 3 chunks * 30 weight = 630 weight`. After the initial fetch, it will cost max `3 chunks * 30 weight = 90 weight / minute`
20 |
21 | - Total weight consumption: First load of the page `915 weight`, after that every minute your data is refreshed automatically and consumes `135 weight / minute`.
22 | - Reminder: Binance API allows you to consume up to `1200 weight / minute / IP`.
23 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es6",
4 | "module": "commonjs",
5 | "baseUrl": "."
6 | },
7 | "include": ["src/**/*"],
8 | "exclude": ["node_modules"]
9 | }
10 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@sh-stark/trading-dashboard",
3 | "version": "1.0.1",
4 | "private": false,
5 | "author": "SH Stark",
6 | "scripts": {
7 | "build": "react-scripts build",
8 | "eject": "react-scripts eject",
9 | "lint": "eslint --ext .js,.jsx ./src",
10 | "lint:fix": "eslint --fix --ext .js,.jsx ./src",
11 | "start": "react-scripts start",
12 | "test": "react-scripts test"
13 | },
14 | "babel": {
15 | "presets": [
16 | "@babel/preset-react"
17 | ]
18 | },
19 | "browserslist": {
20 | "production": [
21 | ">0.2%",
22 | "not dead",
23 | "not op_mini all"
24 | ],
25 | "development": [
26 | "last 1 chrome version",
27 | "last 1 firefox version",
28 | "last 1 safari version"
29 | ]
30 | },
31 | "eslintConfig": {
32 | "extends": [
33 | "react-app",
34 | "react-app/jest"
35 | ]
36 | },
37 | "dependencies": {
38 | "@emotion/react": "11.5.0",
39 | "@emotion/styled": "11.3.0",
40 | "@iconify/icons-eva": "1.1.0",
41 | "@iconify/react": "3.0.1",
42 | "@mui/lab": "5.0.0-alpha.53",
43 | "@mui/material": "5.0.6",
44 | "@mui/utils": "5.0.1",
45 | "@testing-library/jest-dom": "5.15.0",
46 | "apexcharts": "3.29.0",
47 | "axios": "0.24.0",
48 | "crypto": "1.0.1",
49 | "date-fns": "2.25.0",
50 | "faker": "5.5.3",
51 | "formik": "2.2.9",
52 | "framer-motion": "4.1.17",
53 | "history": "5.1.0",
54 | "lodash": "4.17.21",
55 | "moment": "2.29.1",
56 | "numeral": "2.0.6",
57 | "prop-types": "15.7.2",
58 | "react": "17.0.2",
59 | "react-apexcharts": "1.3.9",
60 | "react-dom": "17.0.2",
61 | "react-helmet-async": "1.1.2",
62 | "react-router-dom": "6.0.1",
63 | "react-scripts": "4.0.3",
64 | "react-use-websocket": "2.9.1",
65 | "recoil": "0.5.2",
66 | "simplebar": "5.3.6",
67 | "simplebar-react": "2.3.6",
68 | "web-vitals": "2.1.2"
69 | },
70 | "devDependencies": {
71 | "@babel/core": "^7.16.0",
72 | "@babel/eslint-parser": "^7.16.0",
73 | "eslint": "^7.32.0",
74 | "eslint-config-airbnb": "18.2.1",
75 | "eslint-config-prettier": "8.3.0",
76 | "eslint-config-react-app": "6.0.0",
77 | "eslint-plugin-flowtype": "5.2.0",
78 | "eslint-plugin-import": "2.25.2",
79 | "eslint-plugin-jsx-a11y": "6.4.1",
80 | "eslint-plugin-prettier": "4.0.0",
81 | "eslint-plugin-react": "7.26.1",
82 | "eslint-plugin-react-hooks": "4.2.0",
83 | "prettier": "2.4.1"
84 | },
85 | "licence": "MIT"
86 | }
87 |
--------------------------------------------------------------------------------
/public/_redirects:
--------------------------------------------------------------------------------
1 | /* /index.html 200
--------------------------------------------------------------------------------
/public/favicon/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SH-Stark/trading-dashboard/4a272ec5e788902a61ae327107a4204f8605039e/public/favicon/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/favicon/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SH-Stark/trading-dashboard/4a272ec5e788902a61ae327107a4204f8605039e/public/favicon/android-chrome-512x512.png
--------------------------------------------------------------------------------
/public/favicon/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SH-Stark/trading-dashboard/4a272ec5e788902a61ae327107a4204f8605039e/public/favicon/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/favicon/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SH-Stark/trading-dashboard/4a272ec5e788902a61ae327107a4204f8605039e/public/favicon/favicon-16x16.png
--------------------------------------------------------------------------------
/public/favicon/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SH-Stark/trading-dashboard/4a272ec5e788902a61ae327107a4204f8605039e/public/favicon/favicon-32x32.png
--------------------------------------------------------------------------------
/public/favicon/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SH-Stark/trading-dashboard/4a272ec5e788902a61ae327107a4204f8605039e/public/favicon/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
20 |
21 |
22 |
23 |
24 | Trading Dashboard
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Dashboard App",
3 | "name": "Dashboard Binance Futures",
4 | "icons": [
5 | {
6 | "src": "favicon/android-chrome-192x192.png",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | },
10 | {
11 | "src": "favicon/android-chrome-512x512.png",
12 | "sizes": "512x512",
13 | "type": "image/png"
14 | }
15 | ],
16 | "start_url": ".",
17 | "display": "standalone",
18 | "theme_color": "#000000",
19 | "background_color": "#ffffff"
20 | }
21 |
--------------------------------------------------------------------------------
/public/static/icons/ic_flag_de.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/static/icons/ic_flag_en.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/static/icons/ic_flag_fr.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/static/icons/ic_notification_chat.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/static/icons/ic_notification_mail.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/static/icons/ic_notification_package.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/static/icons/ic_notification_shipping.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/static/icons/shape-avatar.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/static/illustrations/BTC_Logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/static/illustrations/illustration_404.svg:
--------------------------------------------------------------------------------
1 |
41 |
--------------------------------------------------------------------------------
/public/static/illustrations/illustration_BTC.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SH-Stark/trading-dashboard/4a272ec5e788902a61ae327107a4204f8605039e/public/static/illustrations/illustration_BTC.png
--------------------------------------------------------------------------------
/public/static/log.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/static/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import { useSetRecoilState } from 'recoil';
3 | import { format } from 'date-fns';
4 |
5 | // routes
6 | import Router from './routes';
7 | // theme
8 | import ThemeConfig from './theme';
9 | import GlobalStyles from './theme/globalStyles';
10 | // components
11 | import ScrollToTop from './components/ScrollToTop';
12 | import { BaseOptionChartStyle } from './components/charts/BaseOptionChart';
13 | // recoil and services
14 | import { accountAtom, incomesAtom, tradesAtom, updateTimeAtom } from './recoil/atoms';
15 | import { getTradesOfTheWeek, getUserTradesOfTheDay } from './services/tradesServices';
16 | import { getIncomesOfTheWeek, getUserIncomesOfTheDay } from './services/incomeServices';
17 | import { getUserAccount } from './services/accountServices';
18 |
19 | // ----------------------------------------------------------------------
20 |
21 | const INTERVAL_FETCH = 60000; // 1 minute
22 |
23 | const App = () => {
24 | const [intervalId, setIntervalId] = useState();
25 |
26 | const setTradesRecoil = useSetRecoilState(tradesAtom);
27 | const setIncomesRecoil = useSetRecoilState(incomesAtom);
28 | const setAccountRecoil = useSetRecoilState(accountAtom);
29 | const setUpdateTimeRecoil = useSetRecoilState(updateTimeAtom);
30 |
31 | const getDailyData = () => {
32 | getUserAccount().then((account) => setAccountRecoil(account));
33 | getUserTradesOfTheDay().then((trades) =>
34 | setTradesRecoil((oldTrades) => ({
35 | ...oldTrades,
36 | [format(new Date(), 'MM/dd/yyyy')]: trades
37 | }))
38 | );
39 | getUserIncomesOfTheDay().then((incomes) =>
40 | setIncomesRecoil((oldIncomes) => ({
41 | ...oldIncomes,
42 | [format(new Date(), 'MM/dd/yyyy')]: incomes
43 | }))
44 | );
45 |
46 | setUpdateTimeRecoil(new Date());
47 | console.log('Updating your daily data... time: ', new Date());
48 | };
49 |
50 | const periodicallyFetchDailyData = () => {
51 | const id = setInterval(getDailyData, INTERVAL_FETCH);
52 | setIntervalId(id);
53 | };
54 |
55 | useEffect(() => {
56 | console.log('Fetching your weekly data');
57 |
58 | getTradesOfTheWeek().then((trades) => setTradesRecoil(trades));
59 | getIncomesOfTheWeek().then((incomes) => setIncomesRecoil(incomes));
60 | getUserAccount().then((account) => setAccountRecoil(account));
61 | setUpdateTimeRecoil(new Date());
62 |
63 | periodicallyFetchDailyData();
64 | return () => clearInterval(intervalId);
65 | }, []);
66 |
67 | return (
68 |
69 |
70 |
71 |
72 |
73 |
74 | );
75 | };
76 |
77 | export default App;
78 |
--------------------------------------------------------------------------------
/src/assets/avatar_default.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SH-Stark/trading-dashboard/4a272ec5e788902a61ae327107a4204f8605039e/src/assets/avatar_default.jpg
--------------------------------------------------------------------------------
/src/components/@material-extend/MHidden.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | // material
3 | import { useMediaQuery } from '@mui/material';
4 |
5 | // ----------------------------------------------------------------------
6 |
7 | MHidden.propTypes = {
8 | children: PropTypes.node,
9 | width: PropTypes.oneOf([
10 | 'xsDown',
11 | 'smDown',
12 | 'mdDown',
13 | 'lgDown',
14 | 'xlDown',
15 | 'xsUp',
16 | 'smUp',
17 | 'mdUp',
18 | 'lgUp',
19 | 'xlUp'
20 | ]).isRequired
21 | };
22 |
23 | export default function MHidden({ width, children }) {
24 | const breakpoint = width.substring(0, 2);
25 |
26 | const hiddenUp = useMediaQuery((theme) => theme.breakpoints.up(breakpoint));
27 | const hiddenDown = useMediaQuery((theme) => theme.breakpoints.down(breakpoint));
28 |
29 | if (width.includes('Down')) {
30 | return hiddenDown ? null : children;
31 | }
32 |
33 | if (width.includes('Up')) {
34 | return hiddenUp ? null : children;
35 | }
36 |
37 | return null;
38 | }
39 |
--------------------------------------------------------------------------------
/src/components/@material-extend/index.js:
--------------------------------------------------------------------------------
1 | export { default as MHidden } from './MHidden';
2 |
--------------------------------------------------------------------------------
/src/components/ColorManyPicker.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import { Icon } from '@iconify/react';
3 | import checkmarkFill from '@iconify/icons-eva/checkmark-fill';
4 | // material
5 | import { Box, Checkbox } from '@mui/material';
6 |
7 | // ----------------------------------------------------------------------
8 |
9 | IconColor.propTypes = {
10 | sx: PropTypes.object
11 | };
12 |
13 | function IconColor({ sx, ...other }) {
14 | return (
15 |
26 | theme.transitions.create('all', {
27 | duration: theme.transitions.duration.shortest
28 | }),
29 | ...sx
30 | }}
31 | {...other}
32 | >
33 |
34 |
35 | );
36 | }
37 |
38 | ColorManyPicker.propTypes = {
39 | colors: PropTypes.array.isRequired,
40 | onChecked: PropTypes.func,
41 | sx: PropTypes.object
42 | };
43 |
44 | export default function ColorManyPicker({ colors, onChecked, sx, ...other }) {
45 | return (
46 |
47 | {colors.map((color) => {
48 | const isWhite = color === '#FFFFFF' || color === 'white';
49 |
50 | return (
51 | `solid 1px ${theme.palette.divider}`
62 | })
63 | }}
64 | />
65 | }
66 | checkedIcon={
67 | `solid 1px ${theme.palette.divider}`,
82 | boxShadow: (theme) => `4px 4px 8px 0 ${theme.palette.grey[500_24]}`,
83 | '& svg': { width: 12, height: 12, color: 'common.black' }
84 | })
85 | }}
86 | />
87 | }
88 | sx={{
89 | color,
90 | '&:hover': { opacity: 0.72 }
91 | }}
92 | {...other}
93 | />
94 | );
95 | })}
96 |
97 | );
98 | }
99 |
--------------------------------------------------------------------------------
/src/components/ColorPreview.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | // material
3 | import { alpha, styled } from '@mui/material/styles';
4 | import { Box, Typography } from '@mui/material';
5 |
6 | // ----------------------------------------------------------------------
7 |
8 | const RootStyle = styled(Box)({
9 | display: 'flex',
10 | alignItems: 'center',
11 | justifyContent: 'flex-end'
12 | });
13 |
14 | const IconStyle = styled('div')(({ theme }) => ({
15 | marginLeft: -4,
16 | borderRadius: '50%',
17 | width: theme.spacing(2),
18 | height: theme.spacing(2),
19 | border: `solid 2px ${theme.palette.background.paper}`,
20 | boxShadow: `inset -1px 1px 2px ${alpha(theme.palette.common.black, 0.24)}`
21 | }));
22 |
23 | // ----------------------------------------------------------------------
24 |
25 | ColorPreview.propTypes = {
26 | colors: PropTypes.array.isRequired,
27 | limit: PropTypes.number
28 | };
29 |
30 | export default function ColorPreview({ colors, limit = 3, ...other }) {
31 | const showColor = colors.slice(0, limit);
32 | const moreColor = colors.length - limit;
33 |
34 | return (
35 |
36 | {showColor.map((color, index) => (
37 |
38 | ))}
39 |
40 | {colors.length > limit && {`+${moreColor}`}}
41 |
42 | );
43 | }
44 |
--------------------------------------------------------------------------------
/src/components/Label.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | // material
3 | import { alpha, styled } from '@mui/material/styles';
4 |
5 | // ----------------------------------------------------------------------
6 |
7 | const RootStyle = styled('span')(({ theme, ownerState }) => {
8 | const { color, variant } = ownerState;
9 |
10 | const styleFilled = (color) => ({
11 | color: theme.palette[color].contrastText,
12 | backgroundColor: theme.palette[color].main
13 | });
14 |
15 | const styleOutlined = (color) => ({
16 | color: theme.palette[color].main,
17 | backgroundColor: 'transparent',
18 | border: `1px solid ${theme.palette[color].main}`
19 | });
20 |
21 | const styleGhost = (color) => ({
22 | color: theme.palette[color].dark,
23 | backgroundColor: alpha(theme.palette[color].main, 0.16)
24 | });
25 |
26 | return {
27 | height: 22,
28 | minWidth: 22,
29 | lineHeight: 0,
30 | borderRadius: 8,
31 | cursor: 'default',
32 | alignItems: 'center',
33 | whiteSpace: 'nowrap',
34 | display: 'inline-flex',
35 | justifyContent: 'center',
36 | padding: theme.spacing(0, 1),
37 | color: theme.palette.grey[800],
38 | fontSize: theme.typography.pxToRem(12),
39 | fontFamily: theme.typography.fontFamily,
40 | backgroundColor: theme.palette.grey[300],
41 | fontWeight: theme.typography.fontWeightBold,
42 |
43 | ...(color !== 'default'
44 | ? {
45 | ...(variant === 'filled' && { ...styleFilled(color) }),
46 | ...(variant === 'outlined' && { ...styleOutlined(color) }),
47 | ...(variant === 'ghost' && { ...styleGhost(color) })
48 | }
49 | : {
50 | ...(variant === 'outlined' && {
51 | backgroundColor: 'transparent',
52 | color: theme.palette.text.primary,
53 | border: `1px solid ${theme.palette.grey[500_32]}`
54 | }),
55 | ...(variant === 'ghost' && {
56 | color: theme.palette.text.secondary,
57 | backgroundColor: theme.palette.grey[500_16]
58 | })
59 | })
60 | };
61 | });
62 |
63 | // ----------------------------------------------------------------------
64 |
65 | export default function Label({ color = 'default', variant = 'ghost', children, ...other }) {
66 | return (
67 |
68 | {children}
69 |
70 | );
71 | }
72 |
73 | Label.propTypes = {
74 | children: PropTypes.node,
75 | color: PropTypes.oneOf([
76 | 'default',
77 | 'primary',
78 | 'secondary',
79 | 'info',
80 | 'success',
81 | 'warning',
82 | 'error'
83 | ]),
84 | variant: PropTypes.oneOf(['filled', 'outlined', 'ghost'])
85 | };
86 |
--------------------------------------------------------------------------------
/src/components/Logo.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | // material
3 | import { Box } from '@mui/material';
4 |
5 | // ----------------------------------------------------------------------
6 |
7 | Logo.propTypes = {
8 | sx: PropTypes.object
9 | };
10 |
11 | export default function Logo({ sx }) {
12 | return ;
13 | }
14 |
--------------------------------------------------------------------------------
/src/components/MenuPopover.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | // material
3 | import { Popover } from '@mui/material';
4 | import { alpha, styled } from '@mui/material/styles';
5 |
6 | // ----------------------------------------------------------------------
7 |
8 | const ArrowStyle = styled('span')(({ theme }) => ({
9 | [theme.breakpoints.up('sm')]: {
10 | top: -7,
11 | zIndex: 1,
12 | width: 12,
13 | right: 20,
14 | height: 12,
15 | content: "''",
16 | position: 'absolute',
17 | borderRadius: '0 0 4px 0',
18 | transform: 'rotate(-135deg)',
19 | background: theme.palette.background.paper,
20 | borderRight: `solid 1px ${alpha(theme.palette.grey[500], 0.12)}`,
21 | borderBottom: `solid 1px ${alpha(theme.palette.grey[500], 0.12)}`
22 | }
23 | }));
24 |
25 | // ----------------------------------------------------------------------
26 |
27 | MenuPopover.propTypes = {
28 | children: PropTypes.node.isRequired,
29 | sx: PropTypes.object
30 | };
31 |
32 | export default function MenuPopover({ children, sx, ...other }) {
33 | return (
34 | theme.customShadows.z20,
43 | border: (theme) => `solid 1px ${theme.palette.grey[500_8]}`,
44 | width: 200,
45 | ...sx
46 | }
47 | }}
48 | {...other}
49 | >
50 |
51 |
52 | {children}
53 |
54 | );
55 | }
56 |
--------------------------------------------------------------------------------
/src/components/NavSection.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Icon } from '@iconify/react';
4 | import { NavLink as RouterLink, matchPath, useLocation } from 'react-router-dom';
5 | import arrowIosForwardFill from '@iconify/icons-eva/arrow-ios-forward-fill';
6 | import arrowIosDownwardFill from '@iconify/icons-eva/arrow-ios-downward-fill';
7 | // material
8 | import { alpha, useTheme, styled } from '@mui/material/styles';
9 | import { Box, List, Collapse, ListItemText, ListItemIcon, ListItemButton } from '@mui/material';
10 |
11 | // ----------------------------------------------------------------------
12 |
13 | const ListItemStyle = styled((props) => )(
14 | ({ theme }) => ({
15 | ...theme.typography.body2,
16 | height: 48,
17 | position: 'relative',
18 | textTransform: 'capitalize',
19 | paddingLeft: theme.spacing(5),
20 | paddingRight: theme.spacing(2.5),
21 | color: theme.palette.text.secondary,
22 | '&:before': {
23 | top: 0,
24 | right: 0,
25 | width: 3,
26 | bottom: 0,
27 | content: "''",
28 | display: 'none',
29 | position: 'absolute',
30 | borderTopLeftRadius: 4,
31 | borderBottomLeftRadius: 4,
32 | backgroundColor: theme.palette.primary.main
33 | }
34 | })
35 | );
36 |
37 | const ListItemIconStyle = styled(ListItemIcon)({
38 | width: 22,
39 | height: 22,
40 | display: 'flex',
41 | alignItems: 'center',
42 | justifyContent: 'center'
43 | });
44 |
45 | // ----------------------------------------------------------------------
46 |
47 | NavItem.propTypes = {
48 | item: PropTypes.object,
49 | active: PropTypes.func
50 | };
51 |
52 | function NavItem({ item, active }) {
53 | const theme = useTheme();
54 | const isActiveRoot = active(item.path);
55 | const { title, path, icon, info, children } = item;
56 | const [open, setOpen] = useState(isActiveRoot);
57 |
58 | const handleOpen = () => {
59 | setOpen((prev) => !prev);
60 | };
61 |
62 | const activeRootStyle = {
63 | color: 'primary.main',
64 | fontWeight: 'fontWeightMedium',
65 | bgcolor: alpha(theme.palette.primary.main, theme.palette.action.selectedOpacity),
66 | '&:before': { display: 'block' }
67 | };
68 |
69 | const activeSubStyle = {
70 | color: 'text.primary',
71 | fontWeight: 'fontWeightMedium'
72 | };
73 |
74 | if (children) {
75 | return (
76 | <>
77 |
83 | {icon && icon}
84 |
85 | {info && info}
86 |
91 |
92 |
93 |
94 |
95 | {children.map((item) => {
96 | const { title, path } = item;
97 | const isActiveSub = active(path);
98 |
99 | return (
100 |
108 |
109 | theme.transitions.create('transform'),
120 | ...(isActiveSub && {
121 | transform: 'scale(2)',
122 | bgcolor: 'primary.main'
123 | })
124 | }}
125 | />
126 |
127 |
128 |
129 | );
130 | })}
131 |
132 |
133 | >
134 | );
135 | }
136 |
137 | return (
138 |
145 | {icon && icon}
146 |
147 | {info && info}
148 |
149 | );
150 | }
151 |
152 | NavSection.propTypes = {
153 | navConfig: PropTypes.array
154 | };
155 |
156 | export default function NavSection({ navConfig, ...other }) {
157 | const { pathname } = useLocation();
158 | const match = (path) => (path ? !!matchPath({ path, end: false }, pathname) : false);
159 |
160 | return (
161 |
162 |
163 | {navConfig.map((item) => (
164 |
165 | ))}
166 |
167 |
168 | );
169 | }
170 |
--------------------------------------------------------------------------------
/src/components/Page.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import { Helmet } from 'react-helmet-async';
3 | import { forwardRef } from 'react';
4 | // material
5 | import { Box } from '@mui/material';
6 |
7 | // ----------------------------------------------------------------------
8 |
9 | const Page = forwardRef(({ children, title = '', ...other }, ref) => (
10 |
11 |
12 | {title}
13 |
14 | {children}
15 |
16 | ));
17 |
18 | Page.propTypes = {
19 | children: PropTypes.node.isRequired,
20 | title: PropTypes.string
21 | };
22 |
23 | export default Page;
24 |
--------------------------------------------------------------------------------
/src/components/ScrollToTop.js:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import { useLocation } from 'react-router-dom';
3 |
4 | // ----------------------------------------------------------------------
5 |
6 | export default function ScrollToTop() {
7 | const { pathname } = useLocation();
8 |
9 | useEffect(() => {
10 | window.scrollTo(0, 0);
11 | }, [pathname]);
12 |
13 | return null;
14 | }
15 |
--------------------------------------------------------------------------------
/src/components/Scrollbar.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import SimpleBarReact from 'simplebar-react';
3 | // material
4 | import { alpha, styled } from '@mui/material/styles';
5 | import { Box } from '@mui/material';
6 |
7 | // ----------------------------------------------------------------------
8 |
9 | const RootStyle = styled('div')({
10 | flexGrow: 1,
11 | height: '100%',
12 | overflow: 'hidden'
13 | });
14 |
15 | const SimpleBarStyle = styled(SimpleBarReact)(({ theme }) => ({
16 | maxHeight: '100%',
17 | '& .simplebar-scrollbar': {
18 | '&:before': {
19 | backgroundColor: alpha(theme.palette.grey[600], 0.48)
20 | },
21 | '&.simplebar-visible:before': {
22 | opacity: 1
23 | }
24 | },
25 | '& .simplebar-track.simplebar-vertical': {
26 | width: 10
27 | },
28 | '& .simplebar-track.simplebar-horizontal .simplebar-scrollbar': {
29 | height: 6
30 | },
31 | '& .simplebar-mask': {
32 | zIndex: 'inherit'
33 | }
34 | }));
35 |
36 | // ----------------------------------------------------------------------
37 |
38 | Scrollbar.propTypes = {
39 | children: PropTypes.node.isRequired,
40 | sx: PropTypes.object
41 | };
42 |
43 | export default function Scrollbar({ children, sx, ...other }) {
44 | const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
45 | navigator.userAgent
46 | );
47 |
48 | if (isMobile) {
49 | return (
50 |
51 | {children}
52 |
53 | );
54 | }
55 |
56 | return (
57 |
58 |
59 | {children}
60 |
61 |
62 | );
63 | }
64 |
--------------------------------------------------------------------------------
/src/components/SearchNotFound.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | // material
3 | import { Paper, Typography } from '@mui/material';
4 |
5 | // ----------------------------------------------------------------------
6 |
7 | SearchNotFound.propTypes = {
8 | searchQuery: PropTypes.string
9 | };
10 |
11 | export default function SearchNotFound({ searchQuery = '', ...other }) {
12 | return (
13 |
14 |
15 | Not found
16 |
17 |
18 | No results found for
19 | "{searchQuery}". Try checking for typos or using complete words.
20 |
21 |
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/src/components/SvgIconStyle.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import { Box } from '@mui/material';
3 |
4 | // ----------------------------------------------------------------------
5 |
6 | SvgIconStyle.propTypes = {
7 | src: PropTypes.string.isRequired,
8 | color: PropTypes.string,
9 | sx: PropTypes.object
10 | };
11 |
12 | export default function SvgIconStyle({ src, color = 'inherit', sx }) {
13 | return (
14 |
29 | );
30 | }
31 |
--------------------------------------------------------------------------------
/src/components/_dashboard/SummaryBox.jsx:
--------------------------------------------------------------------------------
1 | // material
2 | import { styled } from '@mui/material/styles';
3 | import { Card, Typography } from '@mui/material';
4 |
5 | const SummaryBox = ({ total = 0, color = '', backgroundColor = '', text = '', subText = '' }) => {
6 | const RootStyle = styled(Card)(({ theme }) => ({
7 | boxShadow: 'none',
8 | textAlign: 'center',
9 | padding: theme.spacing(subText ? 1.5 : 3, 0),
10 | color: theme.palette[color].darker,
11 | backgroundColor: theme.palette[backgroundColor].lighter
12 | }));
13 |
14 | return (
15 |
16 | {subText && (
17 |
18 | {subText}
19 |
20 | )}
21 |
22 | {total}
23 |
24 |
25 | {text}
26 |
27 |
28 | );
29 | };
30 |
31 | export default SummaryBox;
32 |
--------------------------------------------------------------------------------
/src/components/_dashboard/app/AppCurrentSubject.js:
--------------------------------------------------------------------------------
1 | import { merge } from 'lodash';
2 | import ReactApexChart from 'react-apexcharts';
3 | // material
4 | import { useTheme, styled } from '@mui/material/styles';
5 | import { Card, CardHeader } from '@mui/material';
6 | //
7 | import { BaseOptionChart } from '../../charts';
8 |
9 | // ----------------------------------------------------------------------
10 |
11 | const CHART_HEIGHT = 392;
12 | const LEGEND_HEIGHT = 72;
13 |
14 | const ChartWrapperStyle = styled('div')(({ theme }) => ({
15 | height: CHART_HEIGHT,
16 | marginTop: theme.spacing(2),
17 | '& .apexcharts-canvas svg': {
18 | height: CHART_HEIGHT
19 | },
20 | '& .apexcharts-canvas svg,.apexcharts-canvas foreignObject': {
21 | overflow: 'visible'
22 | },
23 | '& .apexcharts-legend': {
24 | height: LEGEND_HEIGHT,
25 | alignContent: 'center',
26 | position: 'relative !important',
27 | borderTop: `solid 1px ${theme.palette.divider}`,
28 | top: `calc(${CHART_HEIGHT - LEGEND_HEIGHT}px) !important`
29 | }
30 | }));
31 |
32 | // ----------------------------------------------------------------------
33 |
34 | const CHART_DATA = [
35 | { name: 'Series 1', data: [80, 50, 30, 40, 100, 20] },
36 | { name: 'Series 2', data: [20, 30, 40, 80, 20, 80] },
37 | { name: 'Series 3', data: [44, 76, 78, 13, 43, 10] }
38 | ];
39 |
40 | export default function AppCurrentSubject() {
41 | const theme = useTheme();
42 |
43 | const chartOptions = merge(BaseOptionChart(), {
44 | stroke: { width: 2 },
45 | fill: { opacity: 0.48 },
46 | legend: { floating: true, horizontalAlign: 'center' },
47 | xaxis: {
48 | categories: ['English', 'History', 'Physics', 'Geography', 'Chinese', 'Math'],
49 | labels: {
50 | style: {
51 | colors: [
52 | theme.palette.text.secondary,
53 | theme.palette.text.secondary,
54 | theme.palette.text.secondary,
55 | theme.palette.text.secondary,
56 | theme.palette.text.secondary,
57 | theme.palette.text.secondary
58 | ]
59 | }
60 | }
61 | }
62 | });
63 |
64 | return (
65 |
66 |
67 |
68 |
69 |
70 |
71 | );
72 | }
73 |
--------------------------------------------------------------------------------
/src/components/_dashboard/app/AppTasks.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import { Form, FormikProvider, useFormik } from 'formik';
3 | // material
4 | import {
5 | Box,
6 | Card,
7 | Checkbox,
8 | CardHeader,
9 | Typography,
10 | FormControlLabel,
11 | Stack
12 | } from '@mui/material';
13 |
14 | // ----------------------------------------------------------------------
15 |
16 | const TASKS = [
17 | 'Create FireStone Logo',
18 | 'Add SCSS and JS files if required',
19 | 'Stakeholder Meeting',
20 | 'Scoping & Estimations',
21 | 'Sprint Showcase'
22 | ];
23 |
24 | // ----------------------------------------------------------------------
25 |
26 | TaskItem.propTypes = {
27 | task: PropTypes.string,
28 | checked: PropTypes.bool,
29 | formik: PropTypes.object
30 | };
31 |
32 | function TaskItem({ task, checked, formik, ...other }) {
33 | const { getFieldProps } = formik;
34 |
35 | return (
36 |
37 |
40 | }
41 | label={
42 |
51 | {task}
52 |
53 | }
54 | />
55 |
56 | );
57 | }
58 |
59 | export default function AppTasks() {
60 | const formik = useFormik({
61 | initialValues: {
62 | checked: [TASKS[2]]
63 | },
64 | onSubmit: (values) => {
65 | console.log(values);
66 | }
67 | });
68 |
69 | const { values, handleSubmit } = formik;
70 |
71 | return (
72 |
73 |
74 |
75 |
76 |
86 |
87 |
88 |
89 | );
90 | }
91 |
--------------------------------------------------------------------------------
/src/components/_dashboard/app/AppTrafficBySite.js:
--------------------------------------------------------------------------------
1 | import faker from 'faker';
2 | import PropTypes from 'prop-types';
3 | import { Icon } from '@iconify/react';
4 | import googleFill from '@iconify/icons-eva/google-fill';
5 | import twitterFill from '@iconify/icons-eva/twitter-fill';
6 | import facebookFill from '@iconify/icons-eva/facebook-fill';
7 | import linkedinFill from '@iconify/icons-eva/linkedin-fill';
8 | // material
9 | import { Box, Grid, Card, Paper, Typography, CardHeader, CardContent } from '@mui/material';
10 | // utils
11 | import { fShortenNumber } from '../../../utils/formatNumber';
12 |
13 | // ----------------------------------------------------------------------
14 |
15 | const SOCIALS = [
16 | {
17 | name: 'FaceBook',
18 | value: faker.datatype.number(),
19 | icon:
20 | },
21 | {
22 | name: 'Google',
23 | value: faker.datatype.number(),
24 | icon:
25 | },
26 | {
27 | name: 'Linkedin',
28 | value: faker.datatype.number(),
29 | icon:
30 | },
31 | {
32 | name: 'Twitter',
33 | value: faker.datatype.number(),
34 | icon:
35 | }
36 | ];
37 |
38 | // ----------------------------------------------------------------------
39 |
40 | SiteItem.propTypes = {
41 | site: PropTypes.object
42 | };
43 |
44 | function SiteItem({ site }) {
45 | const { icon, value, name } = site;
46 |
47 | return (
48 |
49 |
50 | {icon}
51 | {fShortenNumber(value)}
52 |
53 | {name}
54 |
55 |
56 |
57 | );
58 | }
59 |
60 | export default function AppTrafficBySite() {
61 | return (
62 |
63 |
64 |
65 |
66 | {SOCIALS.map((site) => (
67 |
68 | ))}
69 |
70 |
71 |
72 | );
73 | }
74 |
--------------------------------------------------------------------------------
/src/components/_dashboard/app/LastOrders.js:
--------------------------------------------------------------------------------
1 | import { isEmpty, orderBy } from 'lodash';
2 | import { useRecoilValue } from 'recoil';
3 | import { memo } from 'react';
4 | import { format } from 'date-fns';
5 |
6 | // material
7 | import { Card, Typography, CardHeader, CardContent, CircularProgress } from '@mui/material';
8 | import {
9 | Timeline,
10 | TimelineItem,
11 | TimelineContent,
12 | TimelineConnector,
13 | TimelineSeparator,
14 | TimelineDot
15 | } from '@mui/lab';
16 |
17 | // utils
18 | import { fDateTime } from '../../../utils/formatTime';
19 | import { tradesAtom } from '../../../recoil/atoms';
20 |
21 | function Trade({ trade, isLast }) {
22 | const { side, time, symbol } = trade;
23 |
24 | const title = `Long ${symbol} ${side === 'BUY' ? 'opened' : 'closed'}`;
25 |
26 | return (
27 |
28 |
29 |
35 | {isLast ? null : }
36 |
37 |
38 | {title}
39 |
40 | {fDateTime(time)}
41 |
42 |
43 |
44 | );
45 | }
46 |
47 | const LastOrders = () => {
48 | const trades = useRecoilValue(tradesAtom);
49 | const today = format(new Date(), 'MM/dd/yyyy');
50 |
51 | return (
52 |
59 |
60 |
61 |
62 |
63 |
64 | );
65 | };
66 |
67 | const Content = memo(({ trades }) => {
68 | if (isEmpty(trades)) {
69 | return ;
70 | }
71 |
72 | const orderedTrades = orderBy(trades, ['time'], ['desc']);
73 | const tradesLength = trades.length;
74 | const maxLength = 5;
75 |
76 | return (
77 |
78 | {orderedTrades.slice(0, maxLength).map((trade, index) => (
79 |
84 | ))}
85 |
86 | );
87 | });
88 |
89 | export default LastOrders;
90 |
--------------------------------------------------------------------------------
/src/components/_dashboard/app/PerformanceOverview.js:
--------------------------------------------------------------------------------
1 | import { merge, sum, max, flatten } from 'lodash';
2 | import { format } from 'date-fns';
3 | import ReactApexChart from 'react-apexcharts';
4 | import { useRecoilValue } from 'recoil';
5 |
6 | // material
7 | import { Card, CardHeader, Box } from '@mui/material';
8 | //
9 | import { BaseOptionChart } from '../../charts';
10 | import { fPercent } from '../../../utils/formatNumber';
11 | import { generateLastNdates } from '../../../utils/formatTime';
12 | import { incomesAtom, accountAtom } from '../../../recoil/atoms';
13 |
14 | // ----------------------------------------------------------------------
15 |
16 | const lastWeekDates = generateLastNdates(7, 'MM/dd/yyyy').reverse();
17 |
18 | const getIncomeForDate = (incomes, date) => {
19 | const incomeList = incomes[format(new Date(date), 'MM/dd/yyyy')];
20 | return sum(incomeList?.map((inc) => JSON.parse(inc?.income)));
21 | };
22 |
23 | const getIncomeOfWeek = (incomes, isRounded = false) => {
24 | const incomeOfWeek = lastWeekDates.map((date) => getIncomeForDate(incomes, date));
25 |
26 | return isRounded
27 | ? incomeOfWeek.map((inc) => Math.round(inc))
28 | : incomeOfWeek.map((inc) => parseFloat(inc.toFixed(2)));
29 | };
30 |
31 | const getBalanceLastWeek = (incomes, balance) => {
32 | let sumIncome = 0;
33 | const balances = [];
34 |
35 | const incomeOfWeek = getIncomeOfWeek(incomes).reverse();
36 |
37 | incomeOfWeek.forEach((inc, i) => {
38 | sumIncome += inc;
39 | balances.push(Math.round(balance - sumIncome + incomeOfWeek[i]));
40 | });
41 |
42 | return balances.reverse();
43 | };
44 |
45 | const PerformanceOverview = () => {
46 | const incomes = useRecoilValue(incomesAtom);
47 | const account = useRecoilValue(accountAtom);
48 |
49 | const profitLastWeek = sum(
50 | flatten(Object.values(incomes))?.map((inc) => JSON.parse(inc?.income))
51 | );
52 |
53 | const { totalCrossWalletBalance = 0 } = account;
54 | const balance = JSON.parse(totalCrossWalletBalance);
55 | const increasePercent =
56 | (balance > 0 && profitLastWeek && (profitLastWeek / (balance - profitLastWeek)) * 100) || 0;
57 |
58 | const balancesOfLastWeek = getBalanceLastWeek(incomes, balance);
59 | const weekIncome = getIncomeOfWeek(incomes, true);
60 |
61 | const chartOptions = merge(BaseOptionChart(), {
62 | stroke: { width: [0, 2, 3] },
63 | plotOptions: { bar: { columnWidth: '11%', borderRadius: 4 } },
64 | fill: { type: ['solid', 'gradient', 'solid'] },
65 | labels: lastWeekDates,
66 | xaxis: { type: 'datetime' },
67 | yaxis: [
68 | {
69 | seriesName: 'Income',
70 | opposite: true,
71 | title: 'Income',
72 | min: 0,
73 | max: max(weekIncome) * 2,
74 | forceNiceScale: true
75 | },
76 | {
77 | seriesName: 'Balance',
78 | min: Math.round(balancesOfLastWeek[0] * 0.99),
79 | max: Math.round(balancesOfLastWeek[balancesOfLastWeek.length - 1] * 1.01),
80 | forceNiceScale: true,
81 | title: 'Balance'
82 | }
83 | ],
84 | tooltip: {
85 | shared: true,
86 | intersect: false,
87 | y: {
88 | formatter: (y) => {
89 | if (typeof y !== 'undefined') {
90 | return `$${y.toFixed(1)}`;
91 | }
92 | return y;
93 | }
94 | }
95 | }
96 | });
97 |
98 | const CHART_DATA = [
99 | {
100 | name: 'Income',
101 | type: 'column',
102 | data: weekIncome
103 | },
104 | {
105 | name: 'Balance',
106 | type: 'area',
107 | data: balancesOfLastWeek
108 | }
109 | ];
110 |
111 | return (
112 |
113 |
117 |
118 |
119 |
120 |
121 | );
122 | };
123 |
124 | export default PerformanceOverview;
125 |
--------------------------------------------------------------------------------
/src/components/_dashboard/app/PositionsRepartition.js:
--------------------------------------------------------------------------------
1 | import { merge, isEmpty } from 'lodash';
2 | import ReactApexChart from 'react-apexcharts';
3 | import { useRecoilValue } from 'recoil';
4 |
5 | // material
6 | import { useTheme, styled } from '@mui/material/styles';
7 | import { Card, CardHeader, CircularProgress } from '@mui/material';
8 | // utils
9 | import { fNumber } from '../../../utils/formatNumber';
10 | //
11 | import { BaseOptionChart } from '../../charts';
12 | import { accountAtom } from '../../../recoil/atoms';
13 |
14 | // ----------------------------------------------------------------------
15 |
16 | const CHART_HEIGHT = 410;
17 | const LEGEND_HEIGHT = 108;
18 |
19 | const ChartWrapperStyle = styled('div')(({ theme }) => ({
20 | height: CHART_HEIGHT,
21 | marginTop: theme.spacing(3),
22 | '& .apexcharts-canvas svg': { height: CHART_HEIGHT },
23 | '& .apexcharts-canvas svg,.apexcharts-canvas foreignObject': {
24 | overflow: 'visible'
25 | },
26 | '& .apexcharts-legend': {
27 | height: LEGEND_HEIGHT,
28 | alignContent: 'center',
29 | position: 'relative !important',
30 | borderTop: `solid 1px ${theme.palette.divider}`,
31 | top: `calc(${CHART_HEIGHT - LEGEND_HEIGHT}px) !important`
32 | }
33 | }));
34 |
35 | // ----------------------------------------------------------------------
36 |
37 | const PositionsRepartition = () => {
38 | const account = useRecoilValue(accountAtom);
39 |
40 | return (
41 |
42 |
43 |
44 | {isEmpty(account) ? (
45 |
46 | ) : (
47 |
48 | )}
49 |
50 |
51 | );
52 | };
53 | const PieChart = ({ accountPositions }) => {
54 | const theme = useTheme();
55 |
56 | const positions = accountPositions?.filter((pos) => pos.positionAmt > 0 || pos.positionAmt < 0);
57 |
58 | const chartOptions = merge(BaseOptionChart(), {
59 | labels: positions?.map((p) => p.symbol),
60 | stroke: { colors: [theme.palette.background.paper] },
61 | legend: { floating: true, horizontalAlign: 'center' },
62 | dataLabels: { enabled: true, dropShadow: { enabled: false } },
63 | tooltip: {
64 | fillSeriesColor: false,
65 | y: {
66 | formatter: (seriesName) => fNumber(seriesName),
67 | title: {
68 | formatter: (seriesName) => `#${seriesName}`
69 | }
70 | }
71 | },
72 | plotOptions: {
73 | pie: { donut: { labels: { show: false } } }
74 | }
75 | });
76 |
77 | return (
78 | JSON.parse(p.positionInitialMargin))}
81 | options={chartOptions}
82 | height={280}
83 | />
84 | );
85 | };
86 |
87 | export default PositionsRepartition;
88 |
--------------------------------------------------------------------------------
/src/components/_dashboard/app/TopPerformers.js:
--------------------------------------------------------------------------------
1 | import { merge, sum, orderBy, flatten } from 'lodash';
2 | import ReactApexChart from 'react-apexcharts';
3 | import { useRecoilValue } from 'recoil';
4 |
5 | // material
6 | import { Box, Card, CardHeader } from '@mui/material';
7 | // utils
8 | import { fCurrency } from '../../../utils/formatNumber';
9 | //
10 | import { BaseOptionChart } from '../../charts';
11 | import { incomesAtom } from '../../../recoil/atoms';
12 |
13 | // ----------------------------------------------------------------------
14 |
15 | const TopPerformers = () => {
16 | const incomes = useRecoilValue(incomesAtom);
17 |
18 | const pnlRepartition = {};
19 |
20 | flatten(Object.values(incomes))?.forEach((inc) => {
21 | pnlRepartition[inc?.symbol] = (pnlRepartition[inc?.symbol] || 0) + JSON.parse(inc?.income);
22 | });
23 |
24 | const orderedPerformers = orderBy(
25 | Object.keys(pnlRepartition).map((key) => ({ label: key, value: pnlRepartition[key] })),
26 | 'value',
27 | 'desc'
28 | );
29 |
30 | return (
31 |
32 | o.value)))} last 7 days`}
35 | />
36 |
37 |
38 |
39 |
40 | );
41 | };
42 |
43 | const Content = ({ orderedPerformers }) => {
44 | const chartOptions = merge(BaseOptionChart(), {
45 | tooltip: {
46 | marker: { show: false },
47 | y: {
48 | formatter: (seriesName) => fCurrency(seriesName),
49 | title: {
50 | formatter: () => 'Last 7 days: '
51 | }
52 | }
53 | },
54 | plotOptions: {
55 | bar: { horizontal: true, barHeight: '28%', borderRadius: 2 }
56 | },
57 | xaxis: {
58 | categories: orderedPerformers.map((o) => o.label)
59 | }
60 | });
61 |
62 | const CHART_DATA = [{ data: orderedPerformers.map((o) => o.value) }];
63 |
64 | return ;
65 | };
66 |
67 | export default TopPerformers;
68 |
--------------------------------------------------------------------------------
/src/components/_dashboard/app/index.js:
--------------------------------------------------------------------------------
1 | export { default as TopPerformers } from './TopPerformers';
2 | export { default as AppCurrentSubject } from './AppCurrentSubject';
3 | export { default as PositionsRepartition } from './PositionsRepartition';
4 | export { default as LastOrders } from './LastOrders';
5 | export { default as AppTasks } from './AppTasks';
6 | export { default as AppTrafficBySite } from './AppTrafficBySite';
7 | export { default as PerformanceOverview } from './PerformanceOverview';
8 |
--------------------------------------------------------------------------------
/src/components/_dashboard/order/OrderListHead.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | // material
3 | import { visuallyHidden } from '@mui/utils';
4 | import { Box, TableRow, TableCell, TableHead, TableSortLabel } from '@mui/material';
5 |
6 | // ----------------------------------------------------------------------
7 |
8 | OrderListHead.propTypes = {
9 | order: PropTypes.oneOf(['asc', 'desc']),
10 | orderBy: PropTypes.string,
11 | headLabel: PropTypes.array,
12 | onRequestSort: PropTypes.func
13 | };
14 |
15 | export default function OrderListHead({ order, orderBy, headLabel, onRequestSort }) {
16 | const createSortHandler = (property) => (event) => {
17 | onRequestSort(event, property);
18 | };
19 |
20 | return (
21 |
22 |
23 | {headLabel.map((headCell) => (
24 |
29 |
35 | {headCell.label}
36 | {orderBy === headCell.id ? (
37 |
38 | {order === 'desc' ? 'sorted descending' : 'sorted ascending'}
39 |
40 | ) : null}
41 |
42 |
43 | ))}
44 |
45 |
46 | );
47 | }
48 |
--------------------------------------------------------------------------------
/src/components/_dashboard/order/OrderListToolbar.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import { Icon } from '@iconify/react';
3 | import searchFill from '@iconify/icons-eva/search-fill';
4 |
5 | // material
6 | import { styled } from '@mui/material/styles';
7 | import { Box, Toolbar, Typography, OutlinedInput, InputAdornment } from '@mui/material';
8 |
9 | // ----------------------------------------------------------------------
10 |
11 | const RootStyle = styled(Toolbar)(({ theme }) => ({
12 | height: 96,
13 | display: 'flex',
14 | justifyContent: 'space-between',
15 | padding: theme.spacing(0, 1, 0, 3)
16 | }));
17 |
18 | const SearchStyle = styled(OutlinedInput)(({ theme }) => ({
19 | width: 240,
20 | transition: theme.transitions.create(['box-shadow', 'width'], {
21 | easing: theme.transitions.easing.easeInOut,
22 | duration: theme.transitions.duration.shorter
23 | }),
24 | '&.Mui-focused': { width: 320, boxShadow: theme.customShadows.z8 },
25 | '& fieldset': {
26 | borderWidth: `1px !important`,
27 | borderColor: `${theme.palette.grey[500_32]} !important`
28 | }
29 | }));
30 |
31 | // ----------------------------------------------------------------------
32 |
33 | OrderListToolbar.propTypes = {
34 | numSelected: PropTypes.number,
35 | filterName: PropTypes.string,
36 | onFilterName: PropTypes.func
37 | };
38 |
39 | export default function OrderListToolbar({ numSelected, filterName, onFilterName }) {
40 | return (
41 | 0 && {
44 | color: 'primary.main',
45 | bgcolor: 'primary.lighter'
46 | })
47 | }}
48 | >
49 | {numSelected > 0 ? (
50 |
51 | {numSelected} selected
52 |
53 | ) : (
54 |
60 |
61 |
62 | }
63 | />
64 | )}
65 |
66 | {/* {numSelected > 0 ? (
67 |
68 |
69 |
70 |
71 |
72 | ) : (
73 |
74 |
75 |
76 |
77 |
78 | )} */}
79 |
80 | );
81 | }
82 |
--------------------------------------------------------------------------------
/src/components/_dashboard/order/OrderMoreMenu.js:
--------------------------------------------------------------------------------
1 | import { Icon } from '@iconify/react';
2 | import { useRef, useState } from 'react';
3 | import editFill from '@iconify/icons-eva/edit-fill';
4 | import { Link as RouterLink } from 'react-router-dom';
5 | import trash2Outline from '@iconify/icons-eva/trash-2-outline';
6 | import moreVerticalFill from '@iconify/icons-eva/more-vertical-fill';
7 | // material
8 | import { Menu, MenuItem, IconButton, ListItemIcon, ListItemText } from '@mui/material';
9 |
10 | // ----------------------------------------------------------------------
11 |
12 | export default function OrderMoreMenu() {
13 | const ref = useRef(null);
14 | const [isOpen, setIsOpen] = useState(false);
15 |
16 | return (
17 | <>
18 | setIsOpen(true)}>
19 |
20 |
21 |
22 |
46 | >
47 | );
48 | }
49 |
--------------------------------------------------------------------------------
/src/components/_dashboard/order/index.js:
--------------------------------------------------------------------------------
1 | export { default as OrderListHead } from './OrderListHead';
2 | export { default as OrderListToolbar } from './OrderListToolbar';
3 | export { default as OrderMoreMenu } from './OrderMoreMenu';
4 |
--------------------------------------------------------------------------------
/src/components/animate/MotionContainer.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import { motion } from 'framer-motion';
3 | // material
4 | import { Box } from '@mui/material';
5 | //
6 | import { varWrapEnter } from './variants';
7 |
8 | // ----------------------------------------------------------------------
9 |
10 | MotionContainer.propTypes = {
11 | open: PropTypes.bool.isRequired,
12 | children: PropTypes.node
13 | };
14 |
15 | export default function MotionContainer({ open, children, ...other }) {
16 | return (
17 |
24 | {children}
25 |
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/src/components/animate/index.js:
--------------------------------------------------------------------------------
1 | export * from './variants';
2 | export { default as MotionContainer } from './MotionContainer';
3 |
--------------------------------------------------------------------------------
/src/components/animate/variants/Wrap.js:
--------------------------------------------------------------------------------
1 | // ----------------------------------------------------------------------
2 |
3 | export const varWrapEnter = {
4 | animate: {
5 | transition: { staggerChildren: 0.1 }
6 | }
7 | };
8 |
9 | export const varWrapExit = {
10 | exit: {
11 | transition: { staggerChildren: 0.1 }
12 | }
13 | };
14 |
15 | export const varWrapBoth = {
16 | animate: {
17 | transition: { staggerChildren: 0.07, delayChildren: 0.1 }
18 | },
19 | exit: {
20 | transition: { staggerChildren: 0.05, staggerDirection: -1 }
21 | }
22 | };
23 |
--------------------------------------------------------------------------------
/src/components/animate/variants/bounce/BounceIn.js:
--------------------------------------------------------------------------------
1 | import {
2 | varBounceOut,
3 | varBounceOutUp,
4 | varBounceOutDown,
5 | varBounceOutLeft,
6 | varBounceOutRight
7 | } from './BounceOut';
8 |
9 | // ----------------------------------------------------------------------
10 |
11 | const TRANSITION_ENTER = {
12 | duration: 0.72,
13 | ease: [0.43, 0.13, 0.23, 0.96]
14 | };
15 |
16 | const TRANSITION_EXIT = {
17 | duration: 0.48,
18 | ease: [0.43, 0.13, 0.23, 0.96]
19 | };
20 |
21 | export const varBounceIn = {
22 | animate: {
23 | scale: [0.3, 1.1, 0.9, 1.03, 0.97, 1],
24 | opacity: [0, 1, 1, 1, 1, 1],
25 | transition: TRANSITION_ENTER
26 | },
27 | exit: varBounceOut.animate
28 | };
29 |
30 | export const varBounceInUp = {
31 | animate: {
32 | y: [720, -24, 12, -4, 0],
33 | scaleY: [4, 0.9, 0.95, 0.985, 1],
34 | opacity: [0, 1, 1, 1, 1],
35 | transition: { ...TRANSITION_ENTER }
36 | },
37 | exit: { ...varBounceOutDown.animate, transition: TRANSITION_EXIT }
38 | };
39 |
40 | export const varBounceInDown = {
41 | animate: {
42 | y: [-720, 24, -12, 4, 0],
43 | scaleY: [4, 0.9, 0.95, 0.985, 1],
44 | opacity: [0, 1, 1, 1, 1],
45 | transition: TRANSITION_ENTER
46 | },
47 | exit: { ...varBounceOutUp.animate, transition: TRANSITION_EXIT }
48 | };
49 |
50 | export const varBounceInLeft = {
51 | animate: {
52 | x: [-720, 24, -12, 4, 0],
53 | scaleX: [3, 1, 0.98, 0.995, 1],
54 | opacity: [0, 1, 1, 1, 1],
55 | transition: TRANSITION_ENTER
56 | },
57 | exit: { ...varBounceOutLeft.animate, transition: TRANSITION_EXIT }
58 | };
59 |
60 | export const varBounceInRight = {
61 | animate: {
62 | x: [720, -24, 12, -4, 0],
63 | scaleX: [3, 1, 0.98, 0.995, 1],
64 | opacity: [0, 1, 1, 1, 1],
65 | transition: TRANSITION_ENTER
66 | },
67 | exit: { ...varBounceOutRight.animate, transition: TRANSITION_EXIT }
68 | };
69 |
--------------------------------------------------------------------------------
/src/components/animate/variants/bounce/BounceOut.js:
--------------------------------------------------------------------------------
1 | // ----------------------------------------------------------------------
2 |
3 | export const varBounceOut = {
4 | animate: {
5 | scale: [0.9, 1.1, 0.3],
6 | opacity: [1, 1, 0]
7 | }
8 | };
9 |
10 | export const varBounceOutUp = {
11 | animate: {
12 | y: [-12, 24, -720],
13 | scaleY: [0.985, 0.9, 3],
14 | opacity: [1, 1, 0]
15 | }
16 | };
17 |
18 | export const varBounceOutDown = {
19 | animate: {
20 | y: [12, -24, 720],
21 | scaleY: [0.985, 0.9, 3],
22 | opacity: [1, 1, 0]
23 | }
24 | };
25 |
26 | export const varBounceOutLeft = {
27 | animate: {
28 | x: [0, 24, -720],
29 | scaleX: [1, 0.9, 2],
30 | opacity: [1, 1, 0]
31 | }
32 | };
33 |
34 | export const varBounceOutRight = {
35 | animate: {
36 | x: [0, -24, 720],
37 | scaleX: [1, 0.9, 2],
38 | opacity: [1, 1, 0]
39 | }
40 | };
41 |
--------------------------------------------------------------------------------
/src/components/animate/variants/bounce/index.js:
--------------------------------------------------------------------------------
1 | export * from './BounceIn';
2 | export * from './BounceOut';
3 |
--------------------------------------------------------------------------------
/src/components/animate/variants/index.js:
--------------------------------------------------------------------------------
1 | export * from './Wrap';
2 | export * from './bounce';
3 |
--------------------------------------------------------------------------------
/src/components/charts/BaseOptionChart.js:
--------------------------------------------------------------------------------
1 | // material
2 | import { alpha, useTheme } from '@mui/material/styles';
3 | import { GlobalStyles } from '@mui/material';
4 |
5 | // ----------------------------------------------------------------------
6 |
7 | export function BaseOptionChartStyle() {
8 | const theme = useTheme();
9 |
10 | const background = {
11 | backdropFilter: 'blur(6px)',
12 | WebkitBackdropFilter: 'blur(6px)', // Fix on Mobile
13 | backgroundColor: alpha(theme.palette.background.default, 0.72)
14 | };
15 |
16 | return (
17 |
61 | );
62 | }
63 |
64 | export default function BaseOptionChart() {
65 | const theme = useTheme();
66 |
67 | const LABEL_TOTAL = {
68 | show: true,
69 | label: 'Total',
70 | color: theme.palette.text.secondary,
71 | ...theme.typography.subtitle2
72 | };
73 |
74 | const LABEL_VALUE = {
75 | offsetY: 8,
76 | color: theme.palette.text.primary,
77 | ...theme.typography.h3
78 | };
79 |
80 | return {
81 | // Colors
82 | colors: [
83 | theme.palette.primary.main,
84 | theme.palette.chart.yellow[0],
85 | theme.palette.chart.blue[0],
86 | theme.palette.chart.violet[0],
87 | theme.palette.chart.green[0],
88 | theme.palette.chart.red[0]
89 | ],
90 |
91 | // Chart
92 | chart: {
93 | toolbar: { show: false },
94 | zoom: { enabled: false },
95 | // animations: { enabled: false },
96 | foreColor: theme.palette.text.disabled,
97 | fontFamily: theme.typography.fontFamily
98 | },
99 |
100 | // States
101 | states: {
102 | hover: {
103 | filter: {
104 | type: 'lighten',
105 | value: 0.04
106 | }
107 | },
108 | active: {
109 | filter: {
110 | type: 'darken',
111 | value: 0.88
112 | }
113 | }
114 | },
115 |
116 | // Fill
117 | fill: {
118 | opacity: 1,
119 | gradient: {
120 | type: 'vertical',
121 | shadeIntensity: 0,
122 | opacityFrom: 0.4,
123 | opacityTo: 0,
124 | stops: [0, 100]
125 | }
126 | },
127 |
128 | // Datalabels
129 | dataLabels: { enabled: false },
130 |
131 | // Stroke
132 | stroke: {
133 | width: 3,
134 | curve: 'smooth',
135 | lineCap: 'round'
136 | },
137 |
138 | // Grid
139 | grid: {
140 | strokeDashArray: 3,
141 | borderColor: theme.palette.divider
142 | },
143 |
144 | // Xaxis
145 | xaxis: {
146 | axisBorder: { show: false },
147 | axisTicks: { show: false }
148 | },
149 |
150 | // Markers
151 | markers: {
152 | size: 0,
153 | strokeColors: theme.palette.background.paper
154 | },
155 |
156 | // Tooltip
157 | tooltip: {
158 | x: {
159 | show: false
160 | }
161 | },
162 |
163 | // Legend
164 | legend: {
165 | show: true,
166 | fontSize: 13,
167 | position: 'top',
168 | horizontalAlign: 'right',
169 | markers: {
170 | radius: 12
171 | },
172 | fontWeight: 500,
173 | itemMargin: { horizontal: 12 },
174 | labels: {
175 | colors: theme.palette.text.primary
176 | }
177 | },
178 |
179 | // plotOptions
180 | plotOptions: {
181 | // Bar
182 | bar: {
183 | columnWidth: '28%',
184 | borderRadius: 4
185 | },
186 | // Pie + Donut
187 | pie: {
188 | donut: {
189 | labels: {
190 | show: true,
191 | value: LABEL_VALUE,
192 | total: LABEL_TOTAL
193 | }
194 | }
195 | },
196 | // Radialbar
197 | radialBar: {
198 | track: {
199 | strokeWidth: '100%',
200 | background: theme.palette.grey[500_16]
201 | },
202 | dataLabels: {
203 | value: LABEL_VALUE,
204 | total: LABEL_TOTAL
205 | }
206 | },
207 | // Radar
208 | radar: {
209 | polygons: {
210 | fill: { colors: ['transparent'] },
211 | strokeColors: theme.palette.divider,
212 | connectorColors: theme.palette.divider
213 | }
214 | },
215 | // polarArea
216 | polarArea: {
217 | rings: {
218 | strokeColor: theme.palette.divider
219 | },
220 | spokes: {
221 | connectorColors: theme.palette.divider
222 | }
223 | }
224 | },
225 |
226 | // Responsive
227 | responsive: [
228 | {
229 | // sm
230 | breakpoint: theme.breakpoints.values.sm,
231 | options: {
232 | plotOptions: { bar: { columnWidth: '40%' } }
233 | }
234 | },
235 | {
236 | // md
237 | breakpoint: theme.breakpoints.values.md,
238 | options: {
239 | plotOptions: { bar: { columnWidth: '32%' } }
240 | }
241 | }
242 | ]
243 | };
244 | }
245 |
--------------------------------------------------------------------------------
/src/components/charts/index.js:
--------------------------------------------------------------------------------
1 | export { default as BaseOptionChart } from './BaseOptionChart';
2 |
--------------------------------------------------------------------------------
/src/config/api-keys.example.js:
--------------------------------------------------------------------------------
1 | export const CONFIGURATION = {
2 | binance: {
3 | key: 'BINANCE_API_KEY',
4 | secret: 'BINANCE_API_SECRET',
5 | exchangeType: 'futures' // spot is not supported yet
6 | },
7 | profile: {
8 | name: 'John Doe'
9 | }
10 | };
11 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | // scroll bar
2 | import 'simplebar/src/simplebar.css';
3 |
4 | import ReactDOM from 'react-dom';
5 | import { BrowserRouter } from 'react-router-dom';
6 | import { HelmetProvider } from 'react-helmet-async';
7 | import { RecoilRoot } from 'recoil';
8 |
9 | //
10 | import App from './App';
11 | import * as serviceWorker from './serviceWorker';
12 | import reportWebVitals from './reportWebVitals';
13 |
14 | // ----------------------------------------------------------------------
15 |
16 | ReactDOM.render(
17 |
18 |
19 |
20 |
21 |
22 |
23 | ,
24 | document.getElementById('root')
25 | );
26 |
27 | // If you want to enable client cache, register instead.
28 | serviceWorker.unregister();
29 |
30 | // If you want to start measuring performance in your app, pass a function
31 | // to log results (for example: reportWebVitals(console.log))
32 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
33 | reportWebVitals();
34 |
--------------------------------------------------------------------------------
/src/layouts/LogoOnlyLayout.js:
--------------------------------------------------------------------------------
1 | import { Link as RouterLink, Outlet } from 'react-router-dom';
2 | // material
3 | import { styled } from '@mui/material/styles';
4 | // components
5 | import Logo from '../components/Logo';
6 |
7 | // ----------------------------------------------------------------------
8 |
9 | const HeaderStyle = styled('header')(({ theme }) => ({
10 | top: 0,
11 | left: 0,
12 | lineHeight: 0,
13 | width: '100%',
14 | position: 'absolute',
15 | padding: theme.spacing(3, 3, 0),
16 | [theme.breakpoints.up('sm')]: {
17 | padding: theme.spacing(5, 5, 0)
18 | }
19 | }));
20 |
21 | // ----------------------------------------------------------------------
22 |
23 | export default function LogoOnlyLayout() {
24 | return (
25 | <>
26 |
27 |
28 |
29 |
30 |
31 |
32 | >
33 | );
34 | }
35 |
--------------------------------------------------------------------------------
/src/layouts/dashboard/AccountPopover.js:
--------------------------------------------------------------------------------
1 | import { Icon } from '@iconify/react';
2 | import { useRef, useState } from 'react';
3 | import homeFill from '@iconify/icons-eva/home-fill';
4 | import personFill from '@iconify/icons-eva/person-fill';
5 | import settings2Fill from '@iconify/icons-eva/settings-2-fill';
6 | import { Link as RouterLink } from 'react-router-dom';
7 | // material
8 | import { alpha } from '@mui/material/styles';
9 | import { Button, Box, Divider, MenuItem, Typography, Avatar, IconButton } from '@mui/material';
10 | // components
11 | import MenuPopover from '../../components/MenuPopover';
12 | //
13 | import profilePicture from '../../assets/avatar_default.jpg';
14 | import { CONFIGURATION } from '../../config/api-keys';
15 |
16 | // ----------------------------------------------------------------------
17 |
18 | const MENU_OPTIONS = [
19 | {
20 | label: 'Home',
21 | icon: homeFill,
22 | linkTo: '/'
23 | },
24 | {
25 | label: 'Profile',
26 | icon: personFill,
27 | linkTo: '#'
28 | },
29 | {
30 | label: 'Settings',
31 | icon: settings2Fill,
32 | linkTo: '#'
33 | }
34 | ];
35 |
36 | // ----------------------------------------------------------------------
37 |
38 | export default function AccountPopover() {
39 | const anchorRef = useRef(null);
40 | const [open, setOpen] = useState(false);
41 |
42 | const handleOpen = () => {
43 | setOpen(true);
44 | };
45 | const handleClose = () => {
46 | setOpen(false);
47 | };
48 |
49 | return (
50 | <>
51 | alpha(theme.palette.grey[900], 0.72)
67 | }
68 | })
69 | }}
70 | >
71 |
72 |
73 |
74 |
80 |
81 |
82 | {CONFIGURATION.profile.name}
83 |
84 |
85 | Email
86 |
87 |
88 |
89 |
90 |
91 | {MENU_OPTIONS.map((option) => (
92 |
111 | ))}
112 |
113 |
114 |
117 |
118 |
119 | >
120 | );
121 | }
122 |
--------------------------------------------------------------------------------
/src/layouts/dashboard/BTCprice.jsx:
--------------------------------------------------------------------------------
1 | import React, { useCallback, useEffect } from 'react';
2 | import useWebSocket from 'react-use-websocket';
3 |
4 | import { Box, Typography, Stack, Grid } from '@mui/material';
5 | import { fCurrency, fPercent } from '../../utils/formatNumber';
6 |
7 | const socketUrl = 'wss://fstream.binance.com/stream';
8 |
9 | const BTCprice = () => {
10 | const { sendJsonMessage, lastJsonMessage } = useWebSocket(socketUrl);
11 |
12 | useEffect(() => {
13 | console.log('Opening websocket for BTC data');
14 | subscribeWebsocket();
15 |
16 | return () => {
17 | console.log('Closing websocket for BTC data');
18 | unsubscribeWebsocket();
19 | };
20 | }, []);
21 |
22 | const subscribeWebsocket = useCallback(
23 | () =>
24 | sendJsonMessage({
25 | method: 'SUBSCRIBE',
26 | params: ['btcusdt@ticker'],
27 | id: 1
28 | }),
29 | [sendJsonMessage]
30 | );
31 |
32 | const unsubscribeWebsocket = useCallback(
33 | () =>
34 | sendJsonMessage({
35 | method: 'UNSUBSCRIBE',
36 | params: ['btcusdt@ticker'],
37 | id: 1
38 | }),
39 | [sendJsonMessage]
40 | );
41 |
42 | return (
43 |
44 |
54 |
59 |
60 |
61 |
62 |
63 | {fCurrency(Math.round(lastJsonMessage?.data?.c))}
64 |
65 |
66 | 0 ? 'green' : 'red'} variant="overline">
67 | {fPercent(lastJsonMessage?.data?.P)}
68 |
69 |
70 |
71 |
72 |
73 | );
74 | };
75 |
76 | export default BTCprice;
77 |
--------------------------------------------------------------------------------
/src/layouts/dashboard/DashboardNavbar.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import { Icon } from '@iconify/react';
3 | import menu2Fill from '@iconify/icons-eva/menu-2-fill';
4 | // material
5 | import { alpha, styled } from '@mui/material/styles';
6 | import { AppBar, Toolbar, IconButton } from '@mui/material';
7 | // components
8 | import { MHidden } from '../../components/@material-extend';
9 | //
10 | // import Searchbar from './Searchbar';
11 | // import AccountPopover from './AccountPopover';
12 | // import LanguagePopover from './LanguagePopover';
13 | // import NotificationsPopover from './NotificationsPopover';
14 |
15 | // ----------------------------------------------------------------------
16 |
17 | const DRAWER_WIDTH = 280;
18 |
19 | const RootStyle = styled(AppBar)(({ theme }) => ({
20 | boxShadow: 'none',
21 | backdropFilter: 'blur(6px)',
22 | WebkitBackdropFilter: 'blur(6px)', // Fix on Mobile
23 | backgroundColor: alpha(theme.palette.background.default, 0.72),
24 | [theme.breakpoints.up('lg')]: {
25 | width: `calc(100% - ${DRAWER_WIDTH + 1}px)`
26 | }
27 | }));
28 |
29 | const ToolbarStyle = styled(Toolbar)(({ theme }) => ({
30 | minHeight: 0,
31 | [theme.breakpoints.up('lg')]: {
32 | minHeight: 0,
33 | padding: theme.spacing(0, 5)
34 | }
35 | }));
36 |
37 | // ----------------------------------------------------------------------
38 |
39 | DashboardNavbar.propTypes = {
40 | onOpenSidebar: PropTypes.func
41 | };
42 |
43 | export default function DashboardNavbar({ onOpenSidebar }) {
44 | return (
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | );
55 | }
56 |
--------------------------------------------------------------------------------
/src/layouts/dashboard/DashboardSidebar.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import { useEffect } from 'react';
3 | import { Link as RouterLink, useLocation } from 'react-router-dom';
4 | // material
5 | import { styled } from '@mui/material/styles';
6 | import { Box, Link, Drawer, Typography, Avatar } from '@mui/material';
7 | // components
8 | import Logo from '../../components/Logo';
9 | import Scrollbar from '../../components/Scrollbar';
10 | import NavSection from '../../components/NavSection';
11 | import { MHidden } from '../../components/@material-extend';
12 | //
13 | import sidebarConfig from './SidebarConfig';
14 | import profilePicture from '../../assets/avatar_default.jpg';
15 | import BTCprice from './BTCprice';
16 | import { CONFIGURATION } from '../../config/api-keys';
17 |
18 | // ----------------------------------------------------------------------
19 |
20 | const DRAWER_WIDTH = 280;
21 |
22 | const RootStyle = styled('div')(({ theme }) => ({
23 | [theme.breakpoints.up('lg')]: {
24 | flexShrink: 0,
25 | width: DRAWER_WIDTH
26 | }
27 | }));
28 |
29 | const AccountStyle = styled('div')(({ theme }) => ({
30 | display: 'flex',
31 | alignItems: 'center',
32 | padding: theme.spacing(2, 2.5),
33 | borderRadius: theme.shape.borderRadiusSm,
34 | backgroundColor: theme.palette.grey[200]
35 | }));
36 |
37 | // ----------------------------------------------------------------------
38 |
39 | DashboardSidebar.propTypes = {
40 | isOpenSidebar: PropTypes.bool,
41 | onCloseSidebar: PropTypes.func
42 | };
43 |
44 | export default function DashboardSidebar({ isOpenSidebar, onCloseSidebar }) {
45 | const { pathname } = useLocation();
46 |
47 | useEffect(() => {
48 | if (isOpenSidebar) {
49 | onCloseSidebar();
50 | }
51 | // eslint-disable-next-line react-hooks/exhaustive-deps
52 | }, [pathname]);
53 |
54 | const renderContent = (
55 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | {CONFIGURATION.profile.name}
74 |
75 |
76 | Crypto Trader
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 | );
90 |
91 | return (
92 |
93 |
94 |
101 | {renderContent}
102 |
103 |
104 |
105 |
106 |
116 | {renderContent}
117 |
118 |
119 |
120 | );
121 | }
122 |
--------------------------------------------------------------------------------
/src/layouts/dashboard/SidebarConfig.js:
--------------------------------------------------------------------------------
1 | import { Icon } from '@iconify/react';
2 | import pieChart2Fill from '@iconify/icons-eva/pie-chart-2-fill';
3 | import fileTextFill from '@iconify/icons-eva/file-text-fill';
4 |
5 | // ----------------------------------------------------------------------
6 |
7 | const getIcon = (name) => ;
8 |
9 | const sidebarConfig = [
10 | {
11 | title: 'dashboard',
12 | path: '/dashboard/app',
13 | icon: getIcon(pieChart2Fill)
14 | },
15 | {
16 | title: 'Order history',
17 | path: '/dashboard/orderHistory',
18 | icon: getIcon(fileTextFill)
19 | }
20 | // {
21 | // title: 'login',
22 | // path: '/login',
23 | // icon: getIcon(lockFill)
24 | // },
25 | // {
26 | // title: 'register',
27 | // path: '/register',
28 | // icon: getIcon(personAddFill)
29 | // },
30 | // {
31 | // title: 'Not found',
32 | // path: '/404',
33 | // icon: getIcon(alertTriangleFill)
34 | // }
35 | ];
36 |
37 | export default sidebarConfig;
38 |
--------------------------------------------------------------------------------
/src/layouts/dashboard/Summary.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Grid } from '@mui/material';
3 | import { sum, flatten } from 'lodash';
4 | import { useRecoilValue } from 'recoil';
5 | import { format } from 'date-fns';
6 |
7 | import SummaryBox from '../../components/_dashboard/SummaryBox';
8 | import { fCurrency, fNumber } from '../../utils/formatNumber';
9 | import { tradesAtom, incomesAtom, accountAtom } from '../../recoil/atoms';
10 | import { getPercentIncrease } from '../../utils/functions';
11 |
12 | const Summary = () => {
13 | const trades = useRecoilValue(tradesAtom);
14 | const incomes = useRecoilValue(incomesAtom);
15 | const account = useRecoilValue(accountAtom);
16 |
17 | const today = format(new Date(), 'MM/dd/yyyy');
18 | const incomeToday = incomes[today];
19 | const tradesToday = trades[today];
20 |
21 | const profitToday = sum(incomeToday?.map((inc) => JSON.parse(inc?.income)));
22 | const weekProfit = sum(flatten(Object.values(incomes)).map((inc) => JSON.parse(inc?.income)));
23 |
24 | const tradesNumber = {};
25 |
26 | tradesToday?.forEach((trade) => {
27 | tradesNumber[trade.symbol] = (tradesNumber[trade.symbol] || 0) + 1;
28 | });
29 |
30 | const sortedPerformersOfToday = Object.entries(tradesNumber).sort(
31 | (prev, next) => prev[1] - next[1]
32 | );
33 |
34 | return (
35 | <>
36 |
37 |
44 |
45 |
46 |
47 |
54 |
55 |
56 |
62 |
63 |
64 |
70 |
71 |
72 |
78 |
79 |
80 |
86 |
87 | >
88 | );
89 | };
90 |
91 | export default Summary;
92 |
--------------------------------------------------------------------------------
/src/layouts/dashboard/index.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { Outlet } from 'react-router-dom';
3 | import { Grid, Typography } from '@mui/material';
4 | import { useRecoilValue } from 'recoil';
5 |
6 | // material
7 | import { styled } from '@mui/material/styles';
8 | //
9 | import DashboardNavbar from './DashboardNavbar';
10 | import DashboardSidebar from './DashboardSidebar';
11 | import { fDateTime } from '../../utils/formatTime';
12 | import { updateTimeAtom } from '../../recoil/atoms';
13 |
14 | // ----------------------------------------------------------------------
15 |
16 | const APP_BAR_MOBILE = 16;
17 | const APP_BAR_DESKTOP = 8;
18 |
19 | const RootStyle = styled('div')({
20 | display: 'flex',
21 | minHeight: '100%',
22 | overflow: 'hidden'
23 | });
24 |
25 | const MainStyle = styled('div')(({ theme }) => ({
26 | flexGrow: 1,
27 | overflow: 'auto',
28 | minHeight: '100%',
29 | paddingTop: APP_BAR_MOBILE + 24,
30 | paddingBottom: theme.spacing(10),
31 | [theme.breakpoints.up('lg')]: {
32 | paddingTop: APP_BAR_DESKTOP + 24,
33 | paddingLeft: theme.spacing(2),
34 | paddingRight: theme.spacing(2)
35 | }
36 | }));
37 |
38 | // ----------------------------------------------------------------------
39 |
40 | export default function DashboardLayout() {
41 | const [open, setOpen] = useState(false);
42 | const updateTime = useRecoilValue(updateTimeAtom);
43 |
44 | return (
45 |
46 | setOpen(true)} />
47 |
48 | setOpen(false)} />
49 |
50 |
51 |
52 |
53 | Last update: {fDateTime(updateTime)}
54 |
55 |
56 |
57 |
58 |
59 | );
60 | }
61 |
--------------------------------------------------------------------------------
/src/pages/DashboardApp.js:
--------------------------------------------------------------------------------
1 | // material
2 | import { Grid, Container } from '@mui/material';
3 |
4 | // components
5 | import Page from '../components/Page';
6 | import {
7 | // AppTasks,
8 | // AppNewsUpdate,
9 | LastOrders,
10 | PositionsRepartition,
11 | PerformanceOverview,
12 | // AppTrafficBySite,
13 | // AppCurrentSubject,
14 | TopPerformers
15 | } from '../components/_dashboard/app';
16 | import Summary from '../layouts/dashboard/Summary';
17 |
18 | // ----------------------------------------------------------------------
19 |
20 | const DashboardApp = () => (
21 |
22 |
23 | {/*
24 | Hi, Welcome back
25 | */}
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | {/*
46 |
47 | */}
48 |
49 | {/*
50 |
51 | */}
52 |
53 | {/*
54 |
55 |
56 |
57 |
58 |
59 | */}
60 |
61 |
62 |
63 | );
64 |
65 | export default DashboardApp;
66 |
--------------------------------------------------------------------------------
/src/pages/OrderHistory.js:
--------------------------------------------------------------------------------
1 | import { filter, orderBy, flatten } from 'lodash';
2 | import { useState } from 'react';
3 | import { useRecoilValue } from 'recoil';
4 |
5 | // material
6 | import {
7 | Card,
8 | Table,
9 | Stack,
10 | // Avatar,
11 | // Button,
12 | // Checkbox,
13 | TableRow,
14 | TableBody,
15 | TableCell,
16 | Container,
17 | Typography,
18 | TableContainer,
19 | TablePagination
20 | } from '@mui/material';
21 | // components
22 | import Page from '../components/Page';
23 | import Label from '../components/Label';
24 | import Scrollbar from '../components/Scrollbar';
25 | import SearchNotFound from '../components/SearchNotFound';
26 | import { OrderListHead, OrderListToolbar } from '../components/_dashboard/order';
27 | //
28 |
29 | import { fDateTime } from '../utils/formatTime';
30 | import { fCurrency } from '../utils/formatNumber';
31 | import { tradesAtom } from '../recoil/atoms';
32 |
33 | // ----------------------------------------------------------------------
34 |
35 | const TABLE_HEAD = [
36 | { id: 'symbol', label: 'Symbol' },
37 | { id: 'side', label: 'Side' },
38 | { id: 'time', label: 'Time' },
39 | { id: 'pnl', label: 'Realized PnL' },
40 | { id: 'price', label: 'Price' },
41 | { id: 'quoteQty', label: 'Quote quantity' }
42 | ];
43 |
44 | // ----------------------------------------------------------------------
45 |
46 | function descendingComparator(a, b, orderedBy) {
47 | if (b[orderedBy] < a[orderedBy]) {
48 | return -1;
49 | }
50 | if (b[orderedBy] > a[orderedBy]) {
51 | return 1;
52 | }
53 | return 0;
54 | }
55 |
56 | function getComparator(order, orderedBy) {
57 | return order === 'desc'
58 | ? (a, b) => descendingComparator(a, b, orderedBy)
59 | : (a, b) => -descendingComparator(a, b, orderedBy);
60 | }
61 |
62 | function applySortFilter(array, comparator, query) {
63 | const stabilizedThis = array.map((el, index) => [el, index]);
64 | stabilizedThis.sort((a, b) => {
65 | const order = comparator(a[0], b[0]);
66 | if (order !== 0) return order;
67 | return a[1] - b[1];
68 | });
69 | if (query) {
70 | return filter(
71 | array,
72 | (_order) => _order.symbol.toLowerCase().indexOf(query.toLowerCase()) !== -1
73 | );
74 | }
75 | return stabilizedThis.map((el) => el[0]);
76 | }
77 |
78 | const OrderHistory = () => {
79 | const trades = useRecoilValue(tradesAtom);
80 |
81 | const [page, setPage] = useState(0);
82 | const [order, setOrder] = useState('asc');
83 | const [selected, setSelected] = useState([]);
84 | const [orderedBy, setOrderBy] = useState('name');
85 | const [filterName, setFilterName] = useState('');
86 | const [rowsPerPage, setRowsPerPage] = useState(25);
87 |
88 | const orderedTrades = orderBy(flatten(Object.values(trades)), ['time'], ['desc']);
89 |
90 | const handleRequestSort = (event, property) => {
91 | const isAsc = orderedBy === property && order === 'asc';
92 | setOrder(isAsc ? 'desc' : 'asc');
93 | setOrderBy(property);
94 | };
95 |
96 | const handleSelectAllClick = (event) => {
97 | if (event.target.checked) {
98 | const newSelecteds = orderedTrades.map((n) => n.symbol);
99 | setSelected(newSelecteds);
100 | return;
101 | }
102 | setSelected([]);
103 | };
104 |
105 | const handleChangePage = (event, newPage) => {
106 | setPage(newPage);
107 | };
108 |
109 | const handleChangeRowsPerPage = (event) => {
110 | setRowsPerPage(parseInt(event.target.value, 10));
111 | setPage(0);
112 | };
113 |
114 | const handleFilterByName = (event) => {
115 | setFilterName(event.target.value);
116 | };
117 |
118 | const emptyRows = page > 0 ? Math.max(0, (1 + page) * rowsPerPage - orderedTrades.length) : 0;
119 |
120 | const filteredOrders = applySortFilter(
121 | orderedTrades,
122 | getComparator(order, orderedBy),
123 | filterName
124 | );
125 |
126 | const isOrderNotFound = filteredOrders.length === 0;
127 |
128 | return (
129 |
130 |
131 |
132 |
133 | Order history
134 |
135 |
136 |
137 |
138 |
143 |
144 |
145 |
146 |
147 |
156 |
157 | {filteredOrders
158 | .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
159 | .map((row) => {
160 | const { id, symbol, buyer, realizedPnl, time, quoteQty, price } = row;
161 | const isItemSelected = selected.indexOf(symbol) !== -1;
162 |
163 | return (
164 |
172 |
173 |
174 | {symbol}
175 |
176 |
177 |
178 |
181 |
182 | {fDateTime(time)}
183 | {fCurrency(realizedPnl)}
184 | {price}
185 | {quoteQty}
186 |
187 | );
188 | })}
189 | {emptyRows > 0 && (
190 |
191 |
192 |
193 | )}
194 |
195 | {isOrderNotFound && (
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 | )}
204 |
205 |
206 |
207 |
208 |
217 |
218 |
219 |
220 | );
221 | };
222 |
223 | export default OrderHistory;
224 |
--------------------------------------------------------------------------------
/src/pages/Page404.js:
--------------------------------------------------------------------------------
1 | import { motion } from 'framer-motion';
2 | import { Link as RouterLink } from 'react-router-dom';
3 | // material
4 | import { styled } from '@mui/material/styles';
5 | import { Box, Button, Typography, Container } from '@mui/material';
6 | // components
7 | import { MotionContainer, varBounceIn } from '../components/animate';
8 | import Page from '../components/Page';
9 |
10 | // ----------------------------------------------------------------------
11 |
12 | const RootStyle = styled(Page)(({ theme }) => ({
13 | display: 'flex',
14 | minHeight: '100%',
15 | alignItems: 'center',
16 | paddingTop: theme.spacing(15),
17 | paddingBottom: theme.spacing(10)
18 | }));
19 |
20 | // ----------------------------------------------------------------------
21 |
22 | export default function Page404() {
23 | return (
24 |
25 |
26 |
27 |
28 |
29 |
30 | Sorry, page not found!
31 |
32 |
33 |
34 | Sorry, we couldn’t find the page you’re looking for. Perhaps you’ve mistyped the URL?
35 | Be sure to check your spelling.
36 |
37 |
38 |
39 |
44 |
45 |
46 |
49 |
50 |
51 |
52 |
53 | );
54 | }
55 |
--------------------------------------------------------------------------------
/src/recoil/atoms/accountAtom.js:
--------------------------------------------------------------------------------
1 | import { atom } from 'recoil';
2 |
3 | const accountAtom = atom({
4 | key: 'accountState',
5 | default: {}
6 | });
7 |
8 | export default accountAtom;
9 |
--------------------------------------------------------------------------------
/src/recoil/atoms/incomesAtom.js:
--------------------------------------------------------------------------------
1 | import { atom } from 'recoil';
2 | import { generateLastNdates } from '../../utils/formatTime';
3 |
4 | const generateEmptyIncomes = () => {
5 | const lastWeekDates = generateLastNdates(7, 'MM/dd/yyyy');
6 | const emptyIncomes = {};
7 |
8 | lastWeekDates.forEach((d) => {
9 | emptyIncomes[d] = [];
10 | });
11 |
12 | return emptyIncomes;
13 | };
14 |
15 | const incomesAtom = atom({
16 | key: 'incomesState',
17 | default: generateEmptyIncomes()
18 | });
19 |
20 | export default incomesAtom;
21 |
--------------------------------------------------------------------------------
/src/recoil/atoms/index.js:
--------------------------------------------------------------------------------
1 | export { default as tradesAtom } from './tradesAtom';
2 | export { default as incomesAtom } from './incomesAtom';
3 | export { default as accountAtom } from './accountAtom';
4 | export { default as updateTimeAtom } from './updateTimeAtom';
5 |
--------------------------------------------------------------------------------
/src/recoil/atoms/tradesAtom.js:
--------------------------------------------------------------------------------
1 | import { atom } from 'recoil';
2 | import { generateLastNdates } from '../../utils/formatTime';
3 |
4 | const generateEmptyTrades = () => {
5 | const lastWeekDates = generateLastNdates(7, 'MM/dd/yyyy');
6 | const emptyTrades = {};
7 |
8 | lastWeekDates.forEach((d) => {
9 | emptyTrades[d] = [];
10 | });
11 |
12 | return emptyTrades;
13 | };
14 |
15 | const tradesAtom = atom({
16 | key: 'tradesState',
17 | default: generateEmptyTrades()
18 | });
19 |
20 | export default tradesAtom;
21 |
--------------------------------------------------------------------------------
/src/recoil/atoms/updateTimeAtom.js:
--------------------------------------------------------------------------------
1 | import { atom } from 'recoil';
2 |
3 | const updateTimeAtom = atom({
4 | key: 'updateTimeState',
5 | default: new Date()
6 | });
7 |
8 | export default updateTimeAtom;
9 |
--------------------------------------------------------------------------------
/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = (onPerfEntry) => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/src/routes.js:
--------------------------------------------------------------------------------
1 | import { Navigate, useRoutes } from 'react-router-dom';
2 |
3 | // layouts
4 | import DashboardLayout from './layouts/dashboard';
5 | import LogoOnlyLayout from './layouts/LogoOnlyLayout';
6 | import DashboardApp from './pages/DashboardApp';
7 | import OrderHistory from './pages/OrderHistory';
8 | import NotFound from './pages/Page404';
9 |
10 | // ----------------------------------------------------------------------
11 |
12 | export default function Router() {
13 | return useRoutes([
14 | {
15 | path: '/dashboard',
16 | element: ,
17 | children: [
18 | { element: },
19 | {
20 | path: 'app',
21 | element:
22 | },
23 | { path: 'orderHistory', element: }
24 | ]
25 | },
26 | {
27 | path: '/',
28 | element: ,
29 | children: [
30 | { path: '404', element: },
31 | { path: '/', element: },
32 | { path: '*', element: }
33 | ]
34 | },
35 | { path: '*', element: }
36 | ]);
37 | }
38 |
--------------------------------------------------------------------------------
/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | /* @ts-nocheck */
3 |
4 | // This optional code is used to register a service worker.
5 | // register() is not called by default.
6 |
7 | // This lets the app load faster on subsequent visits in production, and gives
8 | // it offline capabilities. However, it also means that developers (and users)
9 | // will only see deployed updates on subsequent visits to a page, after all the
10 | // existing tabs open on the page have been closed, since previously cached
11 | // resources are updated in the background.
12 |
13 | // To learn more about the benefits of this model and instructions on how to
14 | // opt-in, read https://bit.ly/CRA-PWA
15 |
16 | const isLocalhost = Boolean(
17 | window.location.hostname === 'localhost' ||
18 | // [::1] is the IPv6 localhost address.
19 | window.location.hostname === '[::1]' ||
20 | // 127.0.0.0/8 are considered localhost for IPv4.
21 | window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/)
22 | );
23 |
24 | export function register(config) {
25 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
26 | // The URL constructor is available in all browsers that support SW.
27 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
28 | if (publicUrl.origin !== window.location.origin) {
29 | // Our service worker won't work if PUBLIC_URL is on a different origin
30 | // from what our page is served on. This might happen if a CDN is used to
31 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
32 | return;
33 | }
34 |
35 | window.addEventListener('load', () => {
36 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
37 |
38 | if (isLocalhost) {
39 | // This is running on localhost. Let's check if a service worker still exists or not.
40 | checkValidServiceWorker(swUrl, config);
41 |
42 | // Add some additional logging to localhost, pointing developers to the
43 | // service worker/PWA documentation.
44 | navigator.serviceWorker.ready.then(() => {
45 | console.log(
46 | 'This web app is being served cache-first by a service ' +
47 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
48 | );
49 | });
50 | } else {
51 | // Is not localhost. Just register service worker
52 | registerValidSW(swUrl, config);
53 | }
54 | });
55 | }
56 | }
57 |
58 | function registerValidSW(swUrl, config) {
59 | navigator.serviceWorker
60 | .register(swUrl)
61 | .then((registration) => {
62 | registration.onupdatefound = () => {
63 | const installingWorker = registration.installing;
64 | if (installingWorker == null) {
65 | return;
66 | }
67 | installingWorker.onstatechange = () => {
68 | if (installingWorker.state === 'installed') {
69 | if (navigator.serviceWorker.controller) {
70 | // At this point, the updated precached content has been fetched,
71 | // but the previous service worker will still serve the older
72 | // content until all client tabs are closed.
73 | console.log(
74 | 'New content is available and will be used when all ' +
75 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
76 | );
77 |
78 | // Execute callback
79 | if (config && config.onUpdate) {
80 | config.onUpdate(registration);
81 | }
82 | } else {
83 | // At this point, everything has been precached.
84 | // It's the perfect time to display a
85 | // "Content is cached for offline use." message.
86 | console.log('Content is cached for offline use.');
87 |
88 | // Execute callback
89 | if (config && config.onSuccess) {
90 | config.onSuccess(registration);
91 | }
92 | }
93 | }
94 | };
95 | };
96 | })
97 | .catch((error) => {
98 | console.error('Error during service worker registration:', error);
99 | });
100 | }
101 |
102 | function checkValidServiceWorker(swUrl, config) {
103 | // Check if the service worker can be found. If it can't reload the page.
104 | fetch(swUrl, {
105 | headers: { 'Service-Worker': 'script' }
106 | })
107 | .then((response) => {
108 | // Ensure service worker exists, and that we really are getting a JS file.
109 | const contentType = response.headers.get('content-type');
110 | if (
111 | response.status === 404 ||
112 | (contentType != null && contentType.indexOf('javascript') === -1)
113 | ) {
114 | // No service worker found. Probably a different app. Reload the page.
115 | navigator.serviceWorker.ready.then((registration) => {
116 | registration.unregister().then(() => {
117 | window.location.reload();
118 | });
119 | });
120 | } else {
121 | // Service worker found. Proceed as normal.
122 | registerValidSW(swUrl, config);
123 | }
124 | })
125 | .catch(() => {
126 | console.log('No internet connection found. App is running in offline mode.');
127 | });
128 | }
129 |
130 | export function unregister() {
131 | if ('serviceWorker' in navigator) {
132 | navigator.serviceWorker.ready
133 | .then((registration) => {
134 | registration.unregister();
135 | })
136 | .catch((error) => {
137 | console.error(error.message);
138 | });
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/src/services/accountServices.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | import { BINANCE_HEADERS, getSignature, getUrl } from './utils';
4 |
5 | export const getUserAccount = async () => {
6 | const timestamp = Date.now();
7 |
8 | const params = {
9 | timestamp,
10 | signature: getSignature(`timestamp=${timestamp}`)
11 | };
12 |
13 | const response = await axios.request({
14 | url: getUrl('account'),
15 | method: 'get',
16 | params,
17 | headers: BINANCE_HEADERS
18 | });
19 |
20 | return response.data;
21 | };
22 |
--------------------------------------------------------------------------------
/src/services/incomeServices.js:
--------------------------------------------------------------------------------
1 | import { isEmpty, flatten } from 'lodash';
2 | import { format } from 'date-fns';
3 | import axios from 'axios';
4 |
5 | import { BINANCE_HEADERS, getSignature, getUrl } from './utils';
6 | import { generateLastNdates } from '../utils/formatTime';
7 |
8 | const ONE_DAY_TIMESTAMP = 60 * 60 * 24 * 1000;
9 | const CHUNK_NUMBER = 3;
10 |
11 | const generateNChunksRequest = (date) => {
12 | if (isEmpty(date)) return [];
13 |
14 | const timestamp = Date.now();
15 | const limit = 1000;
16 | const incomeType = 'REALIZED_PNL';
17 |
18 | const dateRequested = new Date(date).getTime();
19 | const requests = [];
20 |
21 | for (let i = 1; i <= CHUNK_NUMBER; i += 1) {
22 | const chunkTime = Math.round(ONE_DAY_TIMESTAMP / CHUNK_NUMBER);
23 |
24 | const startTime = dateRequested + (i - 1) * chunkTime;
25 | const endTime = startTime + chunkTime;
26 |
27 | if (startTime <= new Date().getTime()) {
28 | // Add the request only if the startTime is in the past
29 |
30 | const params = {
31 | incomeType,
32 | startTime,
33 | endTime,
34 | limit,
35 | timestamp,
36 | signature: getSignature(
37 | `incomeType=${incomeType}&startTime=${startTime}&endTime=${endTime}&limit=${limit}×tamp=${timestamp}`
38 | )
39 | };
40 |
41 | requests.push(
42 | axios.request({
43 | url: getUrl('income'),
44 | method: 'get',
45 | params,
46 | headers: BINANCE_HEADERS
47 | })
48 | );
49 | }
50 | }
51 |
52 | return requests;
53 | };
54 |
55 | export const getUserIncomesByDate = async (date) => {
56 | if (isEmpty(date)) return [];
57 |
58 | const response = await axios
59 | .all(generateNChunksRequest(date))
60 | .then(axios.spread((...responses) => flatten(responses.map((res) => res.data))))
61 | .catch((errors) => {
62 | console.error('Something went wrong when fetching getUserIncomesByDate, error: ', errors);
63 | });
64 |
65 | return response;
66 | };
67 |
68 | export const getUserIncomesOfTheDay = () => getUserIncomesByDate(format(new Date(), 'MM/dd/yyyy'));
69 |
70 | export const getIncomesOfTheWeek = async () => {
71 | const lastWeekDates = generateLastNdates(7, 'MM/dd/yyyy');
72 | const incomes = {};
73 |
74 | await axios
75 | .all(lastWeekDates.map((day) => getUserIncomesByDate(day)))
76 | .then(
77 | axios.spread((...responses) => {
78 | lastWeekDates.forEach((day, index) => {
79 | incomes[day] = responses[index];
80 | });
81 | })
82 | )
83 | .catch((errors) => {
84 | console.error('Something went wrong when fetching all the weekly incomes, error: ', errors);
85 | });
86 |
87 | return incomes;
88 | };
89 |
--------------------------------------------------------------------------------
/src/services/tradesServices.js:
--------------------------------------------------------------------------------
1 | import { isEmpty, flatten } from 'lodash';
2 | import { format } from 'date-fns';
3 | import axios from 'axios';
4 |
5 | import { BINANCE_HEADERS, getSignature, getUrl } from './utils';
6 | import { generateLastNdates } from '../utils/formatTime';
7 |
8 | const ONE_DAY_TIMESTAMP = 60 * 60 * 24 * 1000;
9 | const CHUNK_NUMBER = 8;
10 |
11 | const generateNChunksRequest = (date) => {
12 | if (isEmpty(date)) return [];
13 |
14 | const timestamp = Date.now();
15 | const limit = 1000;
16 |
17 | const dateRequested = new Date(date).getTime();
18 | const requests = [];
19 |
20 | for (let i = 1; i <= CHUNK_NUMBER; i += 1) {
21 | const chunkTime = Math.round(ONE_DAY_TIMESTAMP / CHUNK_NUMBER);
22 |
23 | const startTime = dateRequested + (i - 1) * chunkTime;
24 | const endTime = startTime + chunkTime;
25 |
26 | if (startTime <= new Date().getTime()) {
27 | // Add the request only if the startTime is in the past
28 |
29 | const params = {
30 | startTime,
31 | endTime,
32 | limit,
33 | timestamp,
34 | signature: getSignature(
35 | `startTime=${startTime}&endTime=${endTime}&limit=${limit}×tamp=${timestamp}`
36 | )
37 | };
38 |
39 | requests.push(
40 | axios.request({
41 | url: getUrl('trades'),
42 | method: 'get',
43 | params,
44 | headers: BINANCE_HEADERS
45 | })
46 | );
47 | }
48 | }
49 |
50 | return requests;
51 | };
52 |
53 | export const getUserTradesByDate = async (date) => {
54 | if (isEmpty(date)) return [];
55 |
56 | const response = await axios
57 | .all(generateNChunksRequest(date))
58 | .then(axios.spread((...responses) => flatten(responses.map((res) => res.data))))
59 | .catch((errors) => {
60 | console.error('Something went wrong when fetching getUserTradesByDate, error: ', errors);
61 | });
62 |
63 | return response;
64 | };
65 |
66 | export const getUserTradesOfTheDay = () => getUserTradesByDate(format(new Date(), 'MM/dd/yyyy'));
67 |
68 | export const getTradesOfTheWeek = async () => {
69 | const lastWeekDates = generateLastNdates(7, 'MM/dd/yyyy');
70 | const trades = {};
71 |
72 | await axios
73 | .all(lastWeekDates.map((day) => getUserTradesByDate(day)))
74 | .then(
75 | axios.spread((...responses) => {
76 | lastWeekDates.forEach((day, index) => {
77 | trades[day] = responses[index];
78 | });
79 | })
80 | )
81 | .catch((errors) => {
82 | console.error('Something went wrong when fetching all the weekly trades, error: ', errors);
83 | });
84 |
85 | return trades;
86 | };
87 |
--------------------------------------------------------------------------------
/src/services/utils.js:
--------------------------------------------------------------------------------
1 | import crypto from 'crypto';
2 |
3 | import { CONFIGURATION } from '../config/api-keys';
4 |
5 | const BINANCE_URLS = {
6 | spot: {
7 | trades: 'https://api.binance.com/api/v3/myTrades',
8 | income: 'https://api.binance.com/api/v3/account',
9 | account: 'https://api.binance.com/api/v3/account'
10 | },
11 | futures: {
12 | trades: 'https://fapi.binance.com/fapi/v1/userTrades',
13 | income: 'https://fapi.binance.com/fapi/v1/income',
14 | account: 'https://fapi.binance.com/fapi/v2/account'
15 | }
16 | };
17 |
18 | export const BINANCE_HEADERS = {
19 | Accept: 'Application/json',
20 | 'X-MBX-APIKEY': CONFIGURATION.binance.key
21 | };
22 |
23 | export const getSignature = (queryParams) =>
24 | crypto.createHmac('sha256', CONFIGURATION.binance.secret).update(queryParams).digest('hex');
25 |
26 | export const getUrl = (type) => BINANCE_URLS[CONFIGURATION.binance.exchangeType][type];
27 |
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom/extend-expect';
6 |
--------------------------------------------------------------------------------
/src/theme/breakpoints.js:
--------------------------------------------------------------------------------
1 | const breakpoints = {
2 | values: {
3 | xs: 0,
4 | sm: 600,
5 | md: 900,
6 | lg: 1200,
7 | xl: 1536
8 | }
9 | };
10 |
11 | export default breakpoints;
12 |
--------------------------------------------------------------------------------
/src/theme/globalStyles.js:
--------------------------------------------------------------------------------
1 | // material
2 | import { useTheme } from '@mui/material/styles';
3 | import { GlobalStyles as GlobalThemeStyles } from '@mui/material';
4 |
5 | // ----------------------------------------------------------------------
6 |
7 | export default function GlobalStyles() {
8 | const theme = useTheme();
9 |
10 | return (
11 |
74 | );
75 | }
76 |
--------------------------------------------------------------------------------
/src/theme/index.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import { useMemo } from 'react';
3 | // material
4 | import { CssBaseline } from '@mui/material';
5 | import { ThemeProvider, createTheme, StyledEngineProvider } from '@mui/material/styles';
6 | //
7 | import shape from './shape';
8 | import palette from './palette';
9 | import typography from './typography';
10 | import componentsOverride from './overrides';
11 | import shadows, { customShadows } from './shadows';
12 |
13 | // ----------------------------------------------------------------------
14 |
15 | ThemeConfig.propTypes = {
16 | children: PropTypes.node
17 | };
18 |
19 | export default function ThemeConfig({ children }) {
20 | const themeOptions = useMemo(
21 | () => ({
22 | palette,
23 | shape,
24 | typography,
25 | shadows,
26 | customShadows
27 | }),
28 | []
29 | );
30 |
31 | const theme = createTheme(themeOptions);
32 | theme.components = componentsOverride(theme);
33 |
34 | return (
35 |
36 |
37 |
38 | {children}
39 |
40 |
41 | );
42 | }
43 |
--------------------------------------------------------------------------------
/src/theme/overrides/Autocomplete.js:
--------------------------------------------------------------------------------
1 | // ----------------------------------------------------------------------
2 |
3 | export default function Autocomplete(theme) {
4 | return {
5 | MuiAutocomplete: {
6 | styleOverrides: {
7 | paper: {
8 | boxShadow: theme.customShadows.z20
9 | }
10 | }
11 | }
12 | };
13 | }
14 |
--------------------------------------------------------------------------------
/src/theme/overrides/Backdrop.js:
--------------------------------------------------------------------------------
1 | import { alpha } from '@mui/material/styles';
2 |
3 | // ----------------------------------------------------------------------
4 |
5 | export default function Backdrop(theme) {
6 | const varLow = alpha(theme.palette.grey[900], 0.48);
7 | const varHigh = alpha(theme.palette.grey[900], 1);
8 |
9 | return {
10 | MuiBackdrop: {
11 | styleOverrides: {
12 | root: {
13 | background: [
14 | `rgb(22,28,36)`,
15 | `-moz-linear-gradient(75deg, ${varLow} 0%, ${varHigh} 100%)`,
16 | `-webkit-linear-gradient(75deg, ${varLow} 0%, ${varHigh} 100%)`,
17 | `linear-gradient(75deg, ${varLow} 0%, ${varHigh} 100%)`
18 | ],
19 | '&.MuiBackdrop-invisible': {
20 | background: 'transparent'
21 | }
22 | }
23 | }
24 | }
25 | };
26 | }
27 |
--------------------------------------------------------------------------------
/src/theme/overrides/Button.js:
--------------------------------------------------------------------------------
1 | // ----------------------------------------------------------------------
2 |
3 | export default function Button(theme) {
4 | return {
5 | MuiButton: {
6 | styleOverrides: {
7 | root: {
8 | '&:hover': {
9 | boxShadow: 'none'
10 | }
11 | },
12 | sizeLarge: {
13 | height: 48
14 | },
15 | containedInherit: {
16 | color: theme.palette.grey[800],
17 | boxShadow: theme.customShadows.z8,
18 | '&:hover': {
19 | backgroundColor: theme.palette.grey[400]
20 | }
21 | },
22 | containedPrimary: {
23 | boxShadow: theme.customShadows.primary
24 | },
25 | containedSecondary: {
26 | boxShadow: theme.customShadows.secondary
27 | },
28 | outlinedInherit: {
29 | border: `1px solid ${theme.palette.grey[500_32]}`,
30 | '&:hover': {
31 | backgroundColor: theme.palette.action.hover
32 | }
33 | },
34 | textInherit: {
35 | '&:hover': {
36 | backgroundColor: theme.palette.action.hover
37 | }
38 | }
39 | }
40 | }
41 | };
42 | }
43 |
--------------------------------------------------------------------------------
/src/theme/overrides/Card.js:
--------------------------------------------------------------------------------
1 | // ----------------------------------------------------------------------
2 |
3 | export default function Card(theme) {
4 | return {
5 | MuiCard: {
6 | styleOverrides: {
7 | root: {
8 | boxShadow: theme.customShadows.z16,
9 | borderRadius: theme.shape.borderRadiusMd,
10 | position: 'relative',
11 | zIndex: 0 // Fix Safari overflow: hidden with border radius
12 | }
13 | }
14 | },
15 | MuiCardHeader: {
16 | defaultProps: {
17 | titleTypographyProps: { variant: 'h6' },
18 | subheaderTypographyProps: { variant: 'body2' }
19 | },
20 | styleOverrides: {
21 | root: {
22 | padding: theme.spacing(3, 3, 0)
23 | }
24 | }
25 | },
26 | MuiCardContent: {
27 | styleOverrides: {
28 | root: {
29 | padding: theme.spacing(3)
30 | }
31 | }
32 | }
33 | };
34 | }
35 |
--------------------------------------------------------------------------------
/src/theme/overrides/IconButton.js:
--------------------------------------------------------------------------------
1 | // ----------------------------------------------------------------------
2 |
3 | export default function IconButton(theme) {
4 | return {
5 | MuiIconButton: {
6 | variants: [
7 | {
8 | props: { color: 'default' },
9 | style: {
10 | '&:hover': { backgroundColor: theme.palette.action.hover }
11 | }
12 | },
13 | {
14 | props: { color: 'inherit' },
15 | style: {
16 | '&:hover': { backgroundColor: theme.palette.action.hover }
17 | }
18 | }
19 | ],
20 |
21 | styleOverrides: {
22 | root: {}
23 | }
24 | }
25 | };
26 | }
27 |
--------------------------------------------------------------------------------
/src/theme/overrides/Input.js:
--------------------------------------------------------------------------------
1 | // ----------------------------------------------------------------------
2 |
3 | export default function Input(theme) {
4 | return {
5 | MuiInputBase: {
6 | styleOverrides: {
7 | root: {
8 | '&.Mui-disabled': {
9 | '& svg': { color: theme.palette.text.disabled }
10 | }
11 | },
12 | input: {
13 | '&::placeholder': {
14 | opacity: 1,
15 | color: theme.palette.text.disabled
16 | }
17 | }
18 | }
19 | },
20 | MuiInput: {
21 | styleOverrides: {
22 | underline: {
23 | '&:before': {
24 | borderBottomColor: theme.palette.grey[500_56]
25 | }
26 | }
27 | }
28 | },
29 | MuiFilledInput: {
30 | styleOverrides: {
31 | root: {
32 | backgroundColor: theme.palette.grey[500_12],
33 | '&:hover': {
34 | backgroundColor: theme.palette.grey[500_16]
35 | },
36 | '&.Mui-focused': {
37 | backgroundColor: theme.palette.action.focus
38 | },
39 | '&.Mui-disabled': {
40 | backgroundColor: theme.palette.action.disabledBackground
41 | }
42 | },
43 | underline: {
44 | '&:before': {
45 | borderBottomColor: theme.palette.grey[500_56]
46 | }
47 | }
48 | }
49 | },
50 | MuiOutlinedInput: {
51 | styleOverrides: {
52 | root: {
53 | '& .MuiOutlinedInput-notchedOutline': {
54 | borderColor: theme.palette.grey[500_32]
55 | },
56 | '&.Mui-disabled': {
57 | '& .MuiOutlinedInput-notchedOutline': {
58 | borderColor: theme.palette.action.disabledBackground
59 | }
60 | }
61 | }
62 | }
63 | }
64 | };
65 | }
66 |
--------------------------------------------------------------------------------
/src/theme/overrides/Lists.js:
--------------------------------------------------------------------------------
1 | // ----------------------------------------------------------------------
2 |
3 | export default function Lists(theme) {
4 | return {
5 | MuiListItemIcon: {
6 | styleOverrides: {
7 | root: {
8 | color: 'inherit',
9 | minWidth: 'auto',
10 | marginRight: theme.spacing(2)
11 | }
12 | }
13 | },
14 | MuiListItemAvatar: {
15 | styleOverrides: {
16 | root: {
17 | minWidth: 'auto',
18 | marginRight: theme.spacing(2)
19 | }
20 | }
21 | },
22 | MuiListItemText: {
23 | styleOverrides: {
24 | root: {
25 | marginTop: 0,
26 | marginBottom: 0
27 | },
28 | multiline: {
29 | marginTop: 0,
30 | marginBottom: 0
31 | }
32 | }
33 | }
34 | };
35 | }
36 |
--------------------------------------------------------------------------------
/src/theme/overrides/Paper.js:
--------------------------------------------------------------------------------
1 | // ----------------------------------------------------------------------
2 |
3 | export default function Paper() {
4 | return {
5 | MuiPaper: {
6 | defaultProps: {
7 | elevation: 0
8 | },
9 |
10 | styleOverrides: {
11 | root: {
12 | backgroundImage: 'none'
13 | }
14 | }
15 | }
16 | };
17 | }
18 |
--------------------------------------------------------------------------------
/src/theme/overrides/Tooltip.js:
--------------------------------------------------------------------------------
1 | // ----------------------------------------------------------------------
2 |
3 | export default function Tooltip(theme) {
4 | return {
5 | MuiTooltip: {
6 | styleOverrides: {
7 | tooltip: {
8 | backgroundColor: theme.palette.grey[800]
9 | },
10 | arrow: {
11 | color: theme.palette.grey[800]
12 | }
13 | }
14 | }
15 | };
16 | }
17 |
--------------------------------------------------------------------------------
/src/theme/overrides/Typography.js:
--------------------------------------------------------------------------------
1 | // ----------------------------------------------------------------------
2 |
3 | export default function Typography(theme) {
4 | return {
5 | MuiTypography: {
6 | styleOverrides: {
7 | paragraph: {
8 | marginBottom: theme.spacing(2)
9 | },
10 | gutterBottom: {
11 | marginBottom: theme.spacing(1)
12 | }
13 | }
14 | }
15 | };
16 | }
17 |
--------------------------------------------------------------------------------
/src/theme/overrides/index.js:
--------------------------------------------------------------------------------
1 | import { merge } from 'lodash';
2 | import Card from './Card';
3 | import Lists from './Lists';
4 | import Paper from './Paper';
5 | import Input from './Input';
6 | import Button from './Button';
7 | import Tooltip from './Tooltip';
8 | import Backdrop from './Backdrop';
9 | import Typography from './Typography';
10 | import IconButton from './IconButton';
11 | import Autocomplete from './Autocomplete';
12 |
13 | // ----------------------------------------------------------------------
14 |
15 | export default function ComponentsOverrides(theme) {
16 | return merge(
17 | Card(theme),
18 | Lists(theme),
19 | Paper(theme),
20 | Input(theme),
21 | Button(theme),
22 | Tooltip(theme),
23 | Backdrop(theme),
24 | Typography(theme),
25 | IconButton(theme),
26 | Autocomplete(theme)
27 | );
28 | }
29 |
--------------------------------------------------------------------------------
/src/theme/palette.js:
--------------------------------------------------------------------------------
1 | import { alpha } from '@mui/material/styles';
2 |
3 | // ----------------------------------------------------------------------
4 |
5 | function createGradient(color1, color2) {
6 | return `linear-gradient(to bottom, ${color1}, ${color2})`;
7 | }
8 |
9 | // SETUP COLORS
10 | const GREY = {
11 | 0: '#FFFFFF',
12 | 100: '#F9FAFB',
13 | 200: '#F4F6F8',
14 | 300: '#DFE3E8',
15 | 400: '#C4CDD5',
16 | 500: '#919EAB',
17 | 600: '#637381',
18 | 700: '#454F5B',
19 | 800: '#212B36',
20 | 900: '#161C24',
21 | 500_8: alpha('#919EAB', 0.08),
22 | 500_12: alpha('#919EAB', 0.12),
23 | 500_16: alpha('#919EAB', 0.16),
24 | 500_24: alpha('#919EAB', 0.24),
25 | 500_32: alpha('#919EAB', 0.32),
26 | 500_48: alpha('#919EAB', 0.48),
27 | 500_56: alpha('#919EAB', 0.56),
28 | 500_80: alpha('#919EAB', 0.8)
29 | };
30 |
31 | const PRIMARY = {
32 | lighter: '#C8FACD',
33 | light: '#5BE584',
34 | main: '#00AB55',
35 | dark: '#007B55',
36 | darker: '#005249',
37 | contrastText: '#fff'
38 | };
39 | const SECONDARY = {
40 | lighter: '#D6E4FF',
41 | light: '#84A9FF',
42 | main: '#3366FF',
43 | dark: '#1939B7',
44 | darker: '#091A7A',
45 | contrastText: '#fff'
46 | };
47 | const INFO = {
48 | lighter: '#D0F2FF',
49 | light: '#74CAFF',
50 | main: '#1890FF',
51 | dark: '#0C53B7',
52 | darker: '#04297A',
53 | contrastText: '#fff'
54 | };
55 | const SUCCESS = {
56 | lighter: '#E9FCD4',
57 | light: '#AAF27F',
58 | main: '#54D62C',
59 | dark: '#229A16',
60 | darker: '#08660D',
61 | contrastText: GREY[800]
62 | };
63 | const WARNING = {
64 | lighter: '#FFF7CD',
65 | light: '#FFE16A',
66 | main: '#FFC107',
67 | dark: '#B78103',
68 | darker: '#7A4F01',
69 | contrastText: GREY[800]
70 | };
71 | const ERROR = {
72 | lighter: '#FFE7D9',
73 | light: '#FFA48D',
74 | main: '#FF4842',
75 | dark: '#B72136',
76 | darker: '#7A0C2E',
77 | contrastText: '#fff'
78 | };
79 |
80 | const GRADIENTS = {
81 | primary: createGradient(PRIMARY.light, PRIMARY.main),
82 | info: createGradient(INFO.light, INFO.main),
83 | success: createGradient(SUCCESS.light, SUCCESS.main),
84 | warning: createGradient(WARNING.light, WARNING.main),
85 | error: createGradient(ERROR.light, ERROR.main)
86 | };
87 |
88 | const CHART_COLORS = {
89 | violet: ['#826AF9', '#9E86FF', '#D0AEFF', '#F7D2FF'],
90 | blue: ['#2D99FF', '#83CFFF', '#A5F3FF', '#CCFAFF'],
91 | green: ['#2CD9C5', '#60F1C8', '#A4F7CC', '#C0F2DC'],
92 | yellow: ['#FFE700', '#FFEF5A', '#FFF7AE', '#FFF3D6'],
93 | red: ['#FF6C40', '#FF8F6D', '#FFBD98', '#FFF2D4']
94 | };
95 |
96 | const palette = {
97 | common: { black: '#000', white: '#fff' },
98 | primary: { ...PRIMARY },
99 | secondary: { ...SECONDARY },
100 | info: { ...INFO },
101 | success: { ...SUCCESS },
102 | warning: { ...WARNING },
103 | error: { ...ERROR },
104 | grey: GREY,
105 | gradients: GRADIENTS,
106 | chart: CHART_COLORS,
107 | divider: GREY[500_24],
108 | text: { primary: GREY[800], secondary: GREY[600], disabled: GREY[500] },
109 | background: { paper: '#fff', default: '#fff', neutral: GREY[200] },
110 | action: {
111 | active: GREY[600],
112 | hover: GREY[500_8],
113 | selected: GREY[500_16],
114 | disabled: GREY[500_80],
115 | disabledBackground: GREY[500_24],
116 | focus: GREY[500_24],
117 | hoverOpacity: 0.08,
118 | disabledOpacity: 0.48
119 | }
120 | };
121 |
122 | export default palette;
123 |
--------------------------------------------------------------------------------
/src/theme/shadows.js:
--------------------------------------------------------------------------------
1 | // material
2 | import { alpha } from '@mui/material/styles';
3 | import palette from './palette';
4 |
5 | // ----------------------------------------------------------------------
6 |
7 | const LIGHT_MODE = palette.grey[500];
8 |
9 | const createShadow = (color) => {
10 | const transparent1 = alpha(color, 0.2);
11 | const transparent2 = alpha(color, 0.14);
12 | const transparent3 = alpha(color, 0.12);
13 | return [
14 | 'none',
15 | `0px 2px 1px -1px ${transparent1},0px 1px 1px 0px ${transparent2},0px 1px 3px 0px ${transparent3}`,
16 | `0px 3px 1px -2px ${transparent1},0px 2px 2px 0px ${transparent2},0px 1px 5px 0px ${transparent3}`,
17 | `0px 3px 3px -2px ${transparent1},0px 3px 4px 0px ${transparent2},0px 1px 8px 0px ${transparent3}`,
18 | `0px 2px 4px -1px ${transparent1},0px 4px 5px 0px ${transparent2},0px 1px 10px 0px ${transparent3}`,
19 | `0px 3px 5px -1px ${transparent1},0px 5px 8px 0px ${transparent2},0px 1px 14px 0px ${transparent3}`,
20 | `0px 3px 5px -1px ${transparent1},0px 6px 10px 0px ${transparent2},0px 1px 18px 0px ${transparent3}`,
21 | `0px 4px 5px -2px ${transparent1},0px 7px 10px 1px ${transparent2},0px 2px 16px 1px ${transparent3}`,
22 | `0px 5px 5px -3px ${transparent1},0px 8px 10px 1px ${transparent2},0px 3px 14px 2px ${transparent3}`,
23 | `0px 5px 6px -3px ${transparent1},0px 9px 12px 1px ${transparent2},0px 3px 16px 2px ${transparent3}`,
24 | `0px 6px 6px -3px ${transparent1},0px 10px 14px 1px ${transparent2},0px 4px 18px 3px ${transparent3}`,
25 | `0px 6px 7px -4px ${transparent1},0px 11px 15px 1px ${transparent2},0px 4px 20px 3px ${transparent3}`,
26 | `0px 7px 8px -4px ${transparent1},0px 12px 17px 2px ${transparent2},0px 5px 22px 4px ${transparent3}`,
27 | `0px 7px 8px -4px ${transparent1},0px 13px 19px 2px ${transparent2},0px 5px 24px 4px ${transparent3}`,
28 | `0px 7px 9px -4px ${transparent1},0px 14px 21px 2px ${transparent2},0px 5px 26px 4px ${transparent3}`,
29 | `0px 8px 9px -5px ${transparent1},0px 15px 22px 2px ${transparent2},0px 6px 28px 5px ${transparent3}`,
30 | `0px 8px 10px -5px ${transparent1},0px 16px 24px 2px ${transparent2},0px 6px 30px 5px ${transparent3}`,
31 | `0px 8px 11px -5px ${transparent1},0px 17px 26px 2px ${transparent2},0px 6px 32px 5px ${transparent3}`,
32 | `0px 9px 11px -5px ${transparent1},0px 18px 28px 2px ${transparent2},0px 7px 34px 6px ${transparent3}`,
33 | `0px 9px 12px -6px ${transparent1},0px 19px 29px 2px ${transparent2},0px 7px 36px 6px ${transparent3}`,
34 | `0px 10px 13px -6px ${transparent1},0px 20px 31px 3px ${transparent2},0px 8px 38px 7px ${transparent3}`,
35 | `0px 10px 13px -6px ${transparent1},0px 21px 33px 3px ${transparent2},0px 8px 40px 7px ${transparent3}`,
36 | `0px 10px 14px -6px ${transparent1},0px 22px 35px 3px ${transparent2},0px 8px 42px 7px ${transparent3}`,
37 | `0px 11px 14px -7px ${transparent1},0px 23px 36px 3px ${transparent2},0px 9px 44px 8px ${transparent3}`,
38 | `0px 11px 15px -7px ${transparent1},0px 24px 38px 3px ${transparent2},0px 9px 46px 8px ${transparent3}`
39 | ];
40 | };
41 |
42 | const createCustomShadow = (color) => {
43 | const transparent = alpha(color, 0.24);
44 |
45 | return {
46 | z1: `0 1px 2px 0 ${transparent}`,
47 | z8: `0 8px 16px 0 ${transparent}`,
48 | z12: `0 0 2px 0 ${transparent}, 0 12px 24px 0 ${transparent}`,
49 | z16: `0 0 2px 0 ${transparent}, 0 16px 32px -4px ${transparent}`,
50 | z20: `0 0 2px 0 ${transparent}, 0 20px 40px -4px ${transparent}`,
51 | z24: `0 0 4px 0 ${transparent}, 0 24px 48px 0 ${transparent}`,
52 | primary: `0 8px 16px 0 ${alpha(palette.primary.main, 0.24)}`,
53 | secondary: `0 8px 16px 0 ${alpha(palette.secondary.main, 0.24)}`,
54 | info: `0 8px 16px 0 ${alpha(palette.info.main, 0.24)}`,
55 | success: `0 8px 16px 0 ${alpha(palette.success.main, 0.24)}`,
56 | warning: `0 8px 16px 0 ${alpha(palette.warning.main, 0.24)}`,
57 | error: `0 8px 16px 0 ${alpha(palette.error.main, 0.24)}`
58 | };
59 | };
60 |
61 | export const customShadows = createCustomShadow(LIGHT_MODE);
62 |
63 | const shadows = createShadow(LIGHT_MODE);
64 |
65 | export default shadows;
66 |
--------------------------------------------------------------------------------
/src/theme/shape.js:
--------------------------------------------------------------------------------
1 | // ----------------------------------------------------------------------
2 |
3 | const shape = {
4 | borderRadius: 8,
5 | borderRadiusSm: 12,
6 | borderRadiusMd: 16
7 | };
8 |
9 | export default shape;
10 |
--------------------------------------------------------------------------------
/src/theme/typography.js:
--------------------------------------------------------------------------------
1 | // ----------------------------------------------------------------------
2 |
3 | function pxToRem(value) {
4 | return `${value / 16}rem`;
5 | }
6 |
7 | function responsiveFontSizes({ sm, md, lg }) {
8 | return {
9 | '@media (min-width:600px)': {
10 | fontSize: pxToRem(sm)
11 | },
12 | '@media (min-width:900px)': {
13 | fontSize: pxToRem(md)
14 | },
15 | '@media (min-width:1200px)': {
16 | fontSize: pxToRem(lg)
17 | }
18 | };
19 | }
20 |
21 | const FONT_PRIMARY = 'Public Sans, sans-serif';
22 |
23 | const typography = {
24 | fontFamily: FONT_PRIMARY,
25 | fontWeightRegular: 400,
26 | fontWeightMedium: 600,
27 | fontWeightBold: 700,
28 | h1: {
29 | fontWeight: 700,
30 | lineHeight: 80 / 64,
31 | fontSize: pxToRem(40),
32 | ...responsiveFontSizes({ sm: 52, md: 58, lg: 64 })
33 | },
34 | h2: {
35 | fontWeight: 700,
36 | lineHeight: 64 / 48,
37 | fontSize: pxToRem(32),
38 | ...responsiveFontSizes({ sm: 40, md: 44, lg: 48 })
39 | },
40 | h3: {
41 | fontWeight: 700,
42 | lineHeight: 1.5,
43 | fontSize: pxToRem(24),
44 | ...responsiveFontSizes({ sm: 26, md: 30, lg: 32 })
45 | },
46 | h4: {
47 | fontWeight: 700,
48 | lineHeight: 1.5,
49 | fontSize: pxToRem(20),
50 | ...responsiveFontSizes({ sm: 20, md: 24, lg: 24 })
51 | },
52 | h5: {
53 | fontWeight: 700,
54 | lineHeight: 1.5,
55 | fontSize: pxToRem(18),
56 | ...responsiveFontSizes({ sm: 19, md: 20, lg: 20 })
57 | },
58 | h6: {
59 | fontWeight: 700,
60 | lineHeight: 28 / 18,
61 | fontSize: pxToRem(17),
62 | ...responsiveFontSizes({ sm: 18, md: 18, lg: 18 })
63 | },
64 | subtitle1: {
65 | fontWeight: 600,
66 | lineHeight: 1.5,
67 | fontSize: pxToRem(16)
68 | },
69 | subtitle2: {
70 | fontWeight: 600,
71 | lineHeight: 22 / 14,
72 | fontSize: pxToRem(14)
73 | },
74 | body1: {
75 | lineHeight: 1.5,
76 | fontSize: pxToRem(16)
77 | },
78 | body2: {
79 | lineHeight: 22 / 14,
80 | fontSize: pxToRem(14)
81 | },
82 | caption: {
83 | lineHeight: 1.5,
84 | fontSize: pxToRem(12)
85 | },
86 | overline: {
87 | fontWeight: 700,
88 | lineHeight: 1.5,
89 | fontSize: pxToRem(12),
90 | letterSpacing: 1.1,
91 | textTransform: 'uppercase'
92 | },
93 | button: {
94 | fontWeight: 700,
95 | lineHeight: 24 / 14,
96 | fontSize: pxToRem(14),
97 | textTransform: 'capitalize'
98 | }
99 | };
100 |
101 | export default typography;
102 |
--------------------------------------------------------------------------------
/src/utils/formatNumber.js:
--------------------------------------------------------------------------------
1 | import { replace } from 'lodash';
2 | import numeral from 'numeral';
3 |
4 | // ----------------------------------------------------------------------
5 |
6 | export function fCurrency(number) {
7 | return numeral(number).format(Number.isInteger(number) ? '$0,0' : '$0,0.00');
8 | }
9 |
10 | export function fPercent(number, f = '0.0%') {
11 | const res = numeral(number / 100).format(f);
12 | const indicator = number > 0 ? '+' : '';
13 |
14 | return indicator + res;
15 | }
16 |
17 | export function fNumber(number) {
18 | return numeral(number).format();
19 | }
20 |
21 | export function fShortenNumber(number) {
22 | return replace(numeral(number).format('0.00a'), '.00', '');
23 | }
24 |
25 | export function fData(number) {
26 | return numeral(number).format('0.0 b');
27 | }
28 |
--------------------------------------------------------------------------------
/src/utils/formatTime.js:
--------------------------------------------------------------------------------
1 | import { format, formatDistanceToNow } from 'date-fns';
2 |
3 | // ----------------------------------------------------------------------
4 |
5 | export function fDate(date) {
6 | return format(new Date(date), 'dd MMMM yyyy');
7 | }
8 |
9 | export function fDateTime(date) {
10 | return format(new Date(date), 'dd MMM yyyy HH:mm');
11 | }
12 |
13 | export function fDateTimeSuffix(date) {
14 | return format(new Date(date), 'dd/MM/yyyy hh:mm p');
15 | }
16 |
17 | export function fToNow(date) {
18 | return formatDistanceToNow(new Date(date), {
19 | addSuffix: true
20 | });
21 | }
22 |
23 | export const isToday = (someDate) => {
24 | const today = new Date();
25 | return (
26 | someDate.getDate() === today.getDate() &&
27 | someDate.getMonth() === today.getMonth() &&
28 | someDate.getFullYear() === today.getFullYear()
29 | );
30 | };
31 |
32 | export const generateLastNdates = (n, f = null) =>
33 | [...Array(n)].map((_, i) => {
34 | const d = new Date();
35 | d.setDate(d.getDate() - i);
36 | return f ? format(new Date(d), f) : d;
37 | });
38 |
--------------------------------------------------------------------------------
/src/utils/functions.js:
--------------------------------------------------------------------------------
1 | import { fPercent } from './formatNumber';
2 |
3 | export const getPercentIncrease = (amount, balance) => {
4 | const changePercent = (amount / (balance - amount)) * 100;
5 |
6 | return fPercent(changePercent, '0.00%');
7 | };
8 |
--------------------------------------------------------------------------------