diff --git a/.eslintrc.cjs b/.eslintrc.cjs
new file mode 100644
index 0000000..774281b
--- /dev/null
+++ b/.eslintrc.cjs
@@ -0,0 +1,23 @@
+module.exports = {
+ root: true,
+ env: { browser: true, es2020: true },
+ extends: [
+ 'eslint:recommended',
+ 'plugin:react/recommended',
+ 'plugin:react/jsx-runtime',
+ 'plugin:react-hooks/recommended',
+ ],
+ ignorePatterns: ['dist', '.eslintrc.cjs'],
+ parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
+ settings: { react: { version: '18.2' } },
+ plugins: ['react-refresh'],
+ rules: {
+ 'react/jsx-no-target-blank': 'off',
+ 'react-refresh/only-export-components': [
+ 'warn',
+ { allowConstantExport: true },
+ ],
+ 'no-unused-vars': 'off',
+ "react/prop-types": "off",
+ },
+}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a547bf3
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/README.md b/README.md
index e69de29..f768e33 100644
--- a/README.md
+++ b/README.md
@@ -0,0 +1,8 @@
+# React + Vite
+
+This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
+
+Currently, two official plugins are available:
+
+- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
+- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..dbb5b8e
--- /dev/null
+++ b/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ StockMate
+
+
+
+
+
+
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..b7da430
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,5599 @@
+{
+ "name": "invest-sns-react",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "invest-sns-react",
+ "version": "0.0.0",
+ "dependencies": {
+ "@reduxjs/toolkit": "^2.2.1",
+ "axios": "^1.6.7",
+ "bootstrap": "^5.3.3",
+ "bootstrap-icons": "^1.11.3",
+ "d3-format": "^3.1.0",
+ "d3-time-format": "^4.1.0",
+ "html-entities": "^2.5.2",
+ "nginx": "^1.0.8",
+ "react": "^18.2.0",
+ "react-bootstrap": "^2.10.1",
+ "react-bootstrap-icons": "^1.11.3",
+ "react-cookie": "^7.1.0",
+ "react-dom": "^18.2.0",
+ "react-financial-charts": "^2.0.1",
+ "react-icons": "^5.0.1",
+ "react-redux": "^9.1.0",
+ "react-router-dom": "^6.22.3",
+ "react-spinners": "^0.13.8",
+ "react-textarea-autosize": "^8.5.3",
+ "react-transition-group": "^4.4.5",
+ "redux": "^5.0.1",
+ "redux-logger": "^3.0.6",
+ "redux-persist": "^6.0.0",
+ "socket.io-client": "^4.7.5",
+ "styled-components": "^6.1.8",
+ "uuid": "^9.0.1"
+ },
+ "devDependencies": {
+ "@types/react": "^18.2.56",
+ "@types/react-dom": "^18.2.19",
+ "@vitejs/plugin-react": "^4.2.1",
+ "eslint": "^8.56.0",
+ "eslint-plugin-react": "^7.33.2",
+ "eslint-plugin-react-hooks": "^4.6.0",
+ "eslint-plugin-react-refresh": "^0.4.5",
+ "vite": "^5.2.6"
+ }
+ },
+ "node_modules/@aashutoshrathi/word-wrap": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz",
+ "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/@ampproject/remapping": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
+ "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.23.5",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz",
+ "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/highlight": "^7.23.4",
+ "chalk": "^2.4.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.23.5",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz",
+ "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.24.0",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.0.tgz",
+ "integrity": "sha512-fQfkg0Gjkza3nf0c7/w6Xf34BW4YvzNfACRLmmb7XRLa6XHdR+K9AlJlxneFfWYf6uhOzuzZVTjF/8KfndZANw==",
+ "dev": true,
+ "dependencies": {
+ "@ampproject/remapping": "^2.2.0",
+ "@babel/code-frame": "^7.23.5",
+ "@babel/generator": "^7.23.6",
+ "@babel/helper-compilation-targets": "^7.23.6",
+ "@babel/helper-module-transforms": "^7.23.3",
+ "@babel/helpers": "^7.24.0",
+ "@babel/parser": "^7.24.0",
+ "@babel/template": "^7.24.0",
+ "@babel/traverse": "^7.24.0",
+ "@babel/types": "^7.24.0",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.23.6",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz",
+ "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.23.6",
+ "@jridgewell/gen-mapping": "^0.3.2",
+ "@jridgewell/trace-mapping": "^0.3.17",
+ "jsesc": "^2.5.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.23.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz",
+ "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/compat-data": "^7.23.5",
+ "@babel/helper-validator-option": "^7.23.5",
+ "browserslist": "^4.22.2",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-environment-visitor": {
+ "version": "7.22.20",
+ "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz",
+ "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-function-name": {
+ "version": "7.23.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz",
+ "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/template": "^7.22.15",
+ "@babel/types": "^7.23.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-hoist-variables": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz",
+ "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.22.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz",
+ "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.22.15"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz",
+ "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-environment-visitor": "^7.22.20",
+ "@babel/helper-module-imports": "^7.22.15",
+ "@babel/helper-simple-access": "^7.22.5",
+ "@babel/helper-split-export-declaration": "^7.22.6",
+ "@babel/helper-validator-identifier": "^7.22.20"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.24.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz",
+ "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-simple-access": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz",
+ "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.22.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-split-export-declaration": {
+ "version": "7.22.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz",
+ "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.22.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz",
+ "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.22.20",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
+ "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.23.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz",
+ "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.24.0",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.0.tgz",
+ "integrity": "sha512-ulDZdc0Aj5uLc5nETsa7EPx2L7rM0YJM8r7ck7U73AXi7qOV44IHHRAYZHY6iU1rr3C5N4NtTmMRUJP6kwCWeA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/template": "^7.24.0",
+ "@babel/traverse": "^7.24.0",
+ "@babel/types": "^7.24.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/highlight": {
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz",
+ "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.22.20",
+ "chalk": "^2.4.2",
+ "js-tokens": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.24.0",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.0.tgz",
+ "integrity": "sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==",
+ "dev": true,
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.23.3.tgz",
+ "integrity": "sha512-qXRvbeKDSfwnlJnanVRp0SfuWE5DQhwQr5xtLBzp56Wabyo+4CMosF6Kfp+eOD/4FYpql64XVJ2W0pVLlJZxOQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.23.3.tgz",
+ "integrity": "sha512-91RS0MDnAWDNvGC6Wio5XYkyWI39FMFO+JK9+4AlgaTH+yWwVTsw7/sn6LK0lH7c5F+TFkpv/3LfCJ1Ydwof/g==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/runtime": {
+ "version": "7.24.0",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.0.tgz",
+ "integrity": "sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw==",
+ "dependencies": {
+ "regenerator-runtime": "^0.14.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.24.0",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz",
+ "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.23.5",
+ "@babel/parser": "^7.24.0",
+ "@babel/types": "^7.24.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.24.0",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.0.tgz",
+ "integrity": "sha512-HfuJlI8qq3dEDmNU5ChzzpZRWq+oxCZQyMzIMEqLho+AQnhMnKQUzH6ydo3RBl/YjPCuk68Y6s0Gx0AeyULiWw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.23.5",
+ "@babel/generator": "^7.23.6",
+ "@babel/helper-environment-visitor": "^7.22.20",
+ "@babel/helper-function-name": "^7.23.0",
+ "@babel/helper-hoist-variables": "^7.22.5",
+ "@babel/helper-split-export-declaration": "^7.22.6",
+ "@babel/parser": "^7.24.0",
+ "@babel/types": "^7.24.0",
+ "debug": "^4.3.1",
+ "globals": "^11.1.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.24.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz",
+ "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.23.4",
+ "@babel/helper-validator-identifier": "^7.22.20",
+ "to-fast-properties": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@emotion/is-prop-valid": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz",
+ "integrity": "sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==",
+ "dependencies": {
+ "@emotion/memoize": "^0.8.1"
+ }
+ },
+ "node_modules/@emotion/memoize": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz",
+ "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA=="
+ },
+ "node_modules/@emotion/unitless": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz",
+ "integrity": "sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw=="
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
+ "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz",
+ "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz",
+ "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz",
+ "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz",
+ "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz",
+ "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz",
+ "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz",
+ "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz",
+ "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz",
+ "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz",
+ "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz",
+ "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz",
+ "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz",
+ "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz",
+ "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz",
+ "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz",
+ "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz",
+ "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz",
+ "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz",
+ "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz",
+ "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz",
+ "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz",
+ "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
+ "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==",
+ "dev": true,
+ "dependencies": {
+ "eslint-visitor-keys": "^3.3.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.10.0",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz",
+ "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==",
+ "dev": true,
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
+ "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
+ "dev": true,
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^9.6.0",
+ "globals": "^13.19.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/globals": {
+ "version": "13.24.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
+ "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
+ "dev": true,
+ "dependencies": {
+ "type-fest": "^0.20.2"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "8.57.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz",
+ "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==",
+ "dev": true,
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@humanwhocodes/config-array": {
+ "version": "0.11.14",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
+ "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==",
+ "dev": true,
+ "dependencies": {
+ "@humanwhocodes/object-schema": "^2.0.2",
+ "debug": "^4.3.1",
+ "minimatch": "^3.0.5"
+ },
+ "engines": {
+ "node": ">=10.10.0"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/object-schema": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz",
+ "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==",
+ "dev": true
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
+ "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/set-array": "^1.2.1",
+ "@jridgewell/sourcemap-codec": "^1.4.10",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/set-array": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
+ "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.4.15",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
+ "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
+ "dev": true
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.25",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
+ "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@popperjs/core": {
+ "version": "2.11.8",
+ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
+ "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/popperjs"
+ }
+ },
+ "node_modules/@react-aria/ssr": {
+ "version": "3.9.2",
+ "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.2.tgz",
+ "integrity": "sha512-0gKkgDYdnq1w+ey8KzG9l+H5Z821qh9vVjztk55rUg71vTk/Eaebeir+WtzcLLwTjw3m/asIjx8Y59y1lJZhBw==",
+ "dependencies": {
+ "@swc/helpers": "^0.5.0"
+ },
+ "engines": {
+ "node": ">= 12"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
+ }
+ },
+ "node_modules/@react-financial-charts/annotations": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@react-financial-charts/annotations/-/annotations-2.0.0.tgz",
+ "integrity": "sha512-pnpZa2+h5p3mQ5vR5Z36LgVT0UaWe0V5JfvLdLa97NIBKsLZwzJNP//KXK5T3cdK2RmgHFNbEegpSxYgk4hLYw==",
+ "dependencies": {
+ "@react-financial-charts/core": "^2.0.0",
+ "@types/d3-scale": "^3.2.2"
+ },
+ "peerDependencies": {
+ "react": "^16.0.0 || ^17.0.0 || ^18.0.0",
+ "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/@react-financial-charts/axes": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@react-financial-charts/axes/-/axes-2.0.0.tgz",
+ "integrity": "sha512-VbkKsL+Hp92YpJmC5sDYq9D7z8CIjOtzNGdS+cuSALkU0NHD7XLhzO4GxiSUMMzrp99ecnBOv+2vdgNqEROYtg==",
+ "dependencies": {
+ "@react-financial-charts/core": "^2.0.0",
+ "@types/d3-scale": "^3.2.2",
+ "d3-array": "^2.9.1",
+ "d3-force": "^2.1.1",
+ "d3-scale": "^3.2.3",
+ "d3-selection": "^2.0.0"
+ },
+ "peerDependencies": {
+ "react": "^16.0.0 || ^17.0.0 || ^18.0.0",
+ "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/@react-financial-charts/axes/node_modules/d3-array": {
+ "version": "2.12.1",
+ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz",
+ "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==",
+ "dependencies": {
+ "internmap": "^1.0.0"
+ }
+ },
+ "node_modules/@react-financial-charts/axes/node_modules/internmap": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz",
+ "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="
+ },
+ "node_modules/@react-financial-charts/coordinates": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@react-financial-charts/coordinates/-/coordinates-2.0.0.tgz",
+ "integrity": "sha512-sC9sIrfFJ0GDmOXjZd5TBOToDS6FZKZtbZ0ZabNQzlIullHYRZrkZ4tyHl+Wsji7AmeVQjeE0LlAd1spqSVYEg==",
+ "dependencies": {
+ "@react-financial-charts/core": "^2.0.0",
+ "d3-format": "^2.0.0"
+ },
+ "peerDependencies": {
+ "react": "^16.0.0 || ^17.0.0 || ^18.0.0",
+ "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/@react-financial-charts/coordinates/node_modules/d3-format": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-2.0.0.tgz",
+ "integrity": "sha512-Ab3S6XuE/Q+flY96HXT0jOXcM4EAClYFnRGY5zsjRGNy6qCYrQsMffs7cV5Q9xejb35zxW5hf/guKw34kvIKsA=="
+ },
+ "node_modules/@react-financial-charts/core": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@react-financial-charts/core/-/core-2.0.0.tgz",
+ "integrity": "sha512-WduXh6Vf1N6qgbTl+U4MYBVuvk7MN8yaVB1aOy6FVFXqaZfQUaNIwk/s5LadgSIPjzK7g3NOdBG4StR9RFzy7w==",
+ "dependencies": {
+ "@types/d3-scale": "^3.2.2",
+ "d3-array": "^2.9.1",
+ "d3-scale": "^3.2.3",
+ "d3-selection": "^2.0.0",
+ "lodash.flattendeep": "^4.4.0"
+ },
+ "peerDependencies": {
+ "react": "^16.0.0 || ^17.0.0 || ^18.0.0",
+ "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/@react-financial-charts/core/node_modules/d3-array": {
+ "version": "2.12.1",
+ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz",
+ "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==",
+ "dependencies": {
+ "internmap": "^1.0.0"
+ }
+ },
+ "node_modules/@react-financial-charts/core/node_modules/internmap": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz",
+ "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="
+ },
+ "node_modules/@react-financial-charts/indicators": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@react-financial-charts/indicators/-/indicators-2.0.0.tgz",
+ "integrity": "sha512-6DU3EaA+11twkSrN/wZ9wDEquvKLTaELt4qJ3M8YIX8wWnKyOFWlyzGjmn77hEa7iRv1VuVFNITEviWzy2Nq+A==",
+ "dependencies": {
+ "d3-array": "^2.9.1",
+ "d3-scale": "^3.2.3"
+ },
+ "peerDependencies": {
+ "react": "^16.0.0 || ^17.0.0 || ^18.0.0",
+ "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/@react-financial-charts/indicators/node_modules/d3-array": {
+ "version": "2.12.1",
+ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz",
+ "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==",
+ "dependencies": {
+ "internmap": "^1.0.0"
+ }
+ },
+ "node_modules/@react-financial-charts/indicators/node_modules/internmap": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz",
+ "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="
+ },
+ "node_modules/@react-financial-charts/interactive": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@react-financial-charts/interactive/-/interactive-2.0.0.tgz",
+ "integrity": "sha512-wZHEqUGNHpsrBpidk49pY0sXtAhYi2gQVWUW7xJbgVP1iE+Jpwtodbl6EOf16MGx3sQJuL1qwJvn2wwFObgl1g==",
+ "dependencies": {
+ "@react-financial-charts/coordinates": "^2.0.0",
+ "@react-financial-charts/core": "^2.0.0",
+ "d3-array": "^2.9.1",
+ "d3-format": "^2.0.0",
+ "d3-interpolate": "^2.0.1",
+ "d3-path": "^2.0.0"
+ },
+ "peerDependencies": {
+ "react": "^16.0.0 || ^17.0.0 || ^18.0.0",
+ "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/@react-financial-charts/interactive/node_modules/d3-array": {
+ "version": "2.12.1",
+ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz",
+ "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==",
+ "dependencies": {
+ "internmap": "^1.0.0"
+ }
+ },
+ "node_modules/@react-financial-charts/interactive/node_modules/d3-format": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-2.0.0.tgz",
+ "integrity": "sha512-Ab3S6XuE/Q+flY96HXT0jOXcM4EAClYFnRGY5zsjRGNy6qCYrQsMffs7cV5Q9xejb35zxW5hf/guKw34kvIKsA=="
+ },
+ "node_modules/@react-financial-charts/interactive/node_modules/internmap": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz",
+ "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="
+ },
+ "node_modules/@react-financial-charts/scales": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@react-financial-charts/scales/-/scales-2.0.0.tgz",
+ "integrity": "sha512-8lFXl/mWZ36LsNVHEtTUSURycceNxCWUeU8zY8SsssjQK7FaFPO29KIff+UVYdnlH5QVHVfaUHI/NNWn5wkPIA==",
+ "dependencies": {
+ "@react-financial-charts/core": "^2.0.0",
+ "d3-array": "^2.9.1",
+ "d3-scale": "^3.2.3",
+ "d3-time-format": "^3.0.0"
+ },
+ "peerDependencies": {
+ "react": "^16.0.0 || ^17.0.0 || ^18.0.0",
+ "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/@react-financial-charts/scales/node_modules/d3-array": {
+ "version": "2.12.1",
+ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz",
+ "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==",
+ "dependencies": {
+ "internmap": "^1.0.0"
+ }
+ },
+ "node_modules/@react-financial-charts/scales/node_modules/d3-time": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-2.1.1.tgz",
+ "integrity": "sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ==",
+ "dependencies": {
+ "d3-array": "2"
+ }
+ },
+ "node_modules/@react-financial-charts/scales/node_modules/d3-time-format": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-3.0.0.tgz",
+ "integrity": "sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag==",
+ "dependencies": {
+ "d3-time": "1 - 2"
+ }
+ },
+ "node_modules/@react-financial-charts/scales/node_modules/internmap": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz",
+ "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="
+ },
+ "node_modules/@react-financial-charts/series": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@react-financial-charts/series/-/series-2.0.0.tgz",
+ "integrity": "sha512-hVQEjzKaSLRcPLX8vibtOZa776wbZ3cWYrUtVZ9eYZ7ROHJ2pZslE4GwFV7o74Hjdet7a5AmCwASm3NkeXsFaQ==",
+ "dependencies": {
+ "@react-financial-charts/core": "^2.0.0",
+ "@types/d3-scale": "^3.2.2",
+ "d3-array": "^2.9.1",
+ "d3-scale": "^3.2.3",
+ "d3-shape": "^2.0.0"
+ },
+ "peerDependencies": {
+ "react": "^16.0.0 || ^17.0.0 || ^18.0.0",
+ "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/@react-financial-charts/series/node_modules/d3-array": {
+ "version": "2.12.1",
+ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz",
+ "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==",
+ "dependencies": {
+ "internmap": "^1.0.0"
+ }
+ },
+ "node_modules/@react-financial-charts/series/node_modules/internmap": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz",
+ "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="
+ },
+ "node_modules/@react-financial-charts/tooltip": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@react-financial-charts/tooltip/-/tooltip-2.0.0.tgz",
+ "integrity": "sha512-EEAveu07nafgWVdZK7SNHAwqKVoKLKEjZBq7obhOGdnQ+1CSnQlyO+r6ISyabQt/XBpnASVCwqlK2NHewdoWxA==",
+ "dependencies": {
+ "@react-financial-charts/core": "^2.0.0",
+ "d3-array": "^2.9.1",
+ "d3-format": "^2.0.0",
+ "d3-time-format": "^3.0.0"
+ },
+ "peerDependencies": {
+ "react": "^16.0.0 || ^17.0.0 || ^18.0.0",
+ "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/@react-financial-charts/tooltip/node_modules/d3-array": {
+ "version": "2.12.1",
+ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz",
+ "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==",
+ "dependencies": {
+ "internmap": "^1.0.0"
+ }
+ },
+ "node_modules/@react-financial-charts/tooltip/node_modules/d3-format": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-2.0.0.tgz",
+ "integrity": "sha512-Ab3S6XuE/Q+flY96HXT0jOXcM4EAClYFnRGY5zsjRGNy6qCYrQsMffs7cV5Q9xejb35zxW5hf/guKw34kvIKsA=="
+ },
+ "node_modules/@react-financial-charts/tooltip/node_modules/d3-time": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-2.1.1.tgz",
+ "integrity": "sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ==",
+ "dependencies": {
+ "d3-array": "2"
+ }
+ },
+ "node_modules/@react-financial-charts/tooltip/node_modules/d3-time-format": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-3.0.0.tgz",
+ "integrity": "sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag==",
+ "dependencies": {
+ "d3-time": "1 - 2"
+ }
+ },
+ "node_modules/@react-financial-charts/tooltip/node_modules/internmap": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz",
+ "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="
+ },
+ "node_modules/@react-financial-charts/utils": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@react-financial-charts/utils/-/utils-2.0.1.tgz",
+ "integrity": "sha512-oUyfiURZ4bwTl+9794/BE+oAyK+7MkclOoL3rMQl4NtAY5Xb4NHAGCwK2Zh82gf0f2/68Rl4rho4lpFCDsFYmg==",
+ "dependencies": {
+ "react-virtualized-auto-sizer": "^1.0.16"
+ },
+ "peerDependencies": {
+ "react": "^16.0.0 || ^17.0.0 || ^18.0.0",
+ "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/@reduxjs/toolkit": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.2.1.tgz",
+ "integrity": "sha512-8CREoqJovQW/5I4yvvijm/emUiCCmcs4Ev4XPWd4mizSO+dD3g5G6w34QK5AGeNrSH7qM8Fl66j4vuV7dpOdkw==",
+ "dependencies": {
+ "immer": "^10.0.3",
+ "redux": "^5.0.1",
+ "redux-thunk": "^3.1.0",
+ "reselect": "^5.0.1"
+ },
+ "peerDependencies": {
+ "react": "^16.9.0 || ^17.0.0 || ^18",
+ "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "react": {
+ "optional": true
+ },
+ "react-redux": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@remix-run/router": {
+ "version": "1.15.3",
+ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.3.tgz",
+ "integrity": "sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w==",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@restart/hooks": {
+ "version": "0.4.16",
+ "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz",
+ "integrity": "sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==",
+ "dependencies": {
+ "dequal": "^2.0.3"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
+ "node_modules/@restart/ui": {
+ "version": "1.6.6",
+ "resolved": "https://registry.npmjs.org/@restart/ui/-/ui-1.6.6.tgz",
+ "integrity": "sha512-eC3puKuWE1SRYbojWHXnvCNHGgf3uzHCb6JOhnF4OXPibOIPEkR1sqDSkL643ydigxwh+ruCa1CmYHlzk7ikKA==",
+ "dependencies": {
+ "@babel/runtime": "^7.21.0",
+ "@popperjs/core": "^2.11.6",
+ "@react-aria/ssr": "^3.5.0",
+ "@restart/hooks": "^0.4.9",
+ "@types/warning": "^3.0.0",
+ "dequal": "^2.0.3",
+ "dom-helpers": "^5.2.0",
+ "uncontrollable": "^8.0.1",
+ "warning": "^4.0.3"
+ },
+ "peerDependencies": {
+ "react": ">=16.14.0",
+ "react-dom": ">=16.14.0"
+ }
+ },
+ "node_modules/@restart/ui/node_modules/uncontrollable": {
+ "version": "8.0.4",
+ "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-8.0.4.tgz",
+ "integrity": "sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ==",
+ "peerDependencies": {
+ "react": ">=16.14.0"
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.13.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.0.tgz",
+ "integrity": "sha512-5ZYPOuaAqEH/W3gYsRkxQATBW3Ii1MfaT4EQstTnLKViLi2gLSQmlmtTpGucNP3sXEpOiI5tdGhjdE111ekyEg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.13.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.0.tgz",
+ "integrity": "sha512-BSbaCmn8ZadK3UAQdlauSvtaJjhlDEjS5hEVVIN3A4bbl3X+otyf/kOJV08bYiRxfejP3DXFzO2jz3G20107+Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.13.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.0.tgz",
+ "integrity": "sha512-Ovf2evVaP6sW5Ut0GHyUSOqA6tVKfrTHddtmxGQc1CTQa1Cw3/KMCDEEICZBbyppcwnhMwcDce9ZRxdWRpVd6g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.13.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.0.tgz",
+ "integrity": "sha512-U+Jcxm89UTK592vZ2J9st9ajRv/hrwHdnvyuJpa5A2ngGSVHypigidkQJP+YiGL6JODiUeMzkqQzbCG3At81Gg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.13.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.0.tgz",
+ "integrity": "sha512-8wZidaUJUTIR5T4vRS22VkSMOVooG0F4N+JSwQXWSRiC6yfEsFMLTYRFHvby5mFFuExHa/yAp9juSphQQJAijQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.13.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.0.tgz",
+ "integrity": "sha512-Iu0Kno1vrD7zHQDxOmvweqLkAzjxEVqNhUIXBsZ8hu8Oak7/5VTPrxOEZXYC1nmrBVJp0ZcL2E7lSuuOVaE3+w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.13.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.0.tgz",
+ "integrity": "sha512-C31QrW47llgVyrRjIwiOwsHFcaIwmkKi3PCroQY5aVq4H0A5v/vVVAtFsI1nfBngtoRpeREvZOkIhmRwUKkAdw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.13.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.0.tgz",
+ "integrity": "sha512-Oq90dtMHvthFOPMl7pt7KmxzX7E71AfyIhh+cPhLY9oko97Zf2C9tt/XJD4RgxhaGeAraAXDtqxvKE1y/j35lA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.13.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.0.tgz",
+ "integrity": "sha512-yUD/8wMffnTKuiIsl6xU+4IA8UNhQ/f1sAnQebmE/lyQ8abjsVyDkyRkWop0kdMhKMprpNIhPmYlCxgHrPoXoA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.13.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.0.tgz",
+ "integrity": "sha512-9RyNqoFNdF0vu/qqX63fKotBh43fJQeYC98hCaf89DYQpv+xu0D8QFSOS0biA7cGuqJFOc1bJ+m2rhhsKcw1hw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.13.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.0.tgz",
+ "integrity": "sha512-46ue8ymtm/5PUU6pCvjlic0z82qWkxv54GTJZgHrQUuZnVH+tvvSP0LsozIDsCBFO4VjJ13N68wqrKSeScUKdA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.13.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.0.tgz",
+ "integrity": "sha512-P5/MqLdLSlqxbeuJ3YDeX37srC8mCflSyTrUsgbU1c/U9j6l2g2GiIdYaGD9QjdMQPMSgYm7hgg0551wHyIluw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.13.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.0.tgz",
+ "integrity": "sha512-UKXUQNbO3DOhzLRwHSpa0HnhhCgNODvfoPWv2FCXme8N/ANFfhIPMGuOT+QuKd16+B5yxZ0HdpNlqPvTMS1qfw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@socket.io/component-emitter": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
+ "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg=="
+ },
+ "node_modules/@swc/helpers": {
+ "version": "0.5.6",
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.6.tgz",
+ "integrity": "sha512-aYX01Ke9hunpoCexYAgQucEpARGQ5w/cqHFrIR+e9gdKb1QWTsVJuTJ2ozQzIAxLyRQe/m+2RqzkyOOGiMKRQA==",
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.6.8",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz",
+ "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.5.tgz",
+ "integrity": "sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.20.7"
+ }
+ },
+ "node_modules/@types/cookie": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
+ "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="
+ },
+ "node_modules/@types/d3-scale": {
+ "version": "3.3.5",
+ "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-3.3.5.tgz",
+ "integrity": "sha512-YOpKj0kIEusRf7ofeJcSZQsvKbnTwpe1DUF+P2qsotqG53kEsjm7EzzliqQxMkAWdkZcHrg5rRhB4JiDOQPX+A==",
+ "dependencies": {
+ "@types/d3-time": "^2"
+ }
+ },
+ "node_modules/@types/d3-time": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-2.1.4.tgz",
+ "integrity": "sha512-BTfLsxTeo7yFxI/haOOf1ZwJ6xKgQLT9dCp+EcmQv87Gox6X+oKl4mLKfO6fnWm3P22+A6DknMNEZany8ql2Rw=="
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
+ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
+ "dev": true
+ },
+ "node_modules/@types/hoist-non-react-statics": {
+ "version": "3.3.5",
+ "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz",
+ "integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==",
+ "dependencies": {
+ "@types/react": "*",
+ "hoist-non-react-statics": "^3.3.0"
+ }
+ },
+ "node_modules/@types/prop-types": {
+ "version": "15.7.11",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz",
+ "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng=="
+ },
+ "node_modules/@types/react": {
+ "version": "18.2.64",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.64.tgz",
+ "integrity": "sha512-MlmPvHgjj2p3vZaxbQgFUQFvD8QiZwACfGqEdDSWou5yISWxDQ4/74nCAwsUiX7UFLKZz3BbVSPj+YxeoGGCfg==",
+ "dependencies": {
+ "@types/prop-types": "*",
+ "@types/scheduler": "*",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "18.2.21",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.21.tgz",
+ "integrity": "sha512-gnvBA/21SA4xxqNXEwNiVcP0xSGHh/gi1VhWv9Bl46a0ItbTT5nFY+G9VSQpaG/8N/qdJpJ+vftQ4zflTtnjLw==",
+ "dev": true,
+ "dependencies": {
+ "@types/react": "*"
+ }
+ },
+ "node_modules/@types/react-transition-group": {
+ "version": "4.4.10",
+ "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz",
+ "integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==",
+ "dependencies": {
+ "@types/react": "*"
+ }
+ },
+ "node_modules/@types/scheduler": {
+ "version": "0.16.8",
+ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz",
+ "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A=="
+ },
+ "node_modules/@types/stylis": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.0.tgz",
+ "integrity": "sha512-n4sx2bqL0mW1tvDf/loQ+aMX7GQD3lc3fkCMC55VFNDu/vBOabO+LTIeXKM14xK0ppk5TUGcWRjiSpIlUpghKw=="
+ },
+ "node_modules/@types/use-sync-external-store": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
+ "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA=="
+ },
+ "node_modules/@types/warning": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz",
+ "integrity": "sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q=="
+ },
+ "node_modules/@ungap/structured-clone": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
+ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
+ "dev": true
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.2.1.tgz",
+ "integrity": "sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/core": "^7.23.5",
+ "@babel/plugin-transform-react-jsx-self": "^7.23.3",
+ "@babel/plugin-transform-react-jsx-source": "^7.23.3",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.14.0"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.11.3",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
+ "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
+ "dev": true,
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true
+ },
+ "node_modules/array-buffer-byte-length": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz",
+ "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.5",
+ "is-array-buffer": "^3.0.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array-includes": {
+ "version": "3.1.7",
+ "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz",
+ "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1",
+ "get-intrinsic": "^1.2.1",
+ "is-string": "^1.0.7"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.findlast": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.4.tgz",
+ "integrity": "sha512-BMtLxpV+8BD+6ZPFIWmnUBpQoy+A+ujcg4rhp2iwCRJYA7PEh2MS4NL3lz8EiDlLrJPp2hg9qWihr5pd//jcGw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.5",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.22.3",
+ "es-errors": "^1.3.0",
+ "es-shim-unscopables": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.flat": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz",
+ "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1",
+ "es-shim-unscopables": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.flatmap": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz",
+ "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1",
+ "es-shim-unscopables": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.toreversed": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/array.prototype.toreversed/-/array.prototype.toreversed-1.1.2.tgz",
+ "integrity": "sha512-wwDCoT4Ck4Cz7sLtgUmzR5UV3YF5mFHUlbChCzZBQZ+0m2cl/DH3tKgvphv1nKgFsJ48oCSg6p91q2Vm0I/ZMA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1",
+ "es-shim-unscopables": "^1.0.0"
+ }
+ },
+ "node_modules/array.prototype.tosorted": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.3.tgz",
+ "integrity": "sha512-/DdH4TiTmOKzyQbp/eadcCVexiCb36xJg7HshYOYJnNZFDj33GEv0P7GxsynpShhq4OLYJzbGcBDkLsDt7MnNg==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.5",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.22.3",
+ "es-errors": "^1.1.0",
+ "es-shim-unscopables": "^1.0.2"
+ }
+ },
+ "node_modules/arraybuffer.prototype.slice": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz",
+ "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==",
+ "dev": true,
+ "dependencies": {
+ "array-buffer-byte-length": "^1.0.1",
+ "call-bind": "^1.0.5",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.22.3",
+ "es-errors": "^1.2.1",
+ "get-intrinsic": "^1.2.3",
+ "is-array-buffer": "^3.0.4",
+ "is-shared-array-buffer": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/asynciterator.prototype": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz",
+ "integrity": "sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==",
+ "dev": true,
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ }
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
+ },
+ "node_modules/available-typed-arrays": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
+ "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==",
+ "dev": true,
+ "dependencies": {
+ "possible-typed-array-names": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/axios": {
+ "version": "1.6.7",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz",
+ "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==",
+ "dependencies": {
+ "follow-redirects": "^1.15.4",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true
+ },
+ "node_modules/bootstrap": {
+ "version": "5.3.3",
+ "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz",
+ "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/twbs"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/bootstrap"
+ }
+ ],
+ "peerDependencies": {
+ "@popperjs/core": "^2.11.8"
+ }
+ },
+ "node_modules/bootstrap-icons": {
+ "version": "1.11.3",
+ "resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.11.3.tgz",
+ "integrity": "sha512-+3lpHrCw/it2/7lBL15VR0HEumaBss0+f/Lb6ZvHISn1mlK83jjFpooTLsMWbIjJMDjDjOExMsTxnXSIT4k4ww==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/twbs"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/bootstrap"
+ }
+ ]
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.23.0",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz",
+ "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "caniuse-lite": "^1.0.30001587",
+ "electron-to-chromium": "^1.4.668",
+ "node-releases": "^2.0.14",
+ "update-browserslist-db": "^1.0.13"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/call-bind": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
+ "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
+ "dev": true,
+ "dependencies": {
+ "es-define-property": "^1.0.0",
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2",
+ "get-intrinsic": "^1.2.4",
+ "set-function-length": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/camelize": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
+ "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001596",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001596.tgz",
+ "integrity": "sha512-zpkZ+kEr6We7w63ORkoJ2pOfBwBkY/bJrG/UZ90qNb45Isblu8wzDgevEOrRL1r9dWayHjYiiyCMEXPn4DweGQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ]
+ },
+ "node_modules/chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/classnames": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
+ "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="
+ },
+ "node_modules/color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "1.1.3"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
+ "dev": true
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/commander": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
+ "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true
+ },
+ "node_modules/cookie": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
+ "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+ "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "dev": true,
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/css-color-keywords": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
+ "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/css-to-react-native": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
+ "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==",
+ "dependencies": {
+ "camelize": "^1.0.0",
+ "css-color-keywords": "^1.0.0",
+ "postcss-value-parser": "^4.0.2"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
+ },
+ "node_modules/d3-array": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
+ "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
+ "dependencies": {
+ "internmap": "1 - 2"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-color": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-2.0.0.tgz",
+ "integrity": "sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ=="
+ },
+ "node_modules/d3-dispatch": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-2.0.0.tgz",
+ "integrity": "sha512-S/m2VsXI7gAti2pBoLClFFTMOO1HTtT0j99AuXLoGFKO6deHDdnv6ZGTxSTTUTgO1zVcv82fCOtDjYK4EECmWA=="
+ },
+ "node_modules/d3-force": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-2.1.1.tgz",
+ "integrity": "sha512-nAuHEzBqMvpFVMf9OX75d00OxvOXdxY+xECIXjW6Gv8BRrXu6gAWbv/9XKrvfJ5i5DCokDW7RYE50LRoK092ew==",
+ "dependencies": {
+ "d3-dispatch": "1 - 2",
+ "d3-quadtree": "1 - 2",
+ "d3-timer": "1 - 2"
+ }
+ },
+ "node_modules/d3-format": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz",
+ "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-interpolate": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-2.0.1.tgz",
+ "integrity": "sha512-c5UhwwTs/yybcmTpAVqwSFl6vrQ8JZJoT5F7xNFK9pymv5C0Ymcc9/LIJHtYIggg/yS9YHw8i8O8tgb9pupjeQ==",
+ "dependencies": {
+ "d3-color": "1 - 2"
+ }
+ },
+ "node_modules/d3-path": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-2.0.0.tgz",
+ "integrity": "sha512-ZwZQxKhBnv9yHaiWd6ZU4x5BtCQ7pXszEV9CU6kRgwIQVQGLMv1oiL4M+MK/n79sYzsj+gcgpPQSctJUsLN7fA=="
+ },
+ "node_modules/d3-quadtree": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-2.0.0.tgz",
+ "integrity": "sha512-b0Ed2t1UUalJpc3qXzKi+cPGxeXRr4KU9YSlocN74aTzp6R/Ud43t79yLLqxHRWZfsvWXmbDWPpoENK1K539xw=="
+ },
+ "node_modules/d3-scale": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.3.0.tgz",
+ "integrity": "sha512-1JGp44NQCt5d1g+Yy+GeOnZP7xHo0ii8zsQp6PGzd+C1/dl0KGsp9A7Mxwp+1D1o4unbTTxVdU/ZOIEBoeZPbQ==",
+ "dependencies": {
+ "d3-array": "^2.3.0",
+ "d3-format": "1 - 2",
+ "d3-interpolate": "1.2.0 - 2",
+ "d3-time": "^2.1.1",
+ "d3-time-format": "2 - 3"
+ }
+ },
+ "node_modules/d3-scale/node_modules/d3-array": {
+ "version": "2.12.1",
+ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz",
+ "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==",
+ "dependencies": {
+ "internmap": "^1.0.0"
+ }
+ },
+ "node_modules/d3-scale/node_modules/d3-format": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-2.0.0.tgz",
+ "integrity": "sha512-Ab3S6XuE/Q+flY96HXT0jOXcM4EAClYFnRGY5zsjRGNy6qCYrQsMffs7cV5Q9xejb35zxW5hf/guKw34kvIKsA=="
+ },
+ "node_modules/d3-scale/node_modules/d3-time": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-2.1.1.tgz",
+ "integrity": "sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ==",
+ "dependencies": {
+ "d3-array": "2"
+ }
+ },
+ "node_modules/d3-scale/node_modules/d3-time-format": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-3.0.0.tgz",
+ "integrity": "sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag==",
+ "dependencies": {
+ "d3-time": "1 - 2"
+ }
+ },
+ "node_modules/d3-scale/node_modules/internmap": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz",
+ "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="
+ },
+ "node_modules/d3-selection": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-2.0.0.tgz",
+ "integrity": "sha512-XoGGqhLUN/W14NmaqcO/bb1nqjDAw5WtSYb2X8wiuQWvSZUsUVYsOSkOybUrNvcBjaywBdYPy03eXHMXjk9nZA=="
+ },
+ "node_modules/d3-shape": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-2.1.0.tgz",
+ "integrity": "sha512-PnjUqfM2PpskbSLTJvAzp2Wv4CZsnAgTfcVRTwW03QR3MkXF8Uo7B1y/lWkAsmbKwuecto++4NlsYcvYpXpTHA==",
+ "dependencies": {
+ "d3-path": "1 - 2"
+ }
+ },
+ "node_modules/d3-time": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
+ "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
+ "dependencies": {
+ "d3-array": "2 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-time-format": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
+ "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
+ "dependencies": {
+ "d3-time": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-timer": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-2.0.0.tgz",
+ "integrity": "sha512-TO4VLh0/420Y/9dO3+f9abDEFYeCUr2WZRlxJvbp4HPTQcSylXNiL6yZa9FIUvV1yRiFufl1bszTCLDqv9PWNA=="
+ },
+ "node_modules/debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deep-diff": {
+ "version": "0.3.8",
+ "resolved": "https://registry.npmjs.org/deep-diff/-/deep-diff-0.3.8.tgz",
+ "integrity": "sha512-yVn6RZmHiGnxRKR9sJb3iVV2XTF1Ghh2DiWRZ3dMnGc43yUdWWF/kX6lQyk3+P84iprfWKU/8zFTrlkvtFm1ug=="
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true
+ },
+ "node_modules/define-data-property": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
+ "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
+ "dev": true,
+ "dependencies": {
+ "es-define-property": "^1.0.0",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/define-properties": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
+ "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
+ "dev": true,
+ "dependencies": {
+ "define-data-property": "^1.0.1",
+ "has-property-descriptors": "^1.0.0",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/dequal": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/doctrine": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+ "dev": true,
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/dom-helpers": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
+ "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
+ "dependencies": {
+ "@babel/runtime": "^7.8.7",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.4.699",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.699.tgz",
+ "integrity": "sha512-I7q3BbQi6e4tJJN5CRcyvxhK0iJb34TV8eJQcgh+fR2fQ8miMgZcEInckCo1U9exDHbfz7DLDnFn8oqH/VcRKw==",
+ "dev": true
+ },
+ "node_modules/engine.io-client": {
+ "version": "6.5.3",
+ "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.3.tgz",
+ "integrity": "sha512-9Z0qLB0NIisTRt1DZ/8U2k12RJn8yls/nXMZLn+/N8hANT3TcYjKFKcwbw5zFQiN4NTde3TSY9zb79e1ij6j9Q==",
+ "dependencies": {
+ "@socket.io/component-emitter": "~3.1.0",
+ "debug": "~4.3.1",
+ "engine.io-parser": "~5.2.1",
+ "ws": "~8.11.0",
+ "xmlhttprequest-ssl": "~2.0.0"
+ }
+ },
+ "node_modules/engine.io-parser": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz",
+ "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/es-abstract": {
+ "version": "1.22.5",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.5.tgz",
+ "integrity": "sha512-oW69R+4q2wG+Hc3KZePPZxOiisRIqfKBVo/HLx94QcJeWGU/8sZhCvc829rd1kS366vlJbzBfXf9yWwf0+Ko7w==",
+ "dev": true,
+ "dependencies": {
+ "array-buffer-byte-length": "^1.0.1",
+ "arraybuffer.prototype.slice": "^1.0.3",
+ "available-typed-arrays": "^1.0.7",
+ "call-bind": "^1.0.7",
+ "es-define-property": "^1.0.0",
+ "es-errors": "^1.3.0",
+ "es-set-tostringtag": "^2.0.3",
+ "es-to-primitive": "^1.2.1",
+ "function.prototype.name": "^1.1.6",
+ "get-intrinsic": "^1.2.4",
+ "get-symbol-description": "^1.0.2",
+ "globalthis": "^1.0.3",
+ "gopd": "^1.0.1",
+ "has-property-descriptors": "^1.0.2",
+ "has-proto": "^1.0.3",
+ "has-symbols": "^1.0.3",
+ "hasown": "^2.0.1",
+ "internal-slot": "^1.0.7",
+ "is-array-buffer": "^3.0.4",
+ "is-callable": "^1.2.7",
+ "is-negative-zero": "^2.0.3",
+ "is-regex": "^1.1.4",
+ "is-shared-array-buffer": "^1.0.3",
+ "is-string": "^1.0.7",
+ "is-typed-array": "^1.1.13",
+ "is-weakref": "^1.0.2",
+ "object-inspect": "^1.13.1",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.5",
+ "regexp.prototype.flags": "^1.5.2",
+ "safe-array-concat": "^1.1.0",
+ "safe-regex-test": "^1.0.3",
+ "string.prototype.trim": "^1.2.8",
+ "string.prototype.trimend": "^1.0.7",
+ "string.prototype.trimstart": "^1.0.7",
+ "typed-array-buffer": "^1.0.2",
+ "typed-array-byte-length": "^1.0.1",
+ "typed-array-byte-offset": "^1.0.2",
+ "typed-array-length": "^1.0.5",
+ "unbox-primitive": "^1.0.2",
+ "which-typed-array": "^1.1.14"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
+ "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
+ "dev": true,
+ "dependencies": {
+ "get-intrinsic": "^1.2.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-iterator-helpers": {
+ "version": "1.0.17",
+ "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.17.tgz",
+ "integrity": "sha512-lh7BsUqelv4KUbR5a/ZTaGGIMLCjPGPqJ6q+Oq24YP0RdyptX1uzm4vvaqzk7Zx3bpl/76YLTTDj9L7uYQ92oQ==",
+ "dev": true,
+ "dependencies": {
+ "asynciterator.prototype": "^1.0.0",
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.22.4",
+ "es-errors": "^1.3.0",
+ "es-set-tostringtag": "^2.0.2",
+ "function-bind": "^1.1.2",
+ "get-intrinsic": "^1.2.4",
+ "globalthis": "^1.0.3",
+ "has-property-descriptors": "^1.0.2",
+ "has-proto": "^1.0.1",
+ "has-symbols": "^1.0.3",
+ "internal-slot": "^1.0.7",
+ "iterator.prototype": "^1.1.2",
+ "safe-array-concat": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz",
+ "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==",
+ "dev": true,
+ "dependencies": {
+ "get-intrinsic": "^1.2.4",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-shim-unscopables": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz",
+ "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==",
+ "dev": true,
+ "dependencies": {
+ "hasown": "^2.0.0"
+ }
+ },
+ "node_modules/es-to-primitive": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
+ "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
+ "dev": true,
+ "dependencies": {
+ "is-callable": "^1.1.4",
+ "is-date-object": "^1.0.1",
+ "is-symbol": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz",
+ "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==",
+ "dev": true,
+ "hasInstallScript": true,
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.20.2",
+ "@esbuild/android-arm": "0.20.2",
+ "@esbuild/android-arm64": "0.20.2",
+ "@esbuild/android-x64": "0.20.2",
+ "@esbuild/darwin-arm64": "0.20.2",
+ "@esbuild/darwin-x64": "0.20.2",
+ "@esbuild/freebsd-arm64": "0.20.2",
+ "@esbuild/freebsd-x64": "0.20.2",
+ "@esbuild/linux-arm": "0.20.2",
+ "@esbuild/linux-arm64": "0.20.2",
+ "@esbuild/linux-ia32": "0.20.2",
+ "@esbuild/linux-loong64": "0.20.2",
+ "@esbuild/linux-mips64el": "0.20.2",
+ "@esbuild/linux-ppc64": "0.20.2",
+ "@esbuild/linux-riscv64": "0.20.2",
+ "@esbuild/linux-s390x": "0.20.2",
+ "@esbuild/linux-x64": "0.20.2",
+ "@esbuild/netbsd-x64": "0.20.2",
+ "@esbuild/openbsd-x64": "0.20.2",
+ "@esbuild/sunos-x64": "0.20.2",
+ "@esbuild/win32-arm64": "0.20.2",
+ "@esbuild/win32-ia32": "0.20.2",
+ "@esbuild/win32-x64": "0.20.2"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
+ "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "8.57.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz",
+ "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==",
+ "dev": true,
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@eslint-community/regexpp": "^4.6.1",
+ "@eslint/eslintrc": "^2.1.4",
+ "@eslint/js": "8.57.0",
+ "@humanwhocodes/config-array": "^0.11.14",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@nodelib/fs.walk": "^1.2.8",
+ "@ungap/structured-clone": "^1.2.0",
+ "ajv": "^6.12.4",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.2",
+ "debug": "^4.3.2",
+ "doctrine": "^3.0.0",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^7.2.2",
+ "eslint-visitor-keys": "^3.4.3",
+ "espree": "^9.6.1",
+ "esquery": "^1.4.2",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^6.0.1",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "globals": "^13.19.0",
+ "graphemer": "^1.4.0",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "is-path-inside": "^3.0.3",
+ "js-yaml": "^4.1.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.4.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3",
+ "strip-ansi": "^6.0.1",
+ "text-table": "^0.2.0"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-plugin-react": {
+ "version": "7.34.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.34.0.tgz",
+ "integrity": "sha512-MeVXdReleBTdkz/bvcQMSnCXGi+c9kvy51IpinjnJgutl3YTHWsDdke7Z1ufZpGfDG8xduBDKyjtB9JH1eBKIQ==",
+ "dev": true,
+ "dependencies": {
+ "array-includes": "^3.1.7",
+ "array.prototype.findlast": "^1.2.4",
+ "array.prototype.flatmap": "^1.3.2",
+ "array.prototype.toreversed": "^1.1.2",
+ "array.prototype.tosorted": "^1.1.3",
+ "doctrine": "^2.1.0",
+ "es-iterator-helpers": "^1.0.17",
+ "estraverse": "^5.3.0",
+ "jsx-ast-utils": "^2.4.1 || ^3.0.0",
+ "minimatch": "^3.1.2",
+ "object.entries": "^1.1.7",
+ "object.fromentries": "^2.0.7",
+ "object.hasown": "^1.1.3",
+ "object.values": "^1.1.7",
+ "prop-types": "^15.8.1",
+ "resolve": "^2.0.0-next.5",
+ "semver": "^6.3.1",
+ "string.prototype.matchall": "^4.0.10"
+ },
+ "engines": {
+ "node": ">=4"
+ },
+ "peerDependencies": {
+ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8"
+ }
+ },
+ "node_modules/eslint-plugin-react-hooks": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz",
+ "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0"
+ }
+ },
+ "node_modules/eslint-plugin-react-refresh": {
+ "version": "0.4.5",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.5.tgz",
+ "integrity": "sha512-D53FYKJa+fDmZMtriODxvhwrO+IOqrxoEo21gMA0sjHdU6dPVH4OhyFip9ypl8HOF5RV5KdTo+rBQLvnY2cO8w==",
+ "dev": true,
+ "peerDependencies": {
+ "eslint": ">=7"
+ }
+ },
+ "node_modules/eslint-plugin-react/node_modules/doctrine": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
+ "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
+ "dev": true,
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "7.2.2",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
+ "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
+ "dev": true,
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/eslint/node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/eslint/node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/eslint/node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/eslint/node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint/node_modules/globals": {
+ "version": "13.24.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
+ "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
+ "dev": true,
+ "dependencies": {
+ "type-fest": "^0.20.2"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint/node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/eslint/node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/espree": {
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
+ "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
+ "dev": true,
+ "dependencies": {
+ "acorn": "^8.9.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^3.4.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz",
+ "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==",
+ "dev": true,
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true
+ },
+ "node_modules/fastq": {
+ "version": "1.17.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
+ "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
+ "dev": true,
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+ "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+ "dev": true,
+ "dependencies": {
+ "flat-cache": "^3.0.4"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
+ "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
+ "dev": true,
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.3",
+ "rimraf": "^3.0.2"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz",
+ "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
+ "dev": true
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.15.5",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
+ "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/for-each": {
+ "version": "0.3.3",
+ "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
+ "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==",
+ "dev": true,
+ "dependencies": {
+ "is-callable": "^1.1.3"
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+ "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "dev": true
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/function.prototype.name": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz",
+ "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1",
+ "functions-have-names": "^1.2.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/functions-have-names": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
+ "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
+ "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
+ "dev": true,
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2",
+ "has-proto": "^1.0.1",
+ "has-symbols": "^1.0.3",
+ "hasown": "^2.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-symbol-description": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz",
+ "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.5",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "dev": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/globals": {
+ "version": "11.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/globalthis": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz",
+ "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==",
+ "dev": true,
+ "dependencies": {
+ "define-properties": "^1.1.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
+ "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
+ "dev": true,
+ "dependencies": {
+ "get-intrinsic": "^1.1.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/graphemer": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
+ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+ "dev": true
+ },
+ "node_modules/has-bigints": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
+ "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/has-property-descriptors": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
+ "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
+ "dev": true,
+ "dependencies": {
+ "es-define-property": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-proto": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
+ "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
+ "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "dev": true,
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "dev": true,
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/hoist-non-react-statics": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
+ "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
+ "dependencies": {
+ "react-is": "^16.7.0"
+ }
+ },
+ "node_modules/html-entities": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz",
+ "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/mdevils"
+ },
+ {
+ "type": "patreon",
+ "url": "https://patreon.com/mdevils"
+ }
+ ]
+ },
+ "node_modules/ignore": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
+ "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/immer": {
+ "version": "10.0.4",
+ "resolved": "https://registry.npmjs.org/immer/-/immer-10.0.4.tgz",
+ "integrity": "sha512-cuBuGK40P/sk5IzWa9QPUaAdvPHjkk1c+xYsd9oZw+YQQEV+10G0P5uMpGctZZKnyQ+ibRO08bD25nWLmYi2pw==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/immer"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
+ "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+ "dev": true,
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "dev": true,
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true
+ },
+ "node_modules/internal-slot": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz",
+ "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==",
+ "dev": true,
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "hasown": "^2.0.0",
+ "side-channel": "^1.0.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/internmap": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
+ "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/invariant": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
+ "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
+ "dependencies": {
+ "loose-envify": "^1.0.0"
+ }
+ },
+ "node_modules/is-array-buffer": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz",
+ "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-async-function": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz",
+ "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==",
+ "dev": true,
+ "dependencies": {
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-bigint": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz",
+ "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==",
+ "dev": true,
+ "dependencies": {
+ "has-bigints": "^1.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-boolean-object": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
+ "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-callable": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
+ "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.13.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
+ "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==",
+ "dev": true,
+ "dependencies": {
+ "hasown": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-date-object": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
+ "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
+ "dev": true,
+ "dependencies": {
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-finalizationregistry": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz",
+ "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-generator-function": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz",
+ "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==",
+ "dev": true,
+ "dependencies": {
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-map": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz",
+ "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-negative-zero": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz",
+ "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-number-object": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz",
+ "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==",
+ "dev": true,
+ "dependencies": {
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-path-inside": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
+ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-regex": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
+ "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-set": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz",
+ "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-shared-array-buffer": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz",
+ "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-string": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz",
+ "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==",
+ "dev": true,
+ "dependencies": {
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-symbol": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz",
+ "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==",
+ "dev": true,
+ "dependencies": {
+ "has-symbols": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-typed-array": {
+ "version": "1.1.13",
+ "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz",
+ "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==",
+ "dev": true,
+ "dependencies": {
+ "which-typed-array": "^1.1.14"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-weakmap": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz",
+ "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-weakref": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz",
+ "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-weakset": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz",
+ "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "get-intrinsic": "^1.2.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/isarray": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
+ "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
+ "dev": true
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true
+ },
+ "node_modules/iterator.prototype": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz",
+ "integrity": "sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==",
+ "dev": true,
+ "dependencies": {
+ "define-properties": "^1.2.1",
+ "get-intrinsic": "^1.2.1",
+ "has-symbols": "^1.0.3",
+ "reflect.getprototypeof": "^1.0.4",
+ "set-function-name": "^2.0.1"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
+ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
+ "dev": true,
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/jsx-ast-utils": {
+ "version": "3.3.5",
+ "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
+ "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==",
+ "dev": true,
+ "dependencies": {
+ "array-includes": "^3.1.6",
+ "array.prototype.flat": "^1.3.1",
+ "object.assign": "^4.1.4",
+ "object.values": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash.flattendeep": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz",
+ "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ=="
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.7",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
+ "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true
+ },
+ "node_modules/nginx": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/nginx/-/nginx-1.0.8.tgz",
+ "integrity": "sha512-qd6erupgZjSnPX2Qa6uUYnNrdLyt6WyJAdP3olhaAITAveSHwUUzu9s0hnRbiYEQKf27LoSf1bpSqnljw9TyAg==",
+ "dependencies": {
+ "chalk": "^4.1.0",
+ "commander": "^7.2.0"
+ },
+ "bin": {
+ "nginx-install": "index.js"
+ }
+ },
+ "node_modules/nginx/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/nginx/node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/nginx/node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/nginx/node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+ },
+ "node_modules/nginx/node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/nginx/node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.14",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
+ "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==",
+ "dev": true
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.1",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
+ "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object-keys": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
+ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/object.assign": {
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz",
+ "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.5",
+ "define-properties": "^1.2.1",
+ "has-symbols": "^1.0.3",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object.entries": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.7.tgz",
+ "integrity": "sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/object.fromentries": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz",
+ "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object.hasown": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.3.tgz",
+ "integrity": "sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA==",
+ "dev": true,
+ "dependencies": {
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object.values": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz",
+ "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dev": true,
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.3",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz",
+ "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==",
+ "dev": true,
+ "dependencies": {
+ "@aashutoshrathi/word-wrap": "^1.2.3",
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true
+ },
+ "node_modules/picocolors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
+ },
+ "node_modules/possible-typed-array-names": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz",
+ "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.4.38",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
+ "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "nanoid": "^3.3.7",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.2.0"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/prop-types": {
+ "version": "15.8.1",
+ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
+ "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
+ "dependencies": {
+ "loose-envify": "^1.4.0",
+ "object-assign": "^4.1.1",
+ "react-is": "^16.13.1"
+ }
+ },
+ "node_modules/prop-types-extra": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz",
+ "integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==",
+ "dependencies": {
+ "react-is": "^16.3.2",
+ "warning": "^4.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=0.14.0"
+ }
+ },
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/react": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
+ "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-bootstrap": {
+ "version": "2.10.1",
+ "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.10.1.tgz",
+ "integrity": "sha512-J3OpRZIvCTQK+Tg/jOkRUvpYLHMdGeU9KqFUBQrV0d/Qr/3nsINpiOJyZMWnM5SJ3ctZdhPA6eCIKpEJR3Ellg==",
+ "dependencies": {
+ "@babel/runtime": "^7.22.5",
+ "@restart/hooks": "^0.4.9",
+ "@restart/ui": "^1.6.6",
+ "@types/react-transition-group": "^4.4.6",
+ "classnames": "^2.3.2",
+ "dom-helpers": "^5.2.1",
+ "invariant": "^2.2.4",
+ "prop-types": "^15.8.1",
+ "prop-types-extra": "^1.1.0",
+ "react-transition-group": "^4.4.5",
+ "uncontrollable": "^7.2.1",
+ "warning": "^4.0.3"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.8",
+ "react": ">=16.14.0",
+ "react-dom": ">=16.14.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-bootstrap-icons": {
+ "version": "1.11.3",
+ "resolved": "https://registry.npmjs.org/react-bootstrap-icons/-/react-bootstrap-icons-1.11.3.tgz",
+ "integrity": "sha512-f/DAy4UXnjdbaZyUcZKR2I3xim56uCznb9t+u3ojwzDG1p2RUrua/d8R4xplAQ8Bj/LVZwHVSrvO+npvp3l3pw==",
+ "dependencies": {
+ "prop-types": "^15.7.2"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.6"
+ }
+ },
+ "node_modules/react-cookie": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-7.1.0.tgz",
+ "integrity": "sha512-n2+Gt07/xxuShXary+SImk1sw5l7a1UguQOQEN55YewEW5LoA0opbR4nbeo8sY6OYwR37iCFJtqJ0AGEywqAtg==",
+ "dependencies": {
+ "@types/hoist-non-react-statics": "^3.3.5",
+ "hoist-non-react-statics": "^3.3.2",
+ "universal-cookie": "^7.0.0"
+ },
+ "peerDependencies": {
+ "react": ">= 16.3.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
+ "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.0"
+ },
+ "peerDependencies": {
+ "react": "^18.2.0"
+ }
+ },
+ "node_modules/react-financial-charts": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/react-financial-charts/-/react-financial-charts-2.0.1.tgz",
+ "integrity": "sha512-KpPns2e+Dp90JcavkshhTXBAmw4mAmQunCU5V1QsWFNSIWIubmcigf3BvWxBJBrw9R6gpA91L9fUQsSAjDV3Bg==",
+ "dependencies": {
+ "@react-financial-charts/annotations": "^2.0.0",
+ "@react-financial-charts/axes": "^2.0.0",
+ "@react-financial-charts/coordinates": "^2.0.0",
+ "@react-financial-charts/core": "^2.0.0",
+ "@react-financial-charts/indicators": "^2.0.0",
+ "@react-financial-charts/interactive": "^2.0.0",
+ "@react-financial-charts/scales": "^2.0.0",
+ "@react-financial-charts/series": "^2.0.0",
+ "@react-financial-charts/tooltip": "^2.0.0",
+ "@react-financial-charts/utils": "^2.0.1"
+ },
+ "peerDependencies": {
+ "react": "^16.0.0 || ^17.0.0 || ^18.0.0",
+ "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/react-icons": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.0.1.tgz",
+ "integrity": "sha512-WqLZJ4bLzlhmsvme6iFdgO8gfZP17rfjYEJ2m9RsZjZ+cc4k1hTzknEz63YS1MeT50kVzoa1Nz36f4BEx+Wigw==",
+ "peerDependencies": {
+ "react": "*"
+ }
+ },
+ "node_modules/react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+ },
+ "node_modules/react-lifecycles-compat": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
+ "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
+ },
+ "node_modules/react-redux": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.1.0.tgz",
+ "integrity": "sha512-6qoDzIO+gbrza8h3hjMA9aq4nwVFCKFtY2iLxCtVT38Swyy2C/dJCGBXHeHLtx6qlg/8qzc2MrhOeduf5K32wQ==",
+ "dependencies": {
+ "@types/use-sync-external-store": "^0.0.3",
+ "use-sync-external-store": "^1.0.0"
+ },
+ "peerDependencies": {
+ "@types/react": "^18.2.25",
+ "react": "^18.0",
+ "react-native": ">=0.69",
+ "redux": "^5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "react-native": {
+ "optional": true
+ },
+ "redux": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-refresh": {
+ "version": "0.14.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz",
+ "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-router": {
+ "version": "6.22.3",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.3.tgz",
+ "integrity": "sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ==",
+ "dependencies": {
+ "@remix-run/router": "1.15.3"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8"
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "6.22.3",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.3.tgz",
+ "integrity": "sha512-7ZILI7HjcE+p31oQvwbokjk6OA/bnFxrhJ19n82Ex9Ph8fNAq+Hm/7KchpMGlTgWhUxRHMMCut+vEtNpWpowKw==",
+ "dependencies": {
+ "@remix-run/router": "1.15.3",
+ "react-router": "6.22.3"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8",
+ "react-dom": ">=16.8"
+ }
+ },
+ "node_modules/react-spinners": {
+ "version": "0.13.8",
+ "resolved": "https://registry.npmjs.org/react-spinners/-/react-spinners-0.13.8.tgz",
+ "integrity": "sha512-3e+k56lUkPj0vb5NDXPVFAOkPC//XyhKPJjvcGjyMNPWsBKpplfeyialP74G7H7+It7KzhtET+MvGqbKgAqpZA==",
+ "peerDependencies": {
+ "react": "^16.0.0 || ^17.0.0 || ^18.0.0",
+ "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/react-textarea-autosize": {
+ "version": "8.5.3",
+ "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.5.3.tgz",
+ "integrity": "sha512-XT1024o2pqCuZSuBt9FwHlaDeNtVrtCXu0Rnz88t1jUGheCLa3PhjE1GH8Ctm2axEtvdCl5SUHYschyQ0L5QHQ==",
+ "dependencies": {
+ "@babel/runtime": "^7.20.13",
+ "use-composed-ref": "^1.3.0",
+ "use-latest": "^1.2.1"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/react-transition-group": {
+ "version": "4.4.5",
+ "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
+ "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
+ "dependencies": {
+ "@babel/runtime": "^7.5.5",
+ "dom-helpers": "^5.0.1",
+ "loose-envify": "^1.4.0",
+ "prop-types": "^15.6.2"
+ },
+ "peerDependencies": {
+ "react": ">=16.6.0",
+ "react-dom": ">=16.6.0"
+ }
+ },
+ "node_modules/react-virtualized-auto-sizer": {
+ "version": "1.0.24",
+ "resolved": "https://registry.npmjs.org/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.24.tgz",
+ "integrity": "sha512-3kCn7N9NEb3FlvJrSHWGQ4iVl+ydQObq2fHMn12i5wbtm74zHOPhz/i64OL3c1S1vi9i2GXtZqNqUJTQ+BnNfg==",
+ "peerDependencies": {
+ "react": "^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0",
+ "react-dom": "^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/redux": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
+ "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w=="
+ },
+ "node_modules/redux-logger": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/redux-logger/-/redux-logger-3.0.6.tgz",
+ "integrity": "sha512-JoCIok7bg/XpqA1JqCqXFypuqBbQzGQySrhFzewB7ThcnysTO30l4VCst86AuB9T9tuT03MAA56Jw2PNhRSNCg==",
+ "dependencies": {
+ "deep-diff": "^0.3.5"
+ }
+ },
+ "node_modules/redux-persist": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/redux-persist/-/redux-persist-6.0.0.tgz",
+ "integrity": "sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==",
+ "peerDependencies": {
+ "redux": ">4.0.0"
+ }
+ },
+ "node_modules/redux-thunk": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
+ "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==",
+ "peerDependencies": {
+ "redux": "^5.0.0"
+ }
+ },
+ "node_modules/reflect.getprototypeof": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.5.tgz",
+ "integrity": "sha512-62wgfC8dJWrmxv44CA36pLDnP6KKl3Vhxb7PL+8+qrrFMMoJij4vgiMP8zV4O8+CBMXY1mHxI5fITGHXFHVmQQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.5",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.22.3",
+ "es-errors": "^1.0.0",
+ "get-intrinsic": "^1.2.3",
+ "globalthis": "^1.0.3",
+ "which-builtin-type": "^1.1.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/regenerator-runtime": {
+ "version": "0.14.1",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
+ "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
+ },
+ "node_modules/regexp.prototype.flags": {
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz",
+ "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.6",
+ "define-properties": "^1.2.1",
+ "es-errors": "^1.3.0",
+ "set-function-name": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/reselect": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.0.tgz",
+ "integrity": "sha512-aw7jcGLDpSgNDyWBQLv2cedml85qd95/iszJjN988zX1t7AVRJi19d9kto5+W7oCfQ94gyo40dVbT6g2k4/kXg=="
+ },
+ "node_modules/resolve": {
+ "version": "2.0.0-next.5",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz",
+ "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==",
+ "dev": true,
+ "dependencies": {
+ "is-core-module": "^2.13.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+ "dev": true,
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "dev": true,
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.13.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.0.tgz",
+ "integrity": "sha512-3YegKemjoQnYKmsBlOHfMLVPPA5xLkQ8MHLLSw/fBrFaVkEayL51DilPpNNLq1exr98F2B1TzrV0FUlN3gWRPg==",
+ "dev": true,
+ "dependencies": {
+ "@types/estree": "1.0.5"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.13.0",
+ "@rollup/rollup-android-arm64": "4.13.0",
+ "@rollup/rollup-darwin-arm64": "4.13.0",
+ "@rollup/rollup-darwin-x64": "4.13.0",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.13.0",
+ "@rollup/rollup-linux-arm64-gnu": "4.13.0",
+ "@rollup/rollup-linux-arm64-musl": "4.13.0",
+ "@rollup/rollup-linux-riscv64-gnu": "4.13.0",
+ "@rollup/rollup-linux-x64-gnu": "4.13.0",
+ "@rollup/rollup-linux-x64-musl": "4.13.0",
+ "@rollup/rollup-win32-arm64-msvc": "4.13.0",
+ "@rollup/rollup-win32-ia32-msvc": "4.13.0",
+ "@rollup/rollup-win32-x64-msvc": "4.13.0",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/safe-array-concat": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz",
+ "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "get-intrinsic": "^1.2.4",
+ "has-symbols": "^1.0.3",
+ "isarray": "^2.0.5"
+ },
+ "engines": {
+ "node": ">=0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/safe-regex-test": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz",
+ "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.6",
+ "es-errors": "^1.3.0",
+ "is-regex": "^1.1.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
+ "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/set-function-length": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
+ "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
+ "dev": true,
+ "dependencies": {
+ "define-data-property": "^1.1.4",
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2",
+ "get-intrinsic": "^1.2.4",
+ "gopd": "^1.0.1",
+ "has-property-descriptors": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/set-function-name": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz",
+ "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==",
+ "dev": true,
+ "dependencies": {
+ "define-data-property": "^1.1.4",
+ "es-errors": "^1.3.0",
+ "functions-have-names": "^1.2.3",
+ "has-property-descriptors": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/shallowequal": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
+ "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/side-channel": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
+ "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.4",
+ "object-inspect": "^1.13.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/socket.io-client": {
+ "version": "4.7.5",
+ "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.5.tgz",
+ "integrity": "sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==",
+ "dependencies": {
+ "@socket.io/component-emitter": "~3.1.0",
+ "debug": "~4.3.2",
+ "engine.io-client": "~6.5.2",
+ "socket.io-parser": "~4.2.4"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/socket.io-parser": {
+ "version": "4.2.4",
+ "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
+ "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
+ "dependencies": {
+ "@socket.io/component-emitter": "~3.1.0",
+ "debug": "~4.3.1"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
+ "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/string.prototype.matchall": {
+ "version": "4.0.10",
+ "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.10.tgz",
+ "integrity": "sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1",
+ "get-intrinsic": "^1.2.1",
+ "has-symbols": "^1.0.3",
+ "internal-slot": "^1.0.5",
+ "regexp.prototype.flags": "^1.5.0",
+ "set-function-name": "^2.0.0",
+ "side-channel": "^1.0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.trim": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz",
+ "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.trimend": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz",
+ "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.trimstart": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz",
+ "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/styled-components": {
+ "version": "6.1.8",
+ "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.8.tgz",
+ "integrity": "sha512-PQ6Dn+QxlWyEGCKDS71NGsXoVLKfE1c3vApkvDYS5KAK+V8fNWGhbSUEo9Gg2iaID2tjLXegEW3bZDUGpofRWw==",
+ "dependencies": {
+ "@emotion/is-prop-valid": "1.2.1",
+ "@emotion/unitless": "0.8.0",
+ "@types/stylis": "4.2.0",
+ "css-to-react-native": "3.2.0",
+ "csstype": "3.1.2",
+ "postcss": "8.4.31",
+ "shallowequal": "1.1.0",
+ "stylis": "4.3.1",
+ "tslib": "2.5.0"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/styled-components"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8.0",
+ "react-dom": ">= 16.8.0"
+ }
+ },
+ "node_modules/styled-components/node_modules/csstype": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
+ "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ=="
+ },
+ "node_modules/styled-components/node_modules/postcss": {
+ "version": "8.4.31",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
+ "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "nanoid": "^3.3.6",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.0.2"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/styled-components/node_modules/tslib": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz",
+ "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg=="
+ },
+ "node_modules/stylis": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.1.tgz",
+ "integrity": "sha512-EQepAV+wMsIaGVGX1RECzgrcqRRU/0sYOHkeLsZ3fzHaHXZy4DaOOX0vOlGQdlsjkh3mFHAIlVimpwAs4dslyQ=="
+ },
+ "node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/text-table": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+ "dev": true
+ },
+ "node_modules/to-fast-properties": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
+ "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/typed-array-buffer": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz",
+ "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "es-errors": "^1.3.0",
+ "is-typed-array": "^1.1.13"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/typed-array-byte-length": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz",
+ "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "for-each": "^0.3.3",
+ "gopd": "^1.0.1",
+ "has-proto": "^1.0.3",
+ "is-typed-array": "^1.1.13"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/typed-array-byte-offset": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz",
+ "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==",
+ "dev": true,
+ "dependencies": {
+ "available-typed-arrays": "^1.0.7",
+ "call-bind": "^1.0.7",
+ "for-each": "^0.3.3",
+ "gopd": "^1.0.1",
+ "has-proto": "^1.0.3",
+ "is-typed-array": "^1.1.13"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/typed-array-length": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.5.tgz",
+ "integrity": "sha512-yMi0PlwuznKHxKmcpoOdeLwxBoVPkqZxd7q2FgMkmD3bNwvF5VW0+UlUQ1k1vmktTu4Yu13Q0RIxEP8+B+wloA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "for-each": "^0.3.3",
+ "gopd": "^1.0.1",
+ "has-proto": "^1.0.3",
+ "is-typed-array": "^1.1.13",
+ "possible-typed-array-names": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/unbox-primitive": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
+ "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "has-bigints": "^1.0.2",
+ "has-symbols": "^1.0.3",
+ "which-boxed-primitive": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/uncontrollable": {
+ "version": "7.2.1",
+ "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz",
+ "integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==",
+ "dependencies": {
+ "@babel/runtime": "^7.6.3",
+ "@types/react": ">=16.9.11",
+ "invariant": "^2.2.4",
+ "react-lifecycles-compat": "^3.0.4"
+ },
+ "peerDependencies": {
+ "react": ">=15.0.0"
+ }
+ },
+ "node_modules/universal-cookie": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-7.1.0.tgz",
+ "integrity": "sha512-LCLHwP0whxTqkBYMptW1dzNS0xxIVJmU6c51N5CfPNheVxuJW7fVxPa6MUGX7boUSyOlpMveBO96hMs5Gee6Fg==",
+ "dependencies": {
+ "@types/cookie": "^0.6.0",
+ "cookie": "^0.6.0"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.0.13",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
+ "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "escalade": "^3.1.1",
+ "picocolors": "^1.0.0"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/use-composed-ref": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.3.0.tgz",
+ "integrity": "sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ==",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/use-isomorphic-layout-effect": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz",
+ "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/use-latest": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/use-latest/-/use-latest-1.2.1.tgz",
+ "integrity": "sha512-xA+AVm/Wlg3e2P/JiItTziwS7FK92LWrDB0p+hgXloIMuVCeJJ8v6f0eeHyPZaJrM+usM1FkFfbNCrJGs8A/zw==",
+ "dependencies": {
+ "use-isomorphic-layout-effect": "^1.1.1"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/use-sync-external-store": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
+ "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/uuid": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
+ "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
+ "funding": [
+ "https://github.com/sponsors/broofa",
+ "https://github.com/sponsors/ctavan"
+ ],
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
+ "node_modules/vite": {
+ "version": "5.2.6",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.6.tgz",
+ "integrity": "sha512-FPtnxFlSIKYjZ2eosBQamz4CbyrTizbZ3hnGJlh/wMtCrlp1Hah6AzBLjGI5I2urTfNnpovpHdrL6YRuBOPnCA==",
+ "dev": true,
+ "dependencies": {
+ "esbuild": "^0.20.1",
+ "postcss": "^8.4.36",
+ "rollup": "^4.13.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/warning": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
+ "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
+ "dependencies": {
+ "loose-envify": "^1.0.0"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/which-boxed-primitive": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz",
+ "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==",
+ "dev": true,
+ "dependencies": {
+ "is-bigint": "^1.0.1",
+ "is-boolean-object": "^1.1.0",
+ "is-number-object": "^1.0.4",
+ "is-string": "^1.0.5",
+ "is-symbol": "^1.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/which-builtin-type": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.3.tgz",
+ "integrity": "sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==",
+ "dev": true,
+ "dependencies": {
+ "function.prototype.name": "^1.1.5",
+ "has-tostringtag": "^1.0.0",
+ "is-async-function": "^2.0.0",
+ "is-date-object": "^1.0.5",
+ "is-finalizationregistry": "^1.0.2",
+ "is-generator-function": "^1.0.10",
+ "is-regex": "^1.1.4",
+ "is-weakref": "^1.0.2",
+ "isarray": "^2.0.5",
+ "which-boxed-primitive": "^1.0.2",
+ "which-collection": "^1.0.1",
+ "which-typed-array": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/which-collection": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz",
+ "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==",
+ "dev": true,
+ "dependencies": {
+ "is-map": "^2.0.3",
+ "is-set": "^2.0.3",
+ "is-weakmap": "^2.0.2",
+ "is-weakset": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/which-typed-array": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz",
+ "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==",
+ "dev": true,
+ "dependencies": {
+ "available-typed-arrays": "^1.0.7",
+ "call-bind": "^1.0.7",
+ "for-each": "^0.3.3",
+ "gopd": "^1.0.1",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "dev": true
+ },
+ "node_modules/ws": {
+ "version": "8.11.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
+ "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": "^5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/xmlhttprequest-ssl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz",
+ "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..5767c13
--- /dev/null
+++ b/package.json
@@ -0,0 +1,50 @@
+{
+ "name": "invest-sns-react",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@reduxjs/toolkit": "^2.2.1",
+ "axios": "^1.6.7",
+ "bootstrap": "^5.3.3",
+ "bootstrap-icons": "^1.11.3",
+ "d3-format": "^3.1.0",
+ "d3-time-format": "^4.1.0",
+ "html-entities": "^2.5.2",
+ "nginx": "^1.0.8",
+ "react": "^18.2.0",
+ "react-bootstrap": "^2.10.1",
+ "react-bootstrap-icons": "^1.11.3",
+ "react-cookie": "^7.1.0",
+ "react-dom": "^18.2.0",
+ "react-financial-charts": "^2.0.1",
+ "react-icons": "^5.0.1",
+ "react-redux": "^9.1.0",
+ "react-router-dom": "^6.22.3",
+ "react-spinners": "^0.13.8",
+ "react-textarea-autosize": "^8.5.3",
+ "react-transition-group": "^4.4.5",
+ "redux": "^5.0.1",
+ "redux-logger": "^3.0.6",
+ "redux-persist": "^6.0.0",
+ "socket.io-client": "^4.7.5",
+ "styled-components": "^6.1.8",
+ "uuid": "^9.0.1"
+ },
+ "devDependencies": {
+ "@types/react": "^18.2.56",
+ "@types/react-dom": "^18.2.19",
+ "@vitejs/plugin-react": "^4.2.1",
+ "eslint": "^8.56.0",
+ "eslint-plugin-react": "^7.33.2",
+ "eslint-plugin-react-hooks": "^4.6.0",
+ "eslint-plugin-react-refresh": "^0.4.5",
+ "vite": "^5.2.6"
+ }
+}
diff --git a/public/Json/chartData.json b/public/Json/chartData.json
new file mode 100644
index 0000000..3007f31
--- /dev/null
+++ b/public/Json/chartData.json
@@ -0,0 +1,208 @@
+[
+ {
+ "id": 0,
+ "name": "SMA",
+ "showName": "단순 이동평균선",
+ "vars": [
+ {
+ "id": 0,
+ "key": "lineTime1",
+ "name": "5일 선",
+ "default": 5,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ },
+ {
+ "id": 1,
+ "key": "lineTime2",
+ "name": "20일 선",
+ "default": 20,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ },
+ {
+ "id": 2,
+ "key": "lineTime3",
+ "name": "60일 선",
+ "default": 60,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ },
+ {
+ "id": 3,
+ "key": "lineTime4",
+ "name": "120일 선",
+ "default": 120,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ },
+ {
+ "id": 4,
+ "key": "lineTime5",
+ "name": "240일 선",
+ "default": 240,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ }
+ ]
+ },
+ {
+ "id": 1,
+ "name": "WMA",
+ "showName": "가중 이동평균선",
+ "vars": [
+ {
+ "id": 0,
+ "key": "lineTime1",
+ "name": "5일 선",
+ "default": 5,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ },
+ {
+ "id": 1,
+ "key": "lineTime2",
+ "name": "20일 선",
+ "default": 20,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ },
+ {
+ "id": 2,
+ "key": "lineTime3",
+ "name": "60일 선",
+ "default": 60,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ },
+ {
+ "id": 3,
+ "key": "lineTime4",
+ "name": "120일 선",
+ "default": 120,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ },
+ {
+ "id": 4,
+ "key": "lineTime5",
+ "name": "240일 선",
+ "default": 240,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ }
+ ]
+ },
+ {
+ "id": 2,
+ "name": "EMA",
+ "showName": "지수 이동평균선",
+ "vars": [
+ {
+ "id": 0,
+ "key": "lineTime1",
+ "name": "5일 선",
+ "default": 5,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ },
+ {
+ "id": 1,
+ "key": "lineTime2",
+ "name": "20일 선",
+ "default": 20,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ },
+ {
+ "id": 2,
+ "key": "lineTime3",
+ "name": "60일 선",
+ "default": 60,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ },
+ {
+ "id": 3,
+ "key": "lineTime4",
+ "name": "120일 선",
+ "default": 120,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ },
+ {
+ "id": 4,
+ "key": "lineTime5",
+ "name": "240일 선",
+ "default": 240,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ }
+ ]
+ },
+ {
+ "id": 3,
+ "name": "BBANDS",
+ "showName": "볼린저밴드",
+ "vars": [
+ {
+ "id": 0,
+ "key": "lineTime",
+ "name": "기간",
+ "default": 20,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ },
+ {
+ "id": 1,
+ "key": "stdev",
+ "name": "승수",
+ "default": 2,
+ "val": 0.01,
+ "max": 3,
+ "min": 1
+ }
+ ]
+ },
+ {
+ "id": 4,
+ "name": "SAR",
+ "showName": "Parabolic SAR",
+ "vars": [
+ {
+ "id": 0,
+ "key": "acc",
+ "name": "가속증가량",
+ "default": 0.02,
+ "val": 0.01,
+ "max": 100,
+ "min": 0.01
+ },
+ {
+ "id": 1,
+ "key": "accMax",
+ "name": "최대가속요소",
+ "default": 0.2,
+ "val": 0.01,
+ "max": 100,
+ "min": 0.01
+ }
+ ]
+ }
+]
diff --git a/public/Json/indiData.json b/public/Json/indiData.json
new file mode 100644
index 0000000..8eb47dd
--- /dev/null
+++ b/public/Json/indiData.json
@@ -0,0 +1,433 @@
+[
+ {
+ "id": 0,
+ "name": "MACD",
+ "showName": "MACD",
+ "vars": [
+ {
+ "id": 0,
+ "key": "longPeriod",
+ "name": "장기",
+ "default": 26,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ },
+ {
+ "id": 1,
+ "key": "shortPeriod",
+ "name": "단기",
+ "default": 12,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ },
+ {
+ "id": 2,
+ "key": "signalPeriod",
+ "name": "시그널",
+ "default": 9,
+ "val": 1,
+ "max": 480,
+ "min": 1
+ }
+ ]
+ },
+ {
+ "id": 1,
+ "name": "STOCHF",
+ "showName": "Stochastic Fast",
+ "vars": [
+ {
+ "id": 0,
+ "key": "K",
+ "name": "기간(%K)",
+ "default": 14,
+ "val": 1,
+ "max": 480,
+ "min": 1
+ },
+ {
+ "id": 1,
+ "key": "D",
+ "name": "기간(%D)",
+ "default": 3,
+ "val": 1,
+ "max": 480,
+ "min": 1
+ }
+ ]
+ },
+ {
+ "id": 2,
+ "name": "STOCH",
+ "showName": "Stochastic slow",
+ "vars": [
+ {
+ "id": 0,
+ "key": "Date",
+ "name": "기간",
+ "default": 14,
+ "val": 1,
+ "max": 480,
+ "min": 1
+ },
+ {
+ "id": 1,
+ "key": "K",
+ "name": "기간(%K)",
+ "default": 3,
+ "val": 1,
+ "max": 480,
+ "min": 1
+ },
+ {
+ "id": 2,
+ "key": "D",
+ "name": "기간(%D)",
+ "default": 3,
+ "val": 1,
+ "max": 480,
+ "min": 1
+ }
+ ]
+ },
+ {
+ "id": 3,
+ "name": "RSI",
+ "showName": "RSI",
+ "vars": [
+ {
+ "id": 0,
+ "key": "Date",
+ "name": "기간",
+ "default": 10,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ }
+ ]
+ },
+ {
+ "id": 4,
+ "name": "CCI",
+ "showName": "CCI",
+ "vars": [
+ {
+ "id": 0,
+ "key": "Date",
+ "name": "기간",
+ "default": 20,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ }
+ ]
+ },
+ {
+ "id": 5,
+ "name": "MOM",
+ "showName": "모멘텀",
+ "vars": [
+ {
+ "id": 0,
+ "key": "Date",
+ "name": "기간",
+ "default": 10,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ }
+ ]
+ },
+ {
+ "id": 6,
+ "name": "ROC",
+ "showName": "ROC",
+ "vars": [
+ {
+ "id": 0,
+ "key": "Date",
+ "name": "기간",
+ "default": 10,
+ "val": 1,
+ "max": 480,
+ "min": 1
+ }
+ ]
+ },
+ {
+ "id": 7,
+ "name": "AD",
+ "showName": "AD Line",
+ "vars": []
+ },
+ {
+ "id": 8,
+ "name": "ATR",
+ "showName": "ATR",
+ "vars": [
+ {
+ "id": 0,
+ "key": "Date",
+ "name": "기간",
+ "default": 20,
+ "val": 1,
+ "max": 480,
+ "min": 1
+ }
+ ]
+ },
+ {
+ "id": 9,
+ "name": "MFI",
+ "showName": "MFI",
+ "vars": [
+ {
+ "id": 0,
+ "key": "Date",
+ "name": "기간",
+ "default": 14,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ }
+ ]
+ },
+ {
+ "id": 10,
+ "name": "OBV",
+ "showName": "OBV",
+ "vars": []
+ },
+ {
+ "id": 11,
+ "name": "ADOSC",
+ "showName": "Chaikin Oscillator",
+ "vars": [
+ {
+ "id": 0,
+ "key": "longPeriod",
+ "name": "장기",
+ "default": 10,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ },
+ {
+ "id": 1,
+ "key": "shortPeriod",
+ "name": "단기",
+ "default": 3,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ }
+ ]
+ },
+ {
+ "id": 12,
+ "name": "TRIX",
+ "showName": "TRIX",
+ "vars": [
+ {
+ "id": 0,
+ "key": "Date",
+ "name": "기간",
+ "default": 12,
+ "val": 1,
+ "max": 480,
+ "min": 1
+ }
+ ]
+ },
+ {
+ "id": 13,
+ "name": "WILLR",
+ "showName": "Williams %R",
+ "vars": [
+ {
+ "id": 0,
+ "key": "Date",
+ "name": "기간",
+ "default": 14,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ }
+ ]
+ },
+ {
+ "id": 14,
+ "name": "DX",
+ "showName": "DMI",
+ "vars": [
+ {
+ "id": 0,
+ "key": "Date",
+ "name": "기간",
+ "default": 14,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ }
+ ]
+ },
+ {
+ "id": 15,
+ "name": "ADX",
+ "showName": "ADX",
+ "vars": [
+ {
+ "id": 0,
+ "key": "Date",
+ "name": "기간",
+ "default": 14,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ }
+ ]
+ },
+ {
+ "id": 16,
+ "name": "ADXR",
+ "showName": "ADXR",
+ "vars": [
+ {
+ "id": 0,
+ "key": "Date",
+ "name": "기간",
+ "default": 14,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ }
+ ]
+ },
+ {
+ "id": 17,
+ "name": "AROON",
+ "showName": "Aroon",
+ "vars": [
+ {
+ "id": 0,
+ "key": "Date",
+ "name": "기간",
+ "default": 25,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ }
+ ]
+ },
+ {
+ "id": 18,
+ "name": "AROONOSC",
+ "showName": "Aroon Oscillator",
+ "vars": [
+ {
+ "id": 0,
+ "key": "Date",
+ "name": "기간",
+ "default": 25,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ }
+ ]
+ },
+ {
+ "id": 19,
+ "name": "STOCHRSI",
+ "showName": "Stochastic RSI",
+ "vars": [
+ {
+ "id": 0,
+ "key": "Date",
+ "name": "RSI 기간",
+ "default": 14,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ },
+ {
+ "id": 1,
+ "key": "K",
+ "name": "기간(%K)",
+ "default": 14,
+ "val": 1,
+ "max": 480,
+ "min": 1
+ },
+ {
+ "id": 2,
+ "key": "D",
+ "name": "기간(%D)",
+ "default": 3,
+ "val": 1,
+ "max": 480,
+ "min": 1
+ }
+ ]
+ },
+ {
+ "id": 20,
+ "name": "ULTOSC",
+ "showName": "Ultimate Oscillator",
+ "vars": [
+ {
+ "id": 0,
+ "key": "longPeriod",
+ "name": "장기",
+ "default": 28,
+ "val": 1,
+ "max": 480,
+ "min": 1
+ },
+ {
+ "id": 1,
+ "key": "middlePeriod",
+ "name": "중기",
+ "default": 14,
+ "val": 1,
+ "max": 480,
+ "min": 1
+ },
+ {
+ "id": 2,
+ "key": "shortPeriod",
+ "name": "단기",
+ "default": 7,
+ "val": 1,
+ "max": 480,
+ "min": 1
+ }
+ ]
+ },
+ {
+ "id": 21,
+ "name": "PPO",
+ "showName": "PPO",
+ "vars": [
+ {
+ "id": 0,
+ "key": "longPeriod",
+ "name": "장기",
+ "default": 26,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ },
+ {
+ "id": 1,
+ "key": "shortPeriod",
+ "name": "단기",
+ "default": 12,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ }
+ ]
+ }
+]
diff --git a/public/icon/+.svg b/public/icon/+.svg
new file mode 100644
index 0000000..2fd77ea
--- /dev/null
+++ b/public/icon/+.svg
@@ -0,0 +1,4 @@
+
diff --git a/public/icon/+White.svg b/public/icon/+White.svg
new file mode 100644
index 0000000..cadfc9d
--- /dev/null
+++ b/public/icon/+White.svg
@@ -0,0 +1,4 @@
+
diff --git a/public/icon/-.svg b/public/icon/-.svg
new file mode 100644
index 0000000..ce8a7eb
--- /dev/null
+++ b/public/icon/-.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/icon/-White.svg b/public/icon/-White.svg
new file mode 100644
index 0000000..8dff620
--- /dev/null
+++ b/public/icon/-White.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/icon/BlankStar.svg b/public/icon/BlankStar.svg
new file mode 100644
index 0000000..dda5362
--- /dev/null
+++ b/public/icon/BlankStar.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/icon/Comment.svg b/public/icon/Comment.svg
new file mode 100644
index 0000000..a4f7d0f
--- /dev/null
+++ b/public/icon/Comment.svg
@@ -0,0 +1,4 @@
+
diff --git a/public/icon/FilledHeart.svg b/public/icon/FilledHeart.svg
new file mode 100644
index 0000000..5bda550
--- /dev/null
+++ b/public/icon/FilledHeart.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/icon/FilledStar.svg b/public/icon/FilledStar.svg
new file mode 100644
index 0000000..cf5ddc9
--- /dev/null
+++ b/public/icon/FilledStar.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/icon/Heart.svg b/public/icon/Heart.svg
new file mode 100644
index 0000000..469fdf7
--- /dev/null
+++ b/public/icon/Heart.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/icon/X.svg b/public/icon/X.svg
new file mode 100644
index 0000000..6e2e13a
--- /dev/null
+++ b/public/icon/X.svg
@@ -0,0 +1,4 @@
+
diff --git a/public/icon/alertNo.svg b/public/icon/alertNo.svg
new file mode 100644
index 0000000..cfdfc46
--- /dev/null
+++ b/public/icon/alertNo.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/icon/alertYes.svg b/public/icon/alertYes.svg
new file mode 100644
index 0000000..33ab518
--- /dev/null
+++ b/public/icon/alertYes.svg
@@ -0,0 +1,4 @@
+
diff --git a/public/icon/arrow.svg b/public/icon/arrow.svg
new file mode 100644
index 0000000..9816348
--- /dev/null
+++ b/public/icon/arrow.svg
@@ -0,0 +1,7 @@
+
diff --git a/public/icon/chat_mate.jpg b/public/icon/chat_mate.jpg
new file mode 100644
index 0000000..9b48bf1
Binary files /dev/null and b/public/icon/chat_mate.jpg differ
diff --git a/public/icon/dot.svg b/public/icon/dot.svg
new file mode 100644
index 0000000..dc0816c
--- /dev/null
+++ b/public/icon/dot.svg
@@ -0,0 +1,5 @@
+
diff --git a/public/icon/email.svg b/public/icon/email.svg
new file mode 100644
index 0000000..34b52b2
--- /dev/null
+++ b/public/icon/email.svg
@@ -0,0 +1,16 @@
+
diff --git a/public/icon/feed.svg b/public/icon/feed.svg
new file mode 100644
index 0000000..3ffd703
--- /dev/null
+++ b/public/icon/feed.svg
@@ -0,0 +1,13 @@
+
diff --git a/public/icon/feedOrange.svg b/public/icon/feedOrange.svg
new file mode 100644
index 0000000..bf0ceec
--- /dev/null
+++ b/public/icon/feedOrange.svg
@@ -0,0 +1,13 @@
+
diff --git a/public/icon/hot.svg b/public/icon/hot.svg
new file mode 100644
index 0000000..64c1ac4
--- /dev/null
+++ b/public/icon/hot.svg
@@ -0,0 +1,5 @@
+
diff --git a/public/icon/hotOrange.svg b/public/icon/hotOrange.svg
new file mode 100644
index 0000000..de42fbe
--- /dev/null
+++ b/public/icon/hotOrange.svg
@@ -0,0 +1,5 @@
+
diff --git a/public/icon/logo.svg b/public/icon/logo.svg
new file mode 100644
index 0000000..6b7cade
--- /dev/null
+++ b/public/icon/logo.svg
@@ -0,0 +1,20 @@
+
diff --git a/public/icon/market.svg b/public/icon/market.svg
new file mode 100644
index 0000000..36f70ee
--- /dev/null
+++ b/public/icon/market.svg
@@ -0,0 +1,19 @@
+
diff --git a/public/icon/marketOrange.svg b/public/icon/marketOrange.svg
new file mode 100644
index 0000000..509627c
--- /dev/null
+++ b/public/icon/marketOrange.svg
@@ -0,0 +1,19 @@
+
diff --git a/public/icon/me.svg b/public/icon/me.svg
new file mode 100644
index 0000000..004cdf0
--- /dev/null
+++ b/public/icon/me.svg
@@ -0,0 +1,4 @@
+
diff --git a/public/icon/mypage.svg b/public/icon/mypage.svg
new file mode 100644
index 0000000..b14d0c3
--- /dev/null
+++ b/public/icon/mypage.svg
@@ -0,0 +1,6 @@
+
diff --git a/public/icon/mypageOrange.svg b/public/icon/mypageOrange.svg
new file mode 100644
index 0000000..3c13005
--- /dev/null
+++ b/public/icon/mypageOrange.svg
@@ -0,0 +1,6 @@
+
diff --git a/public/icon/nickname.svg b/public/icon/nickname.svg
new file mode 100644
index 0000000..7f4912e
--- /dev/null
+++ b/public/icon/nickname.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/icon/password.svg b/public/icon/password.svg
new file mode 100644
index 0000000..ec417c3
--- /dev/null
+++ b/public/icon/password.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/icon/picture.svg b/public/icon/picture.svg
new file mode 100644
index 0000000..bc32e7f
--- /dev/null
+++ b/public/icon/picture.svg
@@ -0,0 +1,7 @@
+
diff --git a/public/icon/pictureGray.svg b/public/icon/pictureGray.svg
new file mode 100644
index 0000000..5b09103
--- /dev/null
+++ b/public/icon/pictureGray.svg
@@ -0,0 +1,7 @@
+
diff --git a/public/icon/redAlert.svg b/public/icon/redAlert.svg
new file mode 100644
index 0000000..65bb4ac
--- /dev/null
+++ b/public/icon/redAlert.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/icon/restart.svg b/public/icon/restart.svg
new file mode 100644
index 0000000..b0e53a3
--- /dev/null
+++ b/public/icon/restart.svg
@@ -0,0 +1,11 @@
+
diff --git a/public/icon/search.svg b/public/icon/search.svg
new file mode 100644
index 0000000..03c1a82
--- /dev/null
+++ b/public/icon/search.svg
@@ -0,0 +1,5 @@
+
diff --git a/public/icon/searchGray.svg b/public/icon/searchGray.svg
new file mode 100644
index 0000000..9faeef6
--- /dev/null
+++ b/public/icon/searchGray.svg
@@ -0,0 +1,5 @@
+
diff --git a/public/icon/strategy.svg b/public/icon/strategy.svg
new file mode 100644
index 0000000..10fcec7
--- /dev/null
+++ b/public/icon/strategy.svg
@@ -0,0 +1,7 @@
+
diff --git a/public/icon/strategyOrange.svg b/public/icon/strategyOrange.svg
new file mode 100644
index 0000000..9e329ee
--- /dev/null
+++ b/public/icon/strategyOrange.svg
@@ -0,0 +1,7 @@
+
diff --git a/public/icon/test.png b/public/icon/test.png
new file mode 100644
index 0000000..2bd0d7f
Binary files /dev/null and b/public/icon/test.png differ
diff --git a/public/icon/trading.svg b/public/icon/trading.svg
new file mode 100644
index 0000000..6f41295
--- /dev/null
+++ b/public/icon/trading.svg
@@ -0,0 +1,5 @@
+
diff --git a/public/icon/tradingOrange.svg b/public/icon/tradingOrange.svg
new file mode 100644
index 0000000..84bb2fa
--- /dev/null
+++ b/public/icon/tradingOrange.svg
@@ -0,0 +1,5 @@
+
diff --git a/public/icon/user.svg b/public/icon/user.svg
new file mode 100644
index 0000000..30ba37e
--- /dev/null
+++ b/public/icon/user.svg
@@ -0,0 +1,5 @@
+
diff --git a/public/icon/vote.svg b/public/icon/vote.svg
new file mode 100644
index 0000000..22353ac
--- /dev/null
+++ b/public/icon/vote.svg
@@ -0,0 +1,5 @@
+
diff --git a/public/vite.svg b/public/vite.svg
new file mode 100644
index 0000000..e7b8dfb
--- /dev/null
+++ b/public/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/App.jsx b/src/App.jsx
new file mode 100644
index 0000000..9740a1d
--- /dev/null
+++ b/src/App.jsx
@@ -0,0 +1,20 @@
+import { RouterProvider } from "react-router-dom";
+import mainRouter from "./router/main-router";
+import { Provider } from "react-redux";
+import { PersistGate } from "redux-persist/integration/react";
+import { store, persistor } from "./store/store";
+import { WebSocketProvider } from "./lib/hooks/useWebSocket";
+
+function App() {
+ return (
+
+
+
+
+
+
+
+ );
+}
+
+export default App;
diff --git a/src/Json/chartData.json b/src/Json/chartData.json
new file mode 100644
index 0000000..e6fae23
--- /dev/null
+++ b/src/Json/chartData.json
@@ -0,0 +1,208 @@
+[
+ {
+ "id": 0,
+ "name": "SMA",
+ "showName": "단순 이동평균선",
+ "vars": [
+ {
+ "id": 0,
+ "key": "lineTime1",
+ "name": "5일 선",
+ "default": 5,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ },
+ {
+ "id": 1,
+ "key": "lineTime2",
+ "name": "10일 선",
+ "default": 10,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ },
+ {
+ "id": 2,
+ "key": "lineTime3",
+ "name": "20일 선",
+ "default": 20,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ },
+ {
+ "id": 3,
+ "key": "lineTime4",
+ "name": "60일 선",
+ "default": 60,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ },
+ {
+ "id": 4,
+ "key": "lineTime5",
+ "name": "120일 선",
+ "default": 120,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ }
+ ]
+ },
+ {
+ "id": 1,
+ "name": "WMA",
+ "showName": "가중 이동평균선",
+ "vars": [
+ {
+ "id": 0,
+ "key": "lineTime1",
+ "name": "5일 선",
+ "default": 5,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ },
+ {
+ "id": 1,
+ "key": "lineTime2",
+ "name": "10일 선",
+ "default": 10,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ },
+ {
+ "id": 2,
+ "key": "lineTime3",
+ "name": "20일 선",
+ "default": 20,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ },
+ {
+ "id": 3,
+ "key": "lineTime4",
+ "name": "60일 선",
+ "default": 60,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ },
+ {
+ "id": 4,
+ "key": "lineTime5",
+ "name": "120일 선",
+ "default": 120,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ }
+ ]
+ },
+ {
+ "id": 2,
+ "name": "EMA",
+ "showName": "지수 이동평균선",
+ "vars": [
+ {
+ "id": 0,
+ "key": "lineTime1",
+ "name": "5일 선",
+ "default": 5,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ },
+ {
+ "id": 1,
+ "key": "lineTime2",
+ "name": "10일 선",
+ "default": 10,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ },
+ {
+ "id": 2,
+ "key": "lineTime3",
+ "name": "20일 선",
+ "default": 20,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ },
+ {
+ "id": 3,
+ "key": "lineTime4",
+ "name": "60일 선",
+ "default": 60,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ },
+ {
+ "id": 4,
+ "key": "lineTime5",
+ "name": "120일 선",
+ "default": 120,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ }
+ ]
+ },
+ {
+ "id": 3,
+ "name": "BBANDS",
+ "showName": "볼린저밴드",
+ "vars": [
+ {
+ "id": 0,
+ "key": "lineTime",
+ "name": "기간",
+ "default": 5,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ },
+ {
+ "id": 1,
+ "key": "stdev",
+ "name": "승수",
+ "default": 2,
+ "val": 0.01,
+ "max": 3,
+ "min": 1
+ }
+ ]
+ },
+ {
+ "id": 4,
+ "name": "SAR",
+ "showName": "Parabolic SAR",
+ "vars": [
+ {
+ "id": 0,
+ "key": "acc",
+ "name": "가속증가량",
+ "default": 0.02,
+ "val": 0.01,
+ "max": 100,
+ "min": 0.01
+ },
+ {
+ "id": 1,
+ "key": "accMax",
+ "name": "최대가속요소",
+ "default": 0.2,
+ "val": 0.01,
+ "max": 100,
+ "min": 0.01
+ }
+ ]
+ }
+]
diff --git a/src/Json/indiData.json b/src/Json/indiData.json
new file mode 100644
index 0000000..8eb47dd
--- /dev/null
+++ b/src/Json/indiData.json
@@ -0,0 +1,433 @@
+[
+ {
+ "id": 0,
+ "name": "MACD",
+ "showName": "MACD",
+ "vars": [
+ {
+ "id": 0,
+ "key": "longPeriod",
+ "name": "장기",
+ "default": 26,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ },
+ {
+ "id": 1,
+ "key": "shortPeriod",
+ "name": "단기",
+ "default": 12,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ },
+ {
+ "id": 2,
+ "key": "signalPeriod",
+ "name": "시그널",
+ "default": 9,
+ "val": 1,
+ "max": 480,
+ "min": 1
+ }
+ ]
+ },
+ {
+ "id": 1,
+ "name": "STOCHF",
+ "showName": "Stochastic Fast",
+ "vars": [
+ {
+ "id": 0,
+ "key": "K",
+ "name": "기간(%K)",
+ "default": 14,
+ "val": 1,
+ "max": 480,
+ "min": 1
+ },
+ {
+ "id": 1,
+ "key": "D",
+ "name": "기간(%D)",
+ "default": 3,
+ "val": 1,
+ "max": 480,
+ "min": 1
+ }
+ ]
+ },
+ {
+ "id": 2,
+ "name": "STOCH",
+ "showName": "Stochastic slow",
+ "vars": [
+ {
+ "id": 0,
+ "key": "Date",
+ "name": "기간",
+ "default": 14,
+ "val": 1,
+ "max": 480,
+ "min": 1
+ },
+ {
+ "id": 1,
+ "key": "K",
+ "name": "기간(%K)",
+ "default": 3,
+ "val": 1,
+ "max": 480,
+ "min": 1
+ },
+ {
+ "id": 2,
+ "key": "D",
+ "name": "기간(%D)",
+ "default": 3,
+ "val": 1,
+ "max": 480,
+ "min": 1
+ }
+ ]
+ },
+ {
+ "id": 3,
+ "name": "RSI",
+ "showName": "RSI",
+ "vars": [
+ {
+ "id": 0,
+ "key": "Date",
+ "name": "기간",
+ "default": 10,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ }
+ ]
+ },
+ {
+ "id": 4,
+ "name": "CCI",
+ "showName": "CCI",
+ "vars": [
+ {
+ "id": 0,
+ "key": "Date",
+ "name": "기간",
+ "default": 20,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ }
+ ]
+ },
+ {
+ "id": 5,
+ "name": "MOM",
+ "showName": "모멘텀",
+ "vars": [
+ {
+ "id": 0,
+ "key": "Date",
+ "name": "기간",
+ "default": 10,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ }
+ ]
+ },
+ {
+ "id": 6,
+ "name": "ROC",
+ "showName": "ROC",
+ "vars": [
+ {
+ "id": 0,
+ "key": "Date",
+ "name": "기간",
+ "default": 10,
+ "val": 1,
+ "max": 480,
+ "min": 1
+ }
+ ]
+ },
+ {
+ "id": 7,
+ "name": "AD",
+ "showName": "AD Line",
+ "vars": []
+ },
+ {
+ "id": 8,
+ "name": "ATR",
+ "showName": "ATR",
+ "vars": [
+ {
+ "id": 0,
+ "key": "Date",
+ "name": "기간",
+ "default": 20,
+ "val": 1,
+ "max": 480,
+ "min": 1
+ }
+ ]
+ },
+ {
+ "id": 9,
+ "name": "MFI",
+ "showName": "MFI",
+ "vars": [
+ {
+ "id": 0,
+ "key": "Date",
+ "name": "기간",
+ "default": 14,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ }
+ ]
+ },
+ {
+ "id": 10,
+ "name": "OBV",
+ "showName": "OBV",
+ "vars": []
+ },
+ {
+ "id": 11,
+ "name": "ADOSC",
+ "showName": "Chaikin Oscillator",
+ "vars": [
+ {
+ "id": 0,
+ "key": "longPeriod",
+ "name": "장기",
+ "default": 10,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ },
+ {
+ "id": 1,
+ "key": "shortPeriod",
+ "name": "단기",
+ "default": 3,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ }
+ ]
+ },
+ {
+ "id": 12,
+ "name": "TRIX",
+ "showName": "TRIX",
+ "vars": [
+ {
+ "id": 0,
+ "key": "Date",
+ "name": "기간",
+ "default": 12,
+ "val": 1,
+ "max": 480,
+ "min": 1
+ }
+ ]
+ },
+ {
+ "id": 13,
+ "name": "WILLR",
+ "showName": "Williams %R",
+ "vars": [
+ {
+ "id": 0,
+ "key": "Date",
+ "name": "기간",
+ "default": 14,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ }
+ ]
+ },
+ {
+ "id": 14,
+ "name": "DX",
+ "showName": "DMI",
+ "vars": [
+ {
+ "id": 0,
+ "key": "Date",
+ "name": "기간",
+ "default": 14,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ }
+ ]
+ },
+ {
+ "id": 15,
+ "name": "ADX",
+ "showName": "ADX",
+ "vars": [
+ {
+ "id": 0,
+ "key": "Date",
+ "name": "기간",
+ "default": 14,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ }
+ ]
+ },
+ {
+ "id": 16,
+ "name": "ADXR",
+ "showName": "ADXR",
+ "vars": [
+ {
+ "id": 0,
+ "key": "Date",
+ "name": "기간",
+ "default": 14,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ }
+ ]
+ },
+ {
+ "id": 17,
+ "name": "AROON",
+ "showName": "Aroon",
+ "vars": [
+ {
+ "id": 0,
+ "key": "Date",
+ "name": "기간",
+ "default": 25,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ }
+ ]
+ },
+ {
+ "id": 18,
+ "name": "AROONOSC",
+ "showName": "Aroon Oscillator",
+ "vars": [
+ {
+ "id": 0,
+ "key": "Date",
+ "name": "기간",
+ "default": 25,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ }
+ ]
+ },
+ {
+ "id": 19,
+ "name": "STOCHRSI",
+ "showName": "Stochastic RSI",
+ "vars": [
+ {
+ "id": 0,
+ "key": "Date",
+ "name": "RSI 기간",
+ "default": 14,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ },
+ {
+ "id": 1,
+ "key": "K",
+ "name": "기간(%K)",
+ "default": 14,
+ "val": 1,
+ "max": 480,
+ "min": 1
+ },
+ {
+ "id": 2,
+ "key": "D",
+ "name": "기간(%D)",
+ "default": 3,
+ "val": 1,
+ "max": 480,
+ "min": 1
+ }
+ ]
+ },
+ {
+ "id": 20,
+ "name": "ULTOSC",
+ "showName": "Ultimate Oscillator",
+ "vars": [
+ {
+ "id": 0,
+ "key": "longPeriod",
+ "name": "장기",
+ "default": 28,
+ "val": 1,
+ "max": 480,
+ "min": 1
+ },
+ {
+ "id": 1,
+ "key": "middlePeriod",
+ "name": "중기",
+ "default": 14,
+ "val": 1,
+ "max": 480,
+ "min": 1
+ },
+ {
+ "id": 2,
+ "key": "shortPeriod",
+ "name": "단기",
+ "default": 7,
+ "val": 1,
+ "max": 480,
+ "min": 1
+ }
+ ]
+ },
+ {
+ "id": 21,
+ "name": "PPO",
+ "showName": "PPO",
+ "vars": [
+ {
+ "id": 0,
+ "key": "longPeriod",
+ "name": "장기",
+ "default": 26,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ },
+ {
+ "id": 1,
+ "key": "shortPeriod",
+ "name": "단기",
+ "default": 12,
+ "val": 1,
+ "max": 480,
+ "min": 2
+ }
+ ]
+ }
+]
diff --git a/src/components/.gitkeep b/src/components/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/src/components/Feed/FeedShow/Comment.jsx b/src/components/Feed/FeedShow/Comment.jsx
new file mode 100644
index 0000000..639ffe1
--- /dev/null
+++ b/src/components/Feed/FeedShow/Comment.jsx
@@ -0,0 +1,208 @@
+import React, { useEffect, useState } from "react";
+import styled from "styled-components";
+import * as S from "../../../style/GlobalStyle";
+import { useDispatch, useSelector } from "react-redux";
+import TextareaAutosize from "react-textarea-autosize";
+import {
+ fetchComments,
+ postComment,
+ deleteComment,
+} from "../../../store/reducers/Feed/comment";
+import UserDetail from "../../MyPage/UserDetail";
+
+const Comment = ({ feedId }) => {
+ const dispatch = useDispatch();
+
+ const [input, setInput] = useState("");
+ const [showAll, setShowAll] = useState(false);
+ const [currentPage, setCurrentPage] = useState(1);
+ const [selectedFriend, setSelectedFriend] = useState(null);
+
+ useEffect(() => {
+ dispatch(fetchComments(feedId));
+ }, [dispatch, feedId]);
+
+ const comments = useSelector((state) => state.comment.comments[feedId]) || [];
+ const userId = useSelector((state) => state.user.user.id);
+
+ const commentsPerPage = 5;
+ const totalComments = comments.length;
+
+ const handleShowMore = () => {
+ setCurrentPage(currentPage + 1);
+ };
+
+ const handleFirstPage = () => {
+ setShowAll(false);
+ setCurrentPage(1);
+ };
+
+ const onPostComment = () => {
+ dispatch(postComment({ feedId: feedId, content: input }));
+ setInput("");
+ };
+
+ const onDeleteComment = (id) => {
+ if (window.confirm("댓글을 삭제하시겠습니까?")) {
+ dispatch(deleteComment(id));
+ alert("댓글이 삭제되었습니다.");
+ }
+ };
+
+ const handleShowAll = () => {
+ setShowAll(true);
+ setCurrentPage(Math.ceil(totalComments / commentsPerPage));
+ };
+
+ const toggleUser = (item) => {
+ setSelectedFriend(item);
+ };
+
+ return (
+
+ 댓글 {totalComments}
+ {comments
+ .slice(0, showAll ? totalComments : currentPage * commentsPerPage)
+ .map((item, idx) => (
+
+
+
+ toggleUser(item.user)}>
+ {item.user.nickname}
+
+ {item.createdAt}
+ {item.content}
+
+ {item.user._id === userId && (
+
+ onDeleteComment(item._id)}
+ style={{ width: "100%" }}
+ >
+ 삭제
+
+
+ )}
+
+
+
+ ))}
+ {!showAll && totalComments > currentPage * commentsPerPage ? (
+
+ 더보기..
+ >>
+
+ ) : totalComments <= commentsPerPage ? (
+ <>>
+ ) : (
+ <>
+ 접기
+ >
+ )}
+
+ setInput(e.target.value)}
+ />
+ →
+
+
+ );
+};
+
+const CommentWrapper = styled.div``;
+
+const LengthDiv = styled.div`
+ border-top: 1px solid #dadada;
+ margin: 0px 25px;
+ padding: 16px 0px 8px 5px;
+`;
+
+const Div = styled.div`
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+`;
+
+const CommentDiv = styled.div`
+ padding: 8px 0px 10px 30px;
+ width: 85%;
+ font-size: 16px;
+`;
+
+const NicknameDiv = styled.div`
+ cursor: pointer;
+`;
+
+const DateDiv = styled.div`
+ color: #c1c1c1;
+ font-size: 13px;
+ margin-bottom: 3px;
+`;
+
+const ContentDiv = styled.div``;
+
+const DeleteDiv = styled.div`
+ width: 15%;
+ padding-right: 30px;
+ color: #909090;
+ font-size: 14px;
+`;
+
+const WriteDiv = styled.div`
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 5%;
+ margin: 0px 25px 10px 25px;
+ border-top: ${(props) =>
+ props.$totalComments != 0 ? "1px solid #dadada" : null};
+ padding-top: 10px;
+
+ .textarea {
+ resize: none;
+ width: 80%;
+ min-height: 35px;
+ max-height: 70px;
+ padding-left: 10px;
+ padding-top: 5px;
+ }
+`;
+
+const InputBtn = styled.button`
+ width: 13%;
+ height: 35px;
+ background-color: #ff8b8b;
+ border: none;
+ border-radius: 7px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: white;
+
+ &:hover {
+ background-color: #ff3f3f;
+ }
+`;
+
+const ShowMoreButton = styled.button`
+ margin: 10px 0px 10px 30px;
+ border: none;
+ background: none;
+ color: #a5a4a4;
+ padding-left: 0px;
+`;
+
+export default Comment;
diff --git a/src/components/Feed/FeedShow/Feed.jsx b/src/components/Feed/FeedShow/Feed.jsx
new file mode 100644
index 0000000..02497f6
--- /dev/null
+++ b/src/components/Feed/FeedShow/Feed.jsx
@@ -0,0 +1,160 @@
+import React, { useEffect, useRef, useState } from "react";
+import styled from "styled-components";
+import { useDispatch, useSelector } from "react-redux";
+import {
+ fetchAllFeed,
+ fetchMyFeed,
+ fetchOtherFeed,
+} from "../../../store/reducers/Feed/feed";
+import FeedBoard from "./FeedBoard";
+import FeedOrder from "./FeedOrder";
+import FeedReturns from "./FeedReturns";
+import FeedVote from "./FeedVote";
+import UserDetail from "../../MyPage/UserDetail";
+import { PuffLoader } from "react-spinners";
+
+const Feed = ({ path, friendId }) => {
+ const dispatch = useDispatch();
+
+ const [isBottom, setIsBottom] = useState(false);
+ const [page, setPage] = useState(1);
+ const [fetching, setFetching] = useState(false);
+ const [selectedFriend, setSelectedFriend] = useState(null);
+ const userId = useSelector((state) => state.user.user.id);
+ const loading = useSelector((state) => state.feed.loading);
+
+ useEffect(() => {
+ if (path === "/feed") {
+ if (isBottom) {
+ setFetching(true);
+ dispatch(fetchAllFeed(page))
+ .then(() => setFetching(false))
+ .catch(() => setFetching(false));
+ } else {
+ dispatch(fetchAllFeed(1));
+ }
+ } else if (friendId) {
+ if (isBottom) {
+ setFetching(true);
+ } else {
+ dispatch(fetchOtherFeed({ userId: "-1", page: 1 }));
+ dispatch(fetchOtherFeed({ userId: friendId, page: 1 }));
+ }
+ } else if (path === "/mypage" && userId) {
+ if (isBottom) {
+ setFetching(true);
+ dispatch(fetchMyFeed({ userId: userId, page: page }))
+ .then(() => setFetching(false))
+ .catch(() => setFetching(false));
+ } else {
+ dispatch(fetchMyFeed({ userId: userId, page: 1 }));
+ }
+ }
+ }, [dispatch, path, userId, isBottom, page, friendId]);
+
+ const feedData = useSelector((state) => {
+ if (path === "/feed") return state.feed.allFeed;
+ else if (friendId) return state.feed.otherFeed;
+ else if (path === "/mypage") return state.feed.myFeed;
+ return [];
+ });
+
+ const toggleUser = (item) => {
+ setSelectedFriend(item);
+ };
+
+ const scrollRef = useRef();
+
+ const handleScroll = () => {
+ const scrollHeight = scrollRef.current.scrollHeight;
+ const scrollTop = scrollRef.current.scrollTop;
+ const clientHeight = scrollRef.current.clientHeight;
+
+ let cal;
+ if (path === "/feed") {
+ cal = scrollTop + clientHeight + 0.5;
+ } else if (friendId) {
+ cal = scrollTop + clientHeight;
+ } else {
+ cal = scrollTop + clientHeight + 1;
+ }
+ if (cal >= scrollHeight && !fetching) {
+ setIsBottom(true);
+ setPage((prevPage) => prevPage + 1);
+ }
+ };
+
+ useEffect(() => {
+ const scrollElement = scrollRef.current;
+ if (scrollElement) {
+ scrollElement.addEventListener("scroll", handleScroll);
+ return () => {
+ scrollElement.removeEventListener("scroll", handleScroll);
+ };
+ }
+ }, [handleScroll]);
+
+ if (feedData.length == 0) {
+ if (loading !== "fulfilled") {
+ return (
+
+
+
+ );
+ }
+ return 아직 작성된 글이 없습니다.;
+ }
+
+ return (
+
+ {feedData?.map((item, idx) => {
+ return (
+
+
+ {item.isOrder ? (
+
+ ) : item.isProfit ? (
+
+ ) : item.isVote ? (
+
+ ) : (
+
+ )}
+
+
+
+ );
+ })}
+
+ );
+};
+
+const Container = styled.div`
+ background-color: white;
+ height: 100vh;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 20px;
+`;
+
+const FeedScrollDiv = styled.div`
+ overflow-y: auto;
+ overflow-x: hidden;
+ scrollbar-width: thin;
+`;
+
+export default Feed;
diff --git a/src/components/Feed/FeedShow/FeedBoard.jsx b/src/components/Feed/FeedShow/FeedBoard.jsx
new file mode 100644
index 0000000..d8b66a5
--- /dev/null
+++ b/src/components/Feed/FeedShow/FeedBoard.jsx
@@ -0,0 +1,58 @@
+import React, { useState } from "react";
+import FeedLayout from "./FeedLayout";
+import styled from "styled-components";
+import * as S from "../../../style/GlobalStyle";
+
+const FeedBoard = ({ item, toggleUser }) => {
+ const [modalOpen, setModalOpen] = useState(false);
+
+ const openModal = () => {
+ setModalOpen(true);
+ };
+
+ const closeModal = () => {
+ setModalOpen(false);
+ };
+
+ return (
+
+
+ {item.body}
+ {item.photoUrl ? (
+
+ ) : (
+ <>>
+ )}
+ {modalOpen && (
+
+
+
+ )}
+
+
+ );
+};
+
+const Img = styled.img`
+ width: 100%;
+ cursor: pointer; // 마우스 커서를 변경하여 사용자에게 이미지 클릭 가능 여부를 알려줍니다.
+`;
+
+const ModalOverlay = styled.div`
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.5); // 반투명한 검은색 배경
+ display: flex;
+ justify-content: center;
+ align-items: center;
+`;
+
+const ModalImg = styled.img`
+ max-width: 90%;
+ max-height: 90%;
+`;
+
+export default FeedBoard;
diff --git a/src/components/Feed/FeedShow/FeedLayout.jsx b/src/components/Feed/FeedShow/FeedLayout.jsx
new file mode 100644
index 0000000..450f56e
--- /dev/null
+++ b/src/components/Feed/FeedShow/FeedLayout.jsx
@@ -0,0 +1,83 @@
+import React, { useState } from "react";
+import * as S from "../../../style/GlobalStyle";
+import { useDispatch, useSelector } from "react-redux";
+import {
+ deleteFeed,
+ postLike,
+ postUnlike,
+} from "../../../store/reducers/Feed/feed";
+import Comment from "./Comment";
+
+const FeedLayout = ({ item, toggleUser, children }) => {
+ const dispatch = useDispatch();
+
+ const [like, setLike] = useState(item.like);
+ const [isMyLike, setIsMyLike] = useState(item.isLike);
+ const [check, setCheck] = useState(false);
+
+ const comments = useSelector((state) => state.comment.comments[item._id]);
+ const userId = useSelector((state) => state.user.user.id);
+
+ const onLike = () => {
+ if (!isMyLike) {
+ dispatch(postLike(item._id));
+ setLike((prevLike) => prevLike + 1);
+ setIsMyLike(true);
+ } else {
+ dispatch(postUnlike(item._id));
+ setLike((prevLike) => prevLike - 1);
+ setIsMyLike(false);
+ }
+ };
+
+ return (
+
+
+
+ toggleUser(item.user)}>
+ {item.user.nickname}
+
+ {item.createdAt}
+
+ {item.user._id === userId && (
+ dispatch(deleteFeed(item._id))}>
+ 삭제
+
+ )}
+
+ {children}
+
+
+
+ {like}
+
+
+
setCheck((prev) => !prev)}>
+
+
+ {comments ? comments?.length : item.commentsCount}
+
+
+
+
+ {check ? : <>>}
+
+ );
+};
+
+export default FeedLayout;
diff --git a/src/components/Feed/FeedShow/FeedOrder.jsx b/src/components/Feed/FeedShow/FeedOrder.jsx
new file mode 100644
index 0000000..1af9663
--- /dev/null
+++ b/src/components/Feed/FeedShow/FeedOrder.jsx
@@ -0,0 +1,50 @@
+import React from "react";
+import FeedLayout from "./FeedLayout";
+import styled from "styled-components";
+import * as S from "../../../style/GlobalStyle";
+import { getLogoFileName } from "~/util/getLogoFileName";
+import { onErrorImg } from "~/util/getLogoFileName";
+
+const FeedOrder = ({ item, toggleUser }) => {
+ const order = item.order.buyOrSell == "buy" ? "매수" : "매도";
+ return (
+
+
+
+
+
+ {item.order.name}
+ {item.order.quantity}주
+ {order}
+
+
+
+
+ );
+};
+
+const QuantityDiv = styled.div`
+ margin-left: 7px;
+ color: #000;
+ font-size: 16px;
+ font-weight: 500;
+`;
+
+const OrderDiv = styled.div`
+ margin-left: 7px;
+ color: #000;
+ font-size: 16px;
+ font-weight: 500;
+`;
+
+export default FeedOrder;
diff --git a/src/components/Feed/FeedShow/FeedReturns.jsx b/src/components/Feed/FeedShow/FeedReturns.jsx
new file mode 100644
index 0000000..3793ef5
--- /dev/null
+++ b/src/components/Feed/FeedShow/FeedReturns.jsx
@@ -0,0 +1,27 @@
+import React from "react";
+import FeedLayout from "./FeedLayout";
+import styled from "styled-components";
+import * as S from "../../../style/GlobalStyle";
+
+const FeedReturns = ({ item, toggleUser }) => {
+ return (
+
+
+
+
+ 나의 전체 수익률은?
+ {item.profit}%
+
+
+
+
+ );
+};
+
+const ReturnDiv = styled.div`
+ margin-left: auto;
+ color: ${(props) =>
+ parseFloat(props.$returns) >= 0 ? "#ee2f2a" : "#2679ed"};
+`;
+
+export default FeedReturns;
diff --git a/src/components/Feed/FeedShow/FeedVote.jsx b/src/components/Feed/FeedShow/FeedVote.jsx
new file mode 100644
index 0000000..6209e00
--- /dev/null
+++ b/src/components/Feed/FeedShow/FeedVote.jsx
@@ -0,0 +1,141 @@
+import React, { useState } from "react";
+import styled from "styled-components";
+import * as S from "../../../style/GlobalStyle";
+import { useDispatch } from "react-redux";
+import { postVote } from "../../../store/reducers/Feed/feed";
+import FeedLayout from "./FeedLayout";
+
+const FeedVote = ({ item, toggleUser }) => {
+ const dispatch = useDispatch();
+
+ const [isMyVote, setIsMyVote] = useState(item.myVote);
+ const [O, setO] = useState(item.vote.yes);
+ const [X, setX] = useState(item.vote.no);
+
+ const onVote = (vote) => {
+ dispatch(postVote({ feedId: item._id, voteResult: vote }));
+ setIsMyVote(true);
+ if (vote === "yes") {
+ setO((prev) => prev + 1);
+ } else {
+ setX((prev) => prev + 1);
+ }
+ };
+
+ const calc = (num) => {
+ return Math.round((num / (O + X)) * 100);
+ };
+
+ return (
+
+
+
+ {item.body}
+ {!isMyVote ? (
+
+ onVote("yes")}
+ >
+ O
+
+ onVote("no")}
+ >
+ X
+
+
+ ) : (
+
+
+ O
+ {calc(O)}%
+
+
+
+
+
+
+ X
+ {calc(X)}%
+
+
+ )}
+
+
+
+ );
+};
+
+const ButtonWrapper = styled.div`
+ display: flex;
+ gap: 30px;
+ margin-bottom: 25px;
+ margin-top: 5px;
+ justify-content: center;
+`;
+
+const VoteBtn = styled.button`
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: 150px;
+ height: 44px;
+ border: none;
+ border-radius: 10px;
+ background: ${(props) => props.color};
+
+ color: #fff;
+ font-size: 25px;
+ font-weight: 600;
+
+ &:hover {
+ background: ${(props) => props.$hover};
+ }
+`;
+
+const BarDiv = styled.div`
+ position: relative;
+ width: 250px;
+ height: 30px;
+`;
+
+const ODiv = styled.div`
+ position: absolute;
+ left: 0;
+ top: 0;
+ width: ${(props) => props.$width}%;
+ height: 30px;
+ border-radius: ${(props) =>
+ props.$width == 100 ? "10px" : "10px 0px 0px 10px"};
+ background: #bee4ff;
+`;
+
+const XDiv = styled.div`
+ position: absolute;
+ right: 0;
+ top: 0;
+ width: ${(props) => props.$width}%;
+ height: 30px;
+ border-radius: ${(props) =>
+ props.$width == 100 ? "10px" : "0px 10px 10px 0px"};
+ background: #ffe3d7;
+`;
+
+const OXWrapper = styled.div`
+ display: flex;
+ gap: 10px;
+ margin-bottom: 25px;
+ margin-top: 5px;
+ justify-content: center;
+`;
+
+const OXDiv = styled.div`
+ text-align: center;
+ font-size: 15px;
+`;
+
+export default FeedVote;
diff --git a/src/components/Feed/FeedWriting.jsx b/src/components/Feed/FeedWriting.jsx
new file mode 100644
index 0000000..0262102
--- /dev/null
+++ b/src/components/Feed/FeedWriting.jsx
@@ -0,0 +1,274 @@
+import React, { useState, useRef, useCallback, useEffect } from "react";
+import styled from "styled-components";
+import { useDispatch, useSelector } from "react-redux";
+import { postBoardFeed, postVoteFeed } from "../../store/reducers/Feed/feed";
+
+const FeedWriting = ({ setIsWrite }) => {
+ const dispatch = useDispatch();
+
+ const inputRef = useRef();
+ const [previewImage, setPreviewImage] = useState(null);
+ const [selectedImage, setSelectedImage] = useState(null);
+ const [body, setBody] = useState("");
+ const [isVote, setIsVote] = useState(false);
+ const [modalOpen, setModalOpen] = useState(false);
+ const loading = useSelector((state) => state.feed.loading);
+ const userNickname = useSelector((state) => state.user.user.nickname);
+
+ const onUploadImage = useCallback((e) => {
+ const file = e.target.files[0];
+ setSelectedImage(file);
+ if (!file) {
+ return;
+ }
+ const reader = new FileReader();
+ reader.onloadend = () => {
+ setPreviewImage(reader.result);
+ };
+ reader.readAsDataURL(file);
+ }, []);
+
+ const onUploadImageButtonClick = useCallback(() => {
+ if (!inputRef.current) {
+ return;
+ }
+ inputRef.current.click();
+ }, []);
+
+ const openModal = () => {
+ setModalOpen(true);
+ };
+
+ const closeModal = () => {
+ setModalOpen(false);
+ };
+
+ const clickVote = () => {
+ setIsVote(!isVote);
+ setSelectedImage(null);
+ setPreviewImage(null);
+ };
+
+ const onSubmit = () => {
+ try {
+ const formData = new FormData();
+ if (isVote) {
+ dispatch(postVoteFeed(body));
+ window.location.reload();
+ } else {
+ formData.append("body", body);
+
+ if (selectedImage) {
+ formData.append("file", selectedImage);
+ }
+ dispatch(postBoardFeed(formData));
+ }
+ } catch (err) {
+ console.error(err);
+ }
+ };
+
+ useEffect(() => {
+ if (loading == "go") {
+ window.location.reload();
+ }
+ }, [loading]);
+
+ return (
+
+
+
+ {userNickname}
+
setIsWrite(false)}
+ />
+
+ {isVote ? (
+
+ O / X 투표
+ {
+ setBody(e.target.value);
+ }}
+ />
+
+ ) : (
+ setBody(e.target.value)}
+ />
+ )}
+ {previewImage && (
+
+
+
+ )}
+ {modalOpen && (
+
+
+
+ )}
+
+
+
+
+ 사진
+
+
+
+ 투표
+
+ {loading == "wait" ? (
+ wait
+ ) : (
+ 게시
+ )}
+
+
+ );
+};
+
+const Container = styled.div`
+ display: flex;
+ flex-direction: column;
+ padding: 20px 25px 0px 25px;
+ background-color: white;
+ margin-bottom: 5px;
+`;
+
+const UserContainer = styled.div`
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+`;
+
+const UserNickname = styled.div`
+ margin-left: 13px;
+ font-size: 16px;
+ font-weight: 600;
+ line-height: normal;
+`;
+
+const StyledTextarea = styled.textarea`
+ margin: 10px;
+ resize: none;
+ border: none;
+ height: 100px;
+ padding-left: 10px;
+ padding-right: 10px;
+ &::-webkit-scrollbar {
+ display: none;
+ }
+`;
+
+const UploadContainer = styled.div`
+ border-top: 1px solid #dadada;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+`;
+
+const ButtonDiv = styled.button`
+ display: flex;
+ flex-direction: row;
+ width: 80px;
+ gap: 5px;
+ cursor: pointer;
+ margin-right: 10px;
+ border: none;
+ background: none;
+`;
+
+const UploadBtn = styled.button`
+ margin-left: auto;
+ border-radius: 7px;
+ border: none;
+ background: #ff8049;
+ width: 53px;
+ height: 29px;
+ color: #fefefe;
+ font-size: 15px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: normal;
+
+ &:hover {
+ background: #ff5208;
+ }
+`;
+
+const PreviewImageContainer = styled.div`
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+`;
+
+const VoteWrapper = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ margin: 15px 0px;
+`;
+
+const VoteDiv = styled.div`
+ margin-right: auto;
+ margin-left: 20px;
+`;
+
+const VoteTextarea = styled.textarea`
+ display: flex;
+ resize: none;
+ width: 90%;
+ height: 70px;
+ padding: 10px 10px;
+ margin: 10px 0px 10px 0px;
+ border-radius: 7px;
+ border: 1px solid #d9d9d9;
+
+ &::-webkit-scrollbar {
+ display: none;
+ }
+`;
+
+const ModalOverlay = styled.div`
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.5); // 반투명한 검은색 배경
+ display: flex;
+ justify-content: center;
+ align-items: center;
+`;
+
+const ModalImg = styled.img`
+ max-width: 90%;
+ max-height: 90%;
+`;
+
+export default FeedWriting;
diff --git a/src/components/MyAccount/Account.jsx b/src/components/MyAccount/Account.jsx
new file mode 100644
index 0000000..72e824c
--- /dev/null
+++ b/src/components/MyAccount/Account.jsx
@@ -0,0 +1,112 @@
+import React from "react";
+import { useDispatch, useSelector } from "react-redux";
+import { styled } from "styled-components";
+import { postMyProfit } from "../../store/reducers/Feed/feed";
+
+const Account = () => {
+ const dispatch = useDispatch();
+ const myMoney = useSelector((state) => state.order.myMoney);
+
+ const shareProfit = () => {
+ const confirmed = window.confirm("전체 수익률을 공유하시겠습니까?");
+ if (confirmed) {
+ dispatch(postMyProfit(myMoney.wholeUpDownRate));
+ window.location.reload();
+ }
+ };
+
+ return (
+ <>
+ 공유
+
+
+ 총 평가자산
+ {myMoney.wholeMoney}원
+
+
+
+
+ 보유현금
+ {myMoney.cash}
+
+
+ 전체 평가금
+ {myMoney.wholeStockPrice}
+
+
+
+
+ 전체 수익금
+ = 0 ? "#015FFF" : "#F00" }}
+ >
+ {myMoney.wholeUpDown}
+
+
+
+ 전체 수익률
+ = 0 ? "#015FFF" : "#F00",
+ }}
+ >
+ {myMoney.wholeUpDownRate}%
+
+
+
+
+ >
+ );
+};
+
+const UploadDiv = styled.div`
+ margin: 10px 20px 0px auto;
+ color: gray;
+ font-size: 15px;
+ cursor: pointer;
+ &:hover {
+ color: black;
+ }
+`;
+
+const AccountContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ background-color: #f2f2f2;
+ width: 92%;
+ border-radius: 5px;
+ padding: 15px 10px;
+ margin: 10px auto;
+`;
+
+const EvaluatedAssetsWrapper = styled.div`
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+ font-size: 20px;
+ padding: 0px 5px 15px 5px;
+`;
+const EADiv = styled.div`
+ font-size: 17px;
+`;
+const EAValue = styled.div``;
+const RowDiv = styled.div`
+ display: flex;
+ flex-direction: row;
+ gap: 20px;
+ padding: 10px 0px 0px 0px;
+`;
+const Wrapper = styled.div`
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ width: 50%;
+ font-size: 14.5px;
+`;
+const Div = styled.div``;
+const ValueDiv = styled.div`
+ color: ${(props) => props.$color};
+`;
+
+export default Account;
diff --git a/src/components/MyAccount/MyAccount.jsx b/src/components/MyAccount/MyAccount.jsx
new file mode 100644
index 0000000..fd381a4
--- /dev/null
+++ b/src/components/MyAccount/MyAccount.jsx
@@ -0,0 +1,50 @@
+import React, { useEffect } from "react";
+import Account from "./Account";
+import Share from "./Share";
+import { styled } from "styled-components";
+import { useDispatch, useSelector } from "react-redux";
+import { fetchMyOrder } from "../../store/reducers/User/order";
+import { PuffLoader } from "react-spinners";
+
+const MyAccount = () => {
+ const dispatch = useDispatch();
+ useEffect(() => {
+ dispatch(fetchMyOrder());
+ }, []);
+ const loading = useSelector((state) => state.order.loading);
+
+ return (
+
+ {loading !== "fulfilled" ? (
+
+ ) : (
+ <>
+
+
+ >
+ )}
+
+ );
+};
+
+const Container = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ background-color: white;
+ width: 100%;
+ overflow: hidden;
+`;
+
+export default MyAccount;
diff --git a/src/components/MyAccount/Share.jsx b/src/components/MyAccount/Share.jsx
new file mode 100644
index 0000000..cdc2dd5
--- /dev/null
+++ b/src/components/MyAccount/Share.jsx
@@ -0,0 +1,123 @@
+import React from "react";
+import { useSelector } from "react-redux";
+import { styled } from "styled-components";
+import { getLogoFileName, onErrorImg } from "~/util/getLogoFileName";
+
+// TODO : item.name 확인하기
+const Share = () => {
+ const mystocks = useSelector((state) => state.order.mystocks);
+
+ return (
+
+ 보유 종목
+
+ {mystocks?.map((item, idx) => (
+
+
+
+
+ {item.name}
+ {item.amount}주
+
+
+
+ {item.whole}원
+
+ = 0 ? "#015FFF" : "#F00"}>
+ {item.price}원
+
+ = 0 ? "#015FFF" : "#F00"}
+ >
+ ({item.upDownRate}%)
+
+
+
+
+ ))}
+
+
+ );
+};
+
+const Container = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ width: 100%;
+`;
+
+const ShareContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ height: calc(100vh - 550px);
+ overflow-y: auto;
+ &::-webkit-scrollbar {
+ display: none;
+ }
+`;
+
+const ShareItem = styled.div`
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+ padding: 10px;
+ margin: 7px 15px;
+`;
+
+const ColumnDiv = styled.div`
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+`;
+
+const NameDiv = styled.div`
+ font-size: 16px;
+ font-weight: 700;
+`;
+
+const QuantityDiv = styled.div`
+ color: #8c8c8c;
+ font-size: 12px;
+ font-weight: 400;
+`;
+
+const ProfitDiv = styled.div`
+ text-align: right;
+ font-size: 16px;
+ font-weight: 700;
+`;
+
+const CurrentDiv = styled.div`
+ color: ${(props) => props.$color};
+ font-size: 16px;
+ font-weight: 400;
+`;
+
+const ReturnRateDiv = styled.div`
+ color: ${(props) => props.$color};
+ font-size: 16px;
+ font-weight: 400;
+`;
+
+const RowDiv = styled.div`
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+`;
+
+export default Share;
diff --git a/src/components/MyNavbar.jsx b/src/components/MyNavbar.jsx
new file mode 100644
index 0000000..8d7b49a
--- /dev/null
+++ b/src/components/MyNavbar.jsx
@@ -0,0 +1,200 @@
+import React, { useState } from "react";
+import {
+ Container,
+ Navbar,
+ Nav,
+ Offcanvas,
+ Button,
+ Modal,
+} from "react-bootstrap";
+import { Link } from "react-router-dom";
+import { useDispatch, useSelector } from "react-redux";
+import ChatBot from "../routes/chatBot/chatBot";
+import LogoIcon from "../../public/icon/logo.svg";
+import { postLogout, setUser } from "../store/reducers/User/user";
+import { setSelectedTab } from "../store/reducers/Trading/trading";
+import chatbotImg from "../../public/icon/chat_mate.jpg";
+import styled from "styled-components";
+import { getCookie } from "../lib/apis/cookie";
+
+const EXPAND_BREAKPOINT = "md";
+
+const MyNavbar = ({ offCanvasTitle }) => {
+ const [showChatBot, setShowChatBot] = useState(false); // ChatBot 표시 상태
+ const toggleChatBot = () => setShowChatBot((prev) => !prev);
+ const [logoutModal, setLogoutModal] = useState(false);
+ const User = useSelector((state) => state.user.user);
+
+ const dispatch = useDispatch();
+ const isLogin = !!getCookie("token");
+ const token = getCookie("token");
+ return (
+
+
+
+
+ StockMate
+
+
+
+
+
+ {offCanvasTitle || "invest-SNS"}
+
+
+
+
+
+
+
+
+ );
+};
+
+export default MyNavbar;
+
+const ChatBotBtn = styled.div`
+ display: flex;
+ align-items: center;
+ gap: 3px;
+ color: rgba(0, 0, 0, 0.5);
+ animation: ct 1s infinite;
+
+ &:hover {
+ cursor: pointer;
+ }
+
+ @keyframes ct {
+ 50% {
+ color: #ff8b5c;
+ }
+ }
+`;
diff --git a/src/components/MyPage/FriendList.jsx b/src/components/MyPage/FriendList.jsx
new file mode 100644
index 0000000..bbc37ed
--- /dev/null
+++ b/src/components/MyPage/FriendList.jsx
@@ -0,0 +1,135 @@
+import React, { useEffect, useState } from "react";
+import styled from "styled-components";
+import { useDispatch, useSelector } from "react-redux";
+import {
+ deleteFriend,
+ fetchFriends,
+ fetchPendingFriends,
+} from "../../store/reducers/User/friend";
+import PendingFriendList from "./PendingFriendList";
+import UserDetail from "./UserDetail";
+
+const FriendList = ({ onClose }) => {
+ const dispatch = useDispatch();
+ const [showDetail, setShowDetail] = useState(false);
+ const [selectedFriend, setSelectedFriend] = useState(null);
+
+ useEffect(() => {
+ dispatch(fetchFriends());
+ dispatch(fetchPendingFriends());
+ }, [dispatch]);
+ const friends = useSelector((state) => state.friend.friends);
+ const pendingFriends = useSelector((state) => state.friend.pendingFriends);
+
+ const toggleDetail = () => {
+ setShowDetail(!showDetail);
+ };
+
+ const toggleUser = (item) => {
+ setSelectedFriend(item);
+ };
+
+ const handleDeleteClick = (event, id) => {
+ event.stopPropagation();
+ dispatch(deleteFriend(id));
+ };
+
+ return (
+
+
+
+ 친구 목록
+
+
+
+ {showDetail ? : <>>}
+
+
+ {friends?.map((item) => (
+
+
toggleUser(item)}>
+
+ {item.nickname}
+ handleDeleteClick(event, item._id)}
+ >
+ 친구 삭제
+
+
+
+
+ ))}
+
+
+ );
+};
+
+export default FriendList;
+
+const RowDiv = styled.div`
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+ margin: 15px;
+`;
+
+const FriendContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+`;
+
+const FriendWrapper = styled.div`
+ display: flex;
+ flex-direction: row;
+ border: 1px solid #e4e4e4;
+ background: #fff;
+ padding: 15px 22px;
+ align-items: center;
+ margin: 0px 20px 10px 20px;
+`;
+
+const NicknameDiv = styled.div`
+ margin-left: 20px;
+ font-size: 18px;
+ width: 50%;
+`;
+
+const DeleteDiv = styled.button`
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-left: auto;
+ border-radius: 10px;
+ background: #ffe3d7;
+ width: 25%;
+ height: 37px;
+ font-size: 15px;
+ border: none;
+`;
+
+const DetailContainer = styled.div`
+ width: 400px;
+ height: 100%;
+ background-color: #fff;
+ box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
+ transform: translateX(${(props) => (props.$showdetail ? "0" : "100%")});
+ transition: transform 0.3s ease;
+ position: absolute;
+ top: 0;
+ left: 0;
+ overflow: hidden;
+ z-index: 999;
+`;
diff --git a/src/components/MyPage/FriendSearch.jsx b/src/components/MyPage/FriendSearch.jsx
new file mode 100644
index 0000000..2010b30
--- /dev/null
+++ b/src/components/MyPage/FriendSearch.jsx
@@ -0,0 +1,152 @@
+import React, { useRef, useState } from "react";
+import { styled } from "styled-components";
+import { useDispatch, useSelector } from "react-redux";
+import { postSearchFriend } from "../../store/reducers/User/friend";
+import UserDetail from "./UserDetail";
+
+const FriendSearch = ({ onClose }) => {
+ const searchRef = useRef(null);
+ const [searchInput, setSearchInput] = useState("");
+ const [selectedFriend, setSelectedFriend] = useState(null);
+
+ const dispatch = useDispatch();
+ const friendSearch = useSelector((state) => state.friend.friendSearch);
+
+ const onChangeInput = async (e) => {
+ setSearchInput(e.target.value);
+ dispatch(postSearchFriend({ nickname: e.target.value }));
+ };
+
+ const toggleUser = (item) => {
+ setSelectedFriend(item);
+ };
+
+ return (
+
+
+
{
+ onClose();
+ }}
+ />
+ 친구 검색
+
+
+
+
+ @를 입력하면 모든 사람을 볼 수 있어요!
+
+
+ onChangeInput(e)}
+ >
+ {searchInput && (
+ {
+ setSearchInput("");
+ dispatch(postSearchFriend({ nickname: "" }));
+ }}
+ >
+
+
+ )}
+
+ {friendSearch?.length > 0 && (
+ <>
+
+ {friendSearch?.map((item) => (
+
+ toggleUser(item)}>
+ {item.nickname}
+
+
+
+ ))}
+
+ >
+ )}
+
+
+ );
+};
+
+const RowDiv = styled.div`
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+ margin: 15px;
+`;
+
+const SearchContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+`;
+
+const SearchWrapper = styled.label`
+ position: relative;
+ display: flex;
+ width: 8 0%;
+ padding: 10px;
+ height: 44px;
+ background-color: #f3f3f3;
+ width: 80%;
+`;
+
+const SearchInput = styled.input`
+ width: 85%;
+ padding-left: 0px;
+ font-size: 15px;
+ border: none;
+ background-color: #f3f3f3;
+`;
+
+const SearchClearBtn = styled.button`
+ position: absolute;
+ right: 15px;
+ top: 14px;
+ border: none;
+ background: none;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ cursor: pointer;
+`;
+
+const SearchResults = styled.ul`
+ width: 80%;
+ padding-left: 0;
+ list-style: none;
+ height: calc(100vh - 303px);
+ overflow-y: auto;
+ padding-bottom: 5px;
+ z-index: 98;
+ &::-webkit-scrollbar {
+ display: none;
+ }
+`;
+
+const SearchResult = styled.li`
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ padding: 10px 10px;
+ text-align: left;
+ min-height: 60px;
+ border: 1px solid #f0f0f0;
+ cursor: pointer;
+ &:hover {
+ background-color: #f0f0f0;
+ }
+`;
+
+export default FriendSearch;
diff --git a/src/components/MyPage/PendingFriendList.jsx b/src/components/MyPage/PendingFriendList.jsx
new file mode 100644
index 0000000..43752d4
--- /dev/null
+++ b/src/components/MyPage/PendingFriendList.jsx
@@ -0,0 +1,108 @@
+import React, { useEffect } from "react";
+import styled from "styled-components";
+import { useDispatch, useSelector } from "react-redux";
+import {
+ deleteFriendRequest,
+ fetchPendingFriends,
+ postFriendAccept,
+} from "../../store/reducers/User/friend";
+
+const PendingFriendList = ({ onClose }) => {
+ const dispatch = useDispatch();
+
+ useEffect(() => {
+ dispatch(fetchPendingFriends());
+ }, [dispatch]);
+
+ const pendingFriends = useSelector((state) => state.friend.pendingFriends);
+
+ return (
+ <>
+
+
+ 친구 대기 목록
+
+
+
+ {pendingFriends.map((item) => (
+
+
+ {item.nickname}
+
+ dispatch(postFriendAccept(item._id))}
+ >
+ 수락
+
+ dispatch(deleteFriendRequest(item._id))}
+ >
+ 삭제
+
+
+
+ ))}
+
+ >
+ );
+};
+
+export default PendingFriendList;
+
+const RowDiv = styled.div`
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+ margin: 15px;
+`;
+
+const FriendContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+`;
+
+const FriendWrapper = styled.div`
+ display: flex;
+ flex-direction: row;
+ border: 1px solid #e4e4e4;
+ background: #fff;
+ padding: 15px 22px;
+ align-items: center;
+ margin: 0px 20px 10px 20px;
+ min-height: 75px;
+ height: auto;
+`;
+
+const NicknameDiv = styled.div`
+ margin-left: 20px;
+ font-size: 18px;
+`;
+
+const Div = styled.div`
+ display: flex;
+ flex-direction: row;
+ margin-left: auto;
+ gap: 10px;
+`;
+
+const ButtonDiv = styled.button`
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 10px;
+ background-color: ${(props) => props.$color};
+ width: 40px;
+ height: 40px;
+ font-size: 15px;
+ border: none;
+ padding: 0;
+
+ &:hover {
+ background-color: ${(props) => props.$hover};
+ }
+`;
diff --git a/src/components/MyPage/UserDetail.jsx b/src/components/MyPage/UserDetail.jsx
new file mode 100644
index 0000000..e667f8e
--- /dev/null
+++ b/src/components/MyPage/UserDetail.jsx
@@ -0,0 +1,28 @@
+import React from "react";
+import { styled } from "styled-components";
+import UserPage from "./UserPage";
+
+const UserDetail = ({ item, selectedFriend, func }) => {
+ return (
+
+ {selectedFriend === item && (
+ func(null)} />
+ )}
+
+ );
+};
+
+const Container = styled.div`
+ width: 400px;
+ height: 100%;
+ background-color: #fff;
+ box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
+ transform: translateX(${(props) => (props.$showuser ? "0" : "100%")});
+ transition: transform 0.3s ease;
+ position: absolute;
+ top: 0;
+ left: 0;
+ overflow: hidden;
+ z-index: 999;
+`;
+export default UserDetail;
diff --git a/src/components/MyPage/UserPage.jsx b/src/components/MyPage/UserPage.jsx
new file mode 100644
index 0000000..7f5426d
--- /dev/null
+++ b/src/components/MyPage/UserPage.jsx
@@ -0,0 +1,175 @@
+import React, { useEffect } from "react";
+import { styled } from "styled-components";
+import * as S from "../../style/GlobalStyle";
+import { useDispatch, useSelector } from "react-redux";
+import { fetchMyFeedCount } from "../../store/reducers/Feed/feed";
+import {
+ fetchFriendCount,
+ setFriendNickname,
+ setFriendId,
+ postFriendRequest,
+ fetchFriendsState,
+ deleteMyRequest,
+} from "../../store/reducers/User/friend";
+import Feed from "../Feed/FeedShow/Feed";
+
+const UserPage = ({ item, onClose }) => {
+ const dispatch = useDispatch();
+
+ useEffect(() => {
+ dispatch(setFriendId(item._id));
+ dispatch(setFriendNickname(item.nickname));
+ dispatch(fetchMyFeedCount(item._id));
+ dispatch(fetchFriendCount(item._id));
+ dispatch(fetchFriendsState(item._id));
+ }, [item._id]);
+
+ const friendNickname = useSelector((state) => state.friend.friendNickname);
+ const friendId = useSelector((state) => state.friend.friendId);
+ const feedCount = useSelector((state) => state.feed.myFeedCount);
+ const friendCounts = useSelector((state) => state.friend.friendCount);
+ const userId = useSelector((state) => state.user.user.id);
+ const states = useSelector((state) => state.friend.friendsState);
+
+ const postRequest = () => {
+ if (window.confirm("친구 요청을 보내시겠습니까?")) {
+ dispatch(postFriendRequest(friendId));
+ alert("요청이 완료되었습니다.");
+ }
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+ {friendNickname}
+
+
+
+
+ 게시물
+ {feedCount}
+
+
+ 친구
+ {friendCounts}
+
+
+ {states?.state === "requested" ? (
+ dispatch(deleteMyRequest(friendId))}
+ $color="#cbcbcb"
+ $hover="#959595"
+ >
+ 신청 취소
+
+ ) : states?.state === "pending" ? (
+
+ 알림창 확인
+
+ ) : states?.state === "friend" ? (
+ <>>
+ ) : (
+ friendId !== userId && (
+ 친구 신청
+ )
+ )}
+
+
+
+
+
+
+ );
+};
+
+export default UserPage;
+
+const RowDiv = styled.div`
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+ padding: 15px 15px 0px 15px;
+ height: 75px;
+ background-color: white;
+`;
+
+const UserContainer = styled.div`
+ display: flex;
+ flex-direction: row;
+ padding: 18px 40px;
+ justify-content: space-around;
+ align-items: center;
+ background-color: white;
+ margin-bottom: 5px;
+`;
+
+const ImgDiv = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ word-break: break-all;
+`;
+
+const NicknameDiv = styled.div`
+ font-size: 17px;
+ max-width: 98px;
+`;
+
+const ColumnDiv = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ width: 60px;
+ font-size: 18px;
+`;
+
+const ItemDiv = styled.div``;
+
+const NumDiv = styled.div``;
+
+const ContentContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ overflow: auto;
+ &::-webkit-scrollbar {
+ display: none;
+ }
+`;
+
+const ColumnWrapper = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+`;
+
+const Div = styled.div`
+ display: flex;
+ flex-direction: row;
+ gap: 30px;
+`;
+
+const FriendBtn = styled.button`
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ font-size: 15px;
+ height: 30px;
+ border: none;
+ border-radius: 5px;
+ background-color: ${(props) => props.$color || "#ffe3d7"};
+ width: 130px;
+ margin-top: 15px;
+
+ &:hover {
+ background-color: ${(props) => props.$hover || "#ffaa85"};
+ }
+`;
diff --git a/src/components/invest/chart/ButtonContainer.jsx b/src/components/invest/chart/ButtonContainer.jsx
new file mode 100644
index 0000000..ac79c2f
--- /dev/null
+++ b/src/components/invest/chart/ButtonContainer.jsx
@@ -0,0 +1,83 @@
+import React from 'react'
+import { useDispatch } from 'react-redux';
+import styled from 'styled-components';
+import { setClickDate } from '../../../store/reducers/Chart/chart';
+
+export default function ButtonContainer({ toggleCharts, toggleIndicators, showCharts, showIndicators, getData, clickDate }) {
+ const dispatch = useDispatch();
+
+ return (
+
+
+ 차트지표
+ 보조지표
+
+
+ {/* */}
+ {
+ getData("D");
+ dispatch(setClickDate("D"));
+ }}
+ >
+ 일
+
+ {
+ getData("W");
+ dispatch(setClickDate("W"));
+ }}
+ >
+ 주
+
+ {
+ getData("M");
+ dispatch(setClickDate("M"));
+ }}
+ >
+ 월
+
+ {
+ getData("Y");
+ dispatch(setClickDate("Y"));
+ }}
+ >
+ 년
+
+
+
+ )
+}
+
+const Content = styled.div`
+ display: flex;
+ gap: 10px;
+`;
+
+const BtnContainer = styled.div`
+ display: flex;
+ justify-content: space-between;
+ padding: 15px 20px 0 20px;
+`;
+
+const IndiBtn = styled.button`
+ background-color: ${(props) => props.check === "true" ? "rgba(255, 125, 117, 0.4)" : "#fff"};
+ color: ${(props) => props.check === "true" ? "" : ""};
+ border: ${(props) => props.check === "true" ? "1px solid rgba(255, 125, 117, 0.4)" : "1px solid rgba(0, 0, 0, 0.6)"};
+ border-radius: 999px;
+ padding: 5px 15px;
+ font-size: 14px;
+ font-weight: 600;
+
+ &:hover {
+ background: rgba(255, 125, 117, 0.4);
+ border: 1px solid rgba(255, 125, 117, 0.4);
+ }
+`;
+
diff --git a/src/components/invest/chart/Indicators/chart/BBANDSChart.jsx b/src/components/invest/chart/Indicators/chart/BBANDSChart.jsx
new file mode 100644
index 0000000..077c0b9
--- /dev/null
+++ b/src/components/invest/chart/Indicators/chart/BBANDSChart.jsx
@@ -0,0 +1,71 @@
+import React, { useEffect, useState } from 'react';
+import { BollingerBandTooltip, BollingerSeries } from 'react-financial-charts';
+import { useDispatch, useSelector } from 'react-redux';
+import { setChartDatas } from '../../../../../store/reducers/Chart/chart';
+import { getBBANDSChart } from '../../../../../store/reducers/Chart/Indicators/chart';
+
+export default function BBANDSChart({ datas, isShow }) {
+ const dispatch = useDispatch();
+ const isActive = useSelector((state) => state.clickIndicator.BBANDS);
+ const BBANDSValue = useSelector((state) => state.chartValues.values.BBANDS);
+
+ const calculateBBANDS = (data) => {
+ dispatch(setChartDatas({
+ newData: datas,
+ data: data,
+ name: 'BBANDS'
+ }));
+ }
+
+ useEffect(() => {
+ if (isActive) {
+ const data = {
+ "chart": datas,
+ "lineTime" : BBANDSValue[0],
+ "stdev" : BBANDSValue[1],
+ }
+ dispatch(getBBANDSChart(data))
+ .then((res) => calculateBBANDS(res.payload))
+
+ } else if (!isActive) {
+ const updatedDatas = datas.map(item => {
+ const newItem = { ...item };
+ delete newItem.upper;
+ delete newItem.middle;
+ delete newItem.lower;
+ return newItem;
+ });
+ dispatch(setChartDatas(updatedDatas));
+ }
+ }, [isActive, BBANDSValue, isShow]);
+
+ return (
+ <>
+ (
+ {
+ top: d.upper,
+ middle: d.middle,
+ bottom: d.lower
+ }
+ )}
+ />
+ (
+ {
+ top: d.upper,
+ middle: d.middle,
+ bottom: d.lower
+ }
+ )}
+ />
+ >
+ )
+}
diff --git a/src/components/invest/chart/Indicators/chart/EMAChart.jsx b/src/components/invest/chart/Indicators/chart/EMAChart.jsx
new file mode 100644
index 0000000..c9a299a
--- /dev/null
+++ b/src/components/invest/chart/Indicators/chart/EMAChart.jsx
@@ -0,0 +1,91 @@
+import React, { useEffect } from 'react';
+import { LineSeries, MovingAverageTooltip, SingleTooltip } from 'react-financial-charts';
+import { useDispatch, useSelector } from 'react-redux';
+import { setChartDatas } from '../../../../../store/reducers/Chart/chart';
+import { getEMAChart } from '../../../../../store/reducers/Chart/Indicators/chart';
+
+export default function EMAChart({ datas, isShow, chartIndi }) {
+ const dispatch = useDispatch();
+ const isActive = useSelector((state) => state.clickIndicator.EMA);
+ const EMAValue = useSelector((state) => state.chartValues.values.EMA);
+
+ const calculateEMA = (data) => {
+ dispatch(setChartDatas({
+ newData: datas,
+ data: data,
+ name: 'EMA',
+ value: EMAValue,
+ }));
+ }
+
+ useEffect(() => {
+ if (isActive) {
+ const data = {
+ "chart": datas,
+ "lineTime1" : EMAValue[0],
+ "lineTime2" : EMAValue[1],
+ "lineTime3" : EMAValue[2],
+ "lineTime4" : EMAValue[3],
+ "lineTime5" : EMAValue[4]
+ }
+ dispatch(getEMAChart(data))
+ .then((res) => calculateEMA(res.payload))
+ } else if (!isActive) {
+ const updatedDatas = datas.map(item => {
+ const newItem = { ...item };
+ EMAValue.forEach((value) => {
+ delete newItem[`ema${value}`];
+ });
+ return newItem;
+ });
+ dispatch(setChartDatas(updatedDatas));
+ }
+ }, [isActive, EMAValue, isShow]);
+
+ return (
+ <>
+ d[`ema${EMAValue[0]}`],
+ type: `EMA`,
+ stroke: '#b3009e',
+ windowSize: EMAValue[0]
+ },
+ {
+ yAccessor: d => d[`ema${EMAValue[1]}`],
+ type: `EMA`,
+ stroke: '#b33300',
+ windowSize: EMAValue[1]
+ },
+ {
+ yAccessor: d => d[`ema${EMAValue[2]}`],
+ type: `EMA`,
+ stroke: '#edda02',
+ windowSize: EMAValue[2]
+ },
+ {
+ yAccessor: d => d[`ema${EMAValue[3]}`],
+ type: `EMA`,
+ stroke: '#00b33f',
+ windowSize: EMAValue[3]
+ },
+ {
+ yAccessor: d => d[`ema${EMAValue[4]}`],
+ type: `EMA`,
+ stroke: '#0277ed',
+ windowSize: EMAValue[4]
+ },
+ ]}
+ />
+ {EMAValue.map((value, index) => (
+ d[`ema${value}`]}
+ strokeStyle={['#b3009e', '#b33300', '#edda02', '#00b33f', '#0277ed'][index]}
+ />
+ ))}
+ >
+ )
+}
diff --git a/src/components/invest/chart/Indicators/chart/SARChart.jsx b/src/components/invest/chart/Indicators/chart/SARChart.jsx
new file mode 100644
index 0000000..aff4947
--- /dev/null
+++ b/src/components/invest/chart/Indicators/chart/SARChart.jsx
@@ -0,0 +1,48 @@
+import React, { useEffect } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
+import { setChartDatas } from '../../../../../store/reducers/Chart/chart';
+import { getSARChart } from '../../../../../store/reducers/Chart/Indicators/chart';
+import { SARSeries, SingleTooltip, SingleValueTooltip } from 'react-financial-charts';
+
+export default function SARChart({ datas, isShow, chartIndi }) {
+ const dispatch = useDispatch();
+ const isActive = useSelector((state) => state.clickIndicator.SAR);
+ const SARValue = useSelector((state) => state.chartValues.values.SAR);
+
+ const calculateSAR = (data) => {
+ dispatch(setChartDatas({
+ newData: datas,
+ data: data,
+ name: 'SAR'
+ }));
+ }
+
+ useEffect(() => {
+ if (isActive) {
+ const data = {
+ "chart": datas,
+ "acc" : SARValue[0],
+ "accMax" : SARValue[1],
+ }
+ dispatch(getSARChart(data))
+ .then((res) => calculateSAR(res.payload))
+ } else if (!isActive) {
+ const updatedDatas = datas.map(item => {
+ const newItem = { ...item };
+ delete newItem.sar;
+ return newItem;
+ });
+ dispatch(setChartDatas(updatedDatas));
+ }
+ }, [isActive, SARValue, isShow]);
+
+ return (
+ <>
+ d.sar}
+ origin={[12, 40 + (chartIndi.indexOf('SAR') * 50)]}/>
+ d.sar} />
+ >
+ )
+}
diff --git a/src/components/invest/chart/Indicators/chart/SMAChart.jsx b/src/components/invest/chart/Indicators/chart/SMAChart.jsx
new file mode 100644
index 0000000..3f91972
--- /dev/null
+++ b/src/components/invest/chart/Indicators/chart/SMAChart.jsx
@@ -0,0 +1,92 @@
+import React, { useEffect } from 'react';
+import { Label, LineSeries, MovingAverageTooltip, SingleTooltip, SingleValueTooltip, ToolTipTSpanLabel, ToolTipText } from 'react-financial-charts';
+import { useDispatch, useSelector } from 'react-redux';
+import { setChartDatas } from '../../../../../store/reducers/Chart/chart';
+import { getSMAChart } from '../../../../../store/reducers/Chart/Indicators/chart';
+import styled from 'styled-components';
+
+export default function SMAChart({ datas, isShow, chartIndi }) {
+ const dispatch = useDispatch();
+ const isActive = useSelector((state) => state.clickIndicator.SMA);
+ const SMAValue = useSelector((state) => state.chartValues.values.SMA);
+
+ const calculateSMA = (data) => {
+ dispatch(setChartDatas({
+ newData: datas,
+ data: data,
+ name: 'SMA',
+ value: SMAValue,
+ }));
+ }
+
+ useEffect(() => {
+ if (isActive) {
+ const data = {
+ "chart": datas,
+ "lineTime1" : SMAValue[0],
+ "lineTime2" : SMAValue[1],
+ "lineTime3" : SMAValue[2],
+ "lineTime4" : SMAValue[3],
+ "lineTime5" : SMAValue[4]
+ }
+ dispatch(getSMAChart(data))
+ .then((res) => calculateSMA(res.payload))
+ } else if (!isActive) {
+ const updatedDatas = datas.map((item) => {
+ const newItem = { ...item };
+ SMAValue.forEach((value) => {
+ delete newItem[`sma${value}`];
+ });
+ return newItem;
+ });
+ dispatch(setChartDatas(updatedDatas));
+ }
+ }, [isActive, SMAValue, isShow]);
+
+ return (
+ <>
+ d[`sma${SMAValue[0]}`],
+ type: `SMA`,
+ stroke: '#b3009e',
+ windowSize: SMAValue[0]
+ },
+ {
+ yAccessor: d => d[`sma${SMAValue[1]}`],
+ type: `SMA`,
+ stroke: '#b33300',
+ windowSize: SMAValue[1]
+ },
+ {
+ yAccessor: d => d[`sma${SMAValue[2]}`],
+ type: `SMA`,
+ stroke: '#edda02',
+ windowSize: SMAValue[2]
+ },
+ {
+ yAccessor: d => d[`sma${SMAValue[3]}`],
+ type: `SMA`,
+ stroke: '#00b33f',
+ windowSize: SMAValue[3]
+ },
+ {
+ yAccessor: d => d[`sma${SMAValue[4]}`],
+ type: `SMA`,
+ stroke: '#0277ed',
+ windowSize: SMAValue[4]
+ },
+ ]}
+ />
+ {SMAValue.map((value, index) => (
+ d[`sma${value}`]}
+ strokeStyle={['#b3009e', '#b33300', '#edda02', '#00b33f', '#0277ed'][index]}
+ />
+ ))}
+ >
+ )
+}
diff --git a/src/components/invest/chart/Indicators/chart/WMAChart.jsx b/src/components/invest/chart/Indicators/chart/WMAChart.jsx
new file mode 100644
index 0000000..34a5fe4
--- /dev/null
+++ b/src/components/invest/chart/Indicators/chart/WMAChart.jsx
@@ -0,0 +1,91 @@
+import React, { useEffect } from 'react';
+import { LineSeries, MovingAverageTooltip, SingleTooltip } from 'react-financial-charts';
+import { useDispatch, useSelector } from 'react-redux';
+import { setChartDatas } from '../../../../../store/reducers/Chart/chart';
+import { getWMAChart } from '../../../../../store/reducers/Chart/Indicators/chart';
+
+export default function WMAChart({ datas, isShow, chartIndi }) {
+ const dispatch = useDispatch();
+ const isActive = useSelector((state) => state.clickIndicator.WMA);
+ const WMAValue = useSelector((state) => state.chartValues.values.WMA);
+
+ const calculateWMA = (data) => {
+ dispatch(setChartDatas({
+ newData: datas,
+ data: data,
+ name: 'WMA',
+ value: WMAValue,
+ }));
+ }
+
+ useEffect(() => {
+ if (isActive) {
+ const data = {
+ "chart": datas,
+ "lineTime1" : WMAValue[0],
+ "lineTime2" : WMAValue[1],
+ "lineTime3" : WMAValue[2],
+ "lineTime4" : WMAValue[3],
+ "lineTime5" : WMAValue[4]
+ }
+ dispatch(getWMAChart(data))
+ .then((res) => calculateWMA(res.payload))
+ } else if (!isActive) {
+ const updatedDatas = datas.map(item => {
+ const newItem = { ...item };
+ WMAValue.forEach((value) => {
+ delete newItem[`wma${value}`];
+ });
+ return newItem;
+ });
+ dispatch(setChartDatas(updatedDatas));
+ }
+ }, [isActive, WMAValue, isShow]);
+
+ return (
+ <>
+ d[`wma${WMAValue[0]}`],
+ type: `WMA`,
+ stroke: '#b3009e',
+ windowSize: WMAValue[0]
+ },
+ {
+ yAccessor: d => d[`wma${WMAValue[1]}`],
+ type: `WMA`,
+ stroke: '#b33300',
+ windowSize: WMAValue[1]
+ },
+ {
+ yAccessor: d => d[`wma${WMAValue[2]}`],
+ type: `WMA`,
+ stroke: '#edda02',
+ windowSize: WMAValue[2]
+ },
+ {
+ yAccessor: d => d[`wma${WMAValue[3]}`],
+ type: `WMA`,
+ stroke: '#00b33f',
+ windowSize: WMAValue[3]
+ },
+ {
+ yAccessor: d => d[`wma${WMAValue[4]}`],
+ type: `WMA`,
+ stroke: '#0277ed',
+ windowSize: WMAValue[4]
+ },
+ ]}
+ />
+ {WMAValue.map((value, index) => (
+ d[`wma${value}`]}
+ strokeStyle={['#b3009e', '#b33300', '#edda02', '#00b33f', '#0277ed'][index]}
+ />
+ ))}
+ >
+ )
+}
diff --git a/src/components/invest/chart/Indicators/sub/ADChart.jsx b/src/components/invest/chart/Indicators/sub/ADChart.jsx
new file mode 100644
index 0000000..4e1ad01
--- /dev/null
+++ b/src/components/invest/chart/Indicators/sub/ADChart.jsx
@@ -0,0 +1,54 @@
+import React, { useEffect } from 'react';
+import { LineSeries, SingleValueTooltip, StraightLine, XAxis, YAxis } from 'react-financial-charts';
+import { useDispatch, useSelector } from 'react-redux';
+import { setChartDatas } from '../../../../../store/reducers/Chart/chart';
+import { getADChart } from '../../../../../store/reducers/Chart/Indicators/sub';
+import { format } from 'd3-format';
+
+export default function ADChart({ datas, isShow }) {
+ const dispatch = useDispatch();
+ const isActive = useSelector((state) => state.clickIndicator.AD);
+
+ const calculateAD = (data) => {
+ dispatch(setChartDatas({
+ newData: datas,
+ data: data,
+ name: 'AD'
+ }));
+ }
+
+ useEffect(() => {
+ if (isActive) {
+ const data = {
+ "chart": datas,
+ }
+ dispatch(getADChart(data))
+ .then((res) => calculateAD(res.payload))
+ } else if (!isActive) {
+ const updatedDatas = datas.map(item => {
+ const newItem = { ...item };
+ delete newItem.ad;
+ return newItem;
+ });
+ dispatch(setChartDatas(updatedDatas));
+ }
+ }, [isActive, isShow]);
+
+ const pricesDisplayFormat = format(",");
+
+ return (
+ <>
+
+
+
+ d.ad} strokeStyle='#09b01f' strokeWidth={1.3} />
+ d.ad}
+ yLabel="AD Line"
+ yDisplayFormat={format(",")}
+ labelFill='#09b01f'
+ />
+ >
+ )
+}
diff --git a/src/components/invest/chart/Indicators/sub/ADOSCChart.jsx b/src/components/invest/chart/Indicators/sub/ADOSCChart.jsx
new file mode 100644
index 0000000..706491b
--- /dev/null
+++ b/src/components/invest/chart/Indicators/sub/ADOSCChart.jsx
@@ -0,0 +1,57 @@
+import React, { useEffect } from 'react';
+import { LineSeries, SingleValueTooltip, StraightLine, XAxis, YAxis } from 'react-financial-charts';
+import { useDispatch, useSelector } from 'react-redux';
+import { setChartDatas } from '../../../../../store/reducers/Chart/chart';
+import { getADOSCChart } from '../../../../../store/reducers/Chart/Indicators/sub';
+import { format } from 'd3-format';
+
+export default function ADOSCChart({ datas, isShow }) {
+ const dispatch = useDispatch();
+ const isActive = useSelector((state) => state.clickIndicator.ADOSC);
+ const ADOSCValue = useSelector((state) => state.indicatorValues.values.ADOSC);
+
+ const calculateADOSC = (data) => {
+ dispatch(setChartDatas({
+ newData: datas,
+ data: data,
+ name: 'ADOSC'
+ }));
+ }
+
+ useEffect(() => {
+ if (isActive) {
+ const data = {
+ "chart": datas,
+ "longPeriod": ADOSCValue[0],
+ "shortPeriod": ADOSCValue[1],
+ }
+ dispatch(getADOSCChart(data))
+ .then((res) => calculateADOSC(res.payload))
+ } else if (!isActive) {
+ const updatedDatas = datas.map(item => {
+ const newItem = { ...item };
+ delete newItem.adosc;
+ return newItem;
+ });
+ dispatch(setChartDatas(updatedDatas));
+ }
+ }, [isActive, ADOSCValue, isShow]);
+
+ const pricesDisplayFormat = format(",");
+
+ return (
+ <>
+
+
+
+ d.adosc} strokeStyle='#680A08' strokeWidth={1.3} />
+ d.adosc}
+ yLabel="Chaikin Oscillator"
+ yDisplayFormat={format(",")}
+ labelFill='#680A08'
+ />
+ >
+ )
+}
diff --git a/src/components/invest/chart/Indicators/sub/ADXChart.jsx b/src/components/invest/chart/Indicators/sub/ADXChart.jsx
new file mode 100644
index 0000000..23c0c90
--- /dev/null
+++ b/src/components/invest/chart/Indicators/sub/ADXChart.jsx
@@ -0,0 +1,56 @@
+import React, { useEffect } from 'react';
+import { LineSeries, SingleValueTooltip, StraightLine, XAxis, YAxis } from 'react-financial-charts';
+import { useDispatch, useSelector } from 'react-redux';
+import { setChartDatas } from '../../../../../store/reducers/Chart/chart';
+import { getADXChart } from '../../../../../store/reducers/Chart/Indicators/sub';
+import { format } from 'd3-format';
+
+export default function ADXChart({ datas, isShow }) {
+ const dispatch = useDispatch();
+ const isActive = useSelector((state) => state.clickIndicator.ADX);
+ const ADXValue = useSelector((state) => state.indicatorValues.values.ADX);
+
+ const calculateADX = (data) => {
+ dispatch(setChartDatas({
+ newData: datas,
+ data: data,
+ name: 'ADX'
+ }));
+ }
+
+ useEffect(() => {
+ if (isActive) {
+ const data = {
+ "chart": datas,
+ "Date": ADXValue[0],
+ }
+ dispatch(getADXChart(data))
+ .then((res) => calculateADX(res.payload))
+ } else if (!isActive) {
+ const updatedDatas = datas.map(item => {
+ const newItem = { ...item };
+ delete newItem.adx;
+ return newItem;
+ });
+ dispatch(setChartDatas(updatedDatas));
+ }
+ }, [isActive, ADXValue, isShow]);
+
+ const pricesDisplayFormat = format(",");
+
+ return (
+ <>
+
+
+
+ d.adx} strokeStyle='#09b06d' strokeWidth={1.3} />
+ d.adx}
+ yLabel="ADX"
+ yDisplayFormat={format(",")}
+ labelFill='#09b06d'
+ />
+ >
+ )
+}
diff --git a/src/components/invest/chart/Indicators/sub/ADXRChart.jsx b/src/components/invest/chart/Indicators/sub/ADXRChart.jsx
new file mode 100644
index 0000000..bef47c5
--- /dev/null
+++ b/src/components/invest/chart/Indicators/sub/ADXRChart.jsx
@@ -0,0 +1,56 @@
+import React, { useEffect } from 'react';
+import { LineSeries, SingleValueTooltip, StraightLine, XAxis, YAxis } from 'react-financial-charts';
+import { useDispatch, useSelector } from 'react-redux';
+import { setChartDatas } from '../../../../../store/reducers/Chart/chart';
+import { getADXRChart } from '../../../../../store/reducers/Chart/Indicators/sub';
+import { format } from 'd3-format';
+
+export default function ADXRChart({ datas, isShow }) {
+ const dispatch = useDispatch();
+ const isActive = useSelector((state) => state.clickIndicator.ADXR);
+ const ADXRValue = useSelector((state) => state.indicatorValues.values.ADXR);
+
+ const calculateADXR = (data) => {
+ dispatch(setChartDatas({
+ newData: datas,
+ data: data,
+ name: 'ADXR'
+ }));
+ }
+
+ useEffect(() => {
+ if (isActive) {
+ const data = {
+ "chart": datas,
+ "Date": ADXRValue[0],
+ }
+ dispatch(getADXRChart(data))
+ .then((res) => calculateADXR(res.payload))
+ } else if (!isActive) {
+ const updatedDatas = datas.map(item => {
+ const newItem = { ...item };
+ delete newItem.adxr;
+ return newItem;
+ });
+ dispatch(setChartDatas(updatedDatas));
+ }
+ }, [isActive, ADXRValue, isShow]);
+
+ const pricesDisplayFormat = format(",");
+
+ return (
+ <>
+
+
+
+ d.adxr} strokeStyle='#0989b0' strokeWidth={1.3} />
+ d.adxr}
+ yLabel="ADXR"
+ yDisplayFormat={format(",")}
+ labelFill='#0989b0'
+ />
+ >
+ )
+}
diff --git a/src/components/invest/chart/Indicators/sub/AROONChart.jsx b/src/components/invest/chart/Indicators/sub/AROONChart.jsx
new file mode 100644
index 0000000..9aaf8bb
--- /dev/null
+++ b/src/components/invest/chart/Indicators/sub/AROONChart.jsx
@@ -0,0 +1,65 @@
+import React, { useEffect } from 'react';
+import { LineSeries, SingleValueTooltip, StraightLine, XAxis, YAxis } from 'react-financial-charts';
+import { useDispatch, useSelector } from 'react-redux';
+import { setChartDatas } from '../../../../../store/reducers/Chart/chart';
+import { getAROONChart } from '../../../../../store/reducers/Chart/Indicators/sub';
+import { format } from 'd3-format';
+
+export default function AROONChart({ datas, isShow }) {
+ const dispatch = useDispatch();
+ const isActive = useSelector((state) => state.clickIndicator.AROON);
+ const AROONValue = useSelector((state) => state.indicatorValues.values.AROON);
+
+ const calculateAROON = (data) => {
+ dispatch(setChartDatas({
+ newData: datas,
+ data: data,
+ name: 'AROON'
+ }));
+ }
+
+ useEffect(() => {
+ if (isActive) {
+ const data = {
+ "chart": datas,
+ "Date": AROONValue[0],
+ }
+ dispatch(getAROONChart(data))
+ .then((res) => calculateAROON(res.payload))
+ } else if (!isActive) {
+ const updatedDatas = datas.map(item => {
+ const newItem = { ...item };
+ delete newItem.aroonDown;
+ delete newItem.aroonUp;
+ return newItem;
+ });
+ dispatch(setChartDatas(updatedDatas));
+ }
+ }, [isActive, AROONValue, isShow]);
+
+ const pricesDisplayFormat = format(",");
+
+ return (
+ <>
+
+
+
+ d.aroonUp} strokeStyle='#EDD02B' strokeWidth={1.3} />
+ d.aroonDown} strokeStyle='#680A08' strokeWidth={1.3} />
+ d.aroonUp}
+ yLabel="Aroon Up"
+ yDisplayFormat={format(",")}
+ labelFill='#EDD02B'
+ />
+ d.aroonDown}
+ yLabel="Aroon Down"
+ yDisplayFormat={format(",")}
+ labelFill='#680A08'
+ />
+ >
+ )
+}
diff --git a/src/components/invest/chart/Indicators/sub/AROONOSCChart.jsx b/src/components/invest/chart/Indicators/sub/AROONOSCChart.jsx
new file mode 100644
index 0000000..c1e3ada
--- /dev/null
+++ b/src/components/invest/chart/Indicators/sub/AROONOSCChart.jsx
@@ -0,0 +1,64 @@
+import React, { useEffect } from 'react';
+import { BarSeries, SingleValueTooltip, StraightLine, XAxis, YAxis } from 'react-financial-charts';
+import { useDispatch, useSelector } from 'react-redux';
+import { setChartDatas } from '../../../../../store/reducers/Chart/chart';
+import { getAROONOSCChart } from '../../../../../store/reducers/Chart/Indicators/sub';
+import { format } from 'd3-format';
+
+export default function AROONOSCChart({ datas, isShow }) {
+ const dispatch = useDispatch();
+ const isActive = useSelector((state) => state.clickIndicator.AROONOSC);
+ const AROONOSCValue = useSelector((state) => state.indicatorValues.values.AROONOSC);
+
+ const calculateAROONOSC = (data) => {
+ dispatch(setChartDatas({
+ newData: datas,
+ data: data,
+ name: 'AROONOSC'
+ }));
+ }
+
+ useEffect(() => {
+ if (isActive) {
+ const data = {
+ "chart": datas,
+ "Date": AROONOSCValue[0],
+ }
+ dispatch(getAROONOSCChart(data))
+ .then((res) => calculateAROONOSC(res.payload))
+ } else if (!isActive) {
+ const updatedDatas = datas.map(item => {
+ const newItem = { ...item };
+ delete newItem.aroonosc;
+ return newItem;
+ });
+ dispatch(setChartDatas(updatedDatas));
+ }
+ }, [isActive, AROONOSCValue, isShow]);
+
+ const pricesDisplayFormat = format(",");
+ const volumeColor = (data) => {
+ return data.aroonosc > 0
+ ? "#EDD02B"
+ : "#2679ED";
+ };
+
+ return (
+ <>
+
+
+
+ d.aroonosc}
+ baseAt={(xScale, yScale, d) => yScale(0)}
+ />
+ d.aroonosc}
+ yLabel="Aroon Oscillator"
+ yDisplayFormat={format(",")}
+ />
+ >
+ )
+}
diff --git a/src/components/invest/chart/Indicators/sub/ATRChart.jsx b/src/components/invest/chart/Indicators/sub/ATRChart.jsx
new file mode 100644
index 0000000..8a7fd91
--- /dev/null
+++ b/src/components/invest/chart/Indicators/sub/ATRChart.jsx
@@ -0,0 +1,56 @@
+import React, { useEffect } from 'react';
+import { LineSeries, SingleValueTooltip, StraightLine, XAxis, YAxis } from 'react-financial-charts';
+import { useDispatch, useSelector } from 'react-redux';
+import { setChartDatas } from '../../../../../store/reducers/Chart/chart';
+import { getATRChart } from '../../../../../store/reducers/Chart/Indicators/sub';
+import { format } from 'd3-format';
+
+export default function ATRChart({ datas, isShow }) {
+ const dispatch = useDispatch();
+ const isActive = useSelector((state) => state.clickIndicator.ATR);
+ const ATRValue = useSelector((state) => state.indicatorValues.values.ATR);
+
+ const calculateATR = (data) => {
+ dispatch(setChartDatas({
+ newData: datas,
+ data: data,
+ name: 'ATR'
+ }));
+ }
+
+ useEffect(() => {
+ if (isActive) {
+ const data = {
+ "chart": datas,
+ "Date": ATRValue[0],
+ }
+ dispatch(getATRChart(data))
+ .then((res) => calculateATR(res.payload))
+ } else if (!isActive) {
+ const updatedDatas = datas.map(item => {
+ const newItem = { ...item };
+ delete newItem.atr;
+ return newItem;
+ });
+ dispatch(setChartDatas(updatedDatas));
+ }
+ }, [isActive, ATRValue, isShow]);
+
+ const pricesDisplayFormat = format(",");
+
+ return (
+ <>
+
+
+
+ d.atr} strokeStyle='#0991b0' strokeWidth={1.3} />
+ d.atr}
+ yLabel="ATR"
+ yDisplayFormat={format(",")}
+ labelFill='#0991b0'
+ />
+ >
+ )
+}
diff --git a/src/components/invest/chart/Indicators/sub/CCIChart.jsx b/src/components/invest/chart/Indicators/sub/CCIChart.jsx
new file mode 100644
index 0000000..e508b1d
--- /dev/null
+++ b/src/components/invest/chart/Indicators/sub/CCIChart.jsx
@@ -0,0 +1,56 @@
+import React, { useEffect } from 'react';
+import { LineSeries, SingleValueTooltip, StraightLine, XAxis, YAxis } from 'react-financial-charts';
+import { useDispatch, useSelector } from 'react-redux';
+import { setChartDatas } from '../../../../../store/reducers/Chart/chart';
+import { getCCIChart } from '../../../../../store/reducers/Chart/Indicators/sub';
+import { format } from 'd3-format';
+
+export default function CCIChart({ datas, isShow }) {
+ const dispatch = useDispatch();
+ const isActive = useSelector((state) => state.clickIndicator.CCI);
+ const CCIValue = useSelector((state) => state.indicatorValues.values.CCI);
+
+ const calculateCCI = (data) => {
+ dispatch(setChartDatas({
+ newData: datas,
+ data: data,
+ name: 'CCI'
+ }));
+ }
+
+ useEffect(() => {
+ if (isActive) {
+ const data = {
+ "chart": datas,
+ "Date" : CCIValue[0],
+ }
+ dispatch(getCCIChart(data))
+ .then((res) => calculateCCI(res.payload))
+ } else if (!isActive) {
+ const updatedDatas = datas.map(item => {
+ const newItem = { ...item };
+ delete newItem.cci;
+ return newItem;
+ });
+ dispatch(setChartDatas(updatedDatas));
+ }
+ }, [isActive, CCIValue, isShow]);
+
+ const pricesDisplayFormat = format(",");
+
+ return (
+ <>
+
+
+
+ d.cci} strokeStyle='#6a1cad' strokeWidth={1.3} />
+ d.cci}
+ yLabel="CCI"
+ yDisplayFormat={format(",")}
+ labelFill='#6a1cad'
+ />
+ >
+ )
+}
diff --git a/src/components/invest/chart/Indicators/sub/DMIChart.jsx b/src/components/invest/chart/Indicators/sub/DMIChart.jsx
new file mode 100644
index 0000000..c4b4824
--- /dev/null
+++ b/src/components/invest/chart/Indicators/sub/DMIChart.jsx
@@ -0,0 +1,56 @@
+import React, { useEffect } from 'react';
+import { LineSeries, SingleValueTooltip, StraightLine, XAxis, YAxis } from 'react-financial-charts';
+import { useDispatch, useSelector } from 'react-redux';
+import { setChartDatas } from '../../../../../store/reducers/Chart/chart';
+import { getDXChart } from '../../../../../store/reducers/Chart/Indicators/sub';
+import { format } from 'd3-format';
+
+export default function DMIChart({ datas, isShow }) {
+ const dispatch = useDispatch();
+ const isActive = useSelector((state) => state.clickIndicator.DX);
+ const DXValue = useSelector((state) => state.indicatorValues.values.DX);
+
+ const calculateDX = (data) => {
+ dispatch(setChartDatas({
+ newData: datas,
+ data: data,
+ name: 'DMI'
+ }));
+ }
+
+ useEffect(() => {
+ if (isActive) {
+ const data = {
+ "chart": datas,
+ "Date": DXValue[0],
+ }
+ dispatch(getDXChart(data))
+ .then((res) => calculateDX(res.payload))
+ } else if (!isActive) {
+ const updatedDatas = datas.map(item => {
+ const newItem = { ...item };
+ delete newItem.dx;
+ return newItem;
+ });
+ dispatch(setChartDatas(updatedDatas));
+ }
+ }, [isActive, DXValue, isShow]);
+
+ const pricesDisplayFormat = format(",");
+
+ return (
+ <>
+
+
+
+ d.dx} strokeStyle='#8109b0' strokeWidth={1.3} />
+ d.dx}
+ yLabel="DMI"
+ yDisplayFormat={format(",")}
+ labelFill='#8109b0'
+ />
+ >
+ )
+}
diff --git a/src/components/invest/chart/Indicators/sub/MACDChart.jsx b/src/components/invest/chart/Indicators/sub/MACDChart.jsx
new file mode 100644
index 0000000..c9819d0
--- /dev/null
+++ b/src/components/invest/chart/Indicators/sub/MACDChart.jsx
@@ -0,0 +1,84 @@
+import React, { useEffect } from 'react';
+import { BarSeries, Chart, LineSeries, MACDSeries, MACDTooltip, StraightLine, XAxis, YAxis } from 'react-financial-charts';
+import { useDispatch, useSelector } from 'react-redux';
+import { setChartDatas } from '../../../../../store/reducers/Chart/chart';
+import { getMACDChart } from '../../../../../store/reducers/Chart/Indicators/sub';
+import { format } from 'd3-format';
+
+export default function MACDChart({ datas, isShow }) {
+ const dispatch = useDispatch();
+ const isActive = useSelector((state) => state.clickIndicator.MACD);
+ const MACDValue = useSelector((state) => state.indicatorValues.values.MACD);
+
+ const calculateMACD = (data) => {
+ dispatch(setChartDatas({
+ newData: datas,
+ data: data,
+ name: 'MACD'
+ }));
+ }
+
+ useEffect(() => {
+ if (isActive) {
+ const data = {
+ "chart": datas,
+ "longPeriod" : MACDValue[0],
+ "shortPeriod" : MACDValue[1],
+ "signalPeriod" : MACDValue[2]
+ }
+ dispatch(getMACDChart(data))
+ .then((res) => calculateMACD(res.payload))
+ } else if (!isActive) {
+ const updatedDatas = datas.map(item => {
+ const newItem = { ...item };
+ delete newItem.macd;
+ delete newItem.macdSignal;
+ delete newItem.macdHist;
+ return newItem;
+ });
+ dispatch(setChartDatas(updatedDatas));
+ }
+ }, [isActive, MACDValue, isShow]);
+
+ const pricesDisplayFormat = format(",");
+
+ return (
+ <>
+
+
+
+ ({
+ divergence: d.macdHist,
+ macd: d.macd,
+ signal: d.macdSignal
+ })}
+ strokeStyle={{
+ macd: '#680A08',
+ signal: '#A8693D'
+ }}
+ fillStyle={{divergence: d => d.macdHist > 0 ? '#F5E872' : '#A3E79A'}}
+ />
+ ({
+ divergence: d.macdHist,
+ macd: d.macd,
+ signal: d.macdSignal
+ })}
+ appearance={{
+ strokeStyle: {
+ macd: '#680A08',
+ signal: '#A8693D'
+ },
+ fillStyle: {divergence: d => d.macdHist > 0 ? '#F5E872' : '#A3E79A'}
+ }}
+ />
+ >
+ )
+}
diff --git a/src/components/invest/chart/Indicators/sub/MFIChart.jsx b/src/components/invest/chart/Indicators/sub/MFIChart.jsx
new file mode 100644
index 0000000..e1755c2
--- /dev/null
+++ b/src/components/invest/chart/Indicators/sub/MFIChart.jsx
@@ -0,0 +1,56 @@
+import React, { useEffect } from 'react';
+import { LineSeries, SingleValueTooltip, StraightLine, XAxis, YAxis } from 'react-financial-charts';
+import { useDispatch, useSelector } from 'react-redux';
+import { setChartDatas } from '../../../../../store/reducers/Chart/chart';
+import { getMFIChart } from '../../../../../store/reducers/Chart/Indicators/sub';
+import { format } from 'd3-format';
+
+export default function MFIChart({ datas, isShow }) {
+ const dispatch = useDispatch();
+ const isActive = useSelector((state) => state.clickIndicator.MFI);
+ const MFIValue = useSelector((state) => state.indicatorValues.values.MFI);
+
+ const calculateMFI = (data) => {
+ dispatch(setChartDatas({
+ newData: datas,
+ data: data,
+ name: 'MFI'
+ }));
+ }
+
+ useEffect(() => {
+ if (isActive) {
+ const data = {
+ "chart": datas,
+ "Date": MFIValue[0],
+ }
+ dispatch(getMFIChart(data))
+ .then((res) => calculateMFI(res.payload))
+ } else if (!isActive) {
+ const updatedDatas = datas.map(item => {
+ const newItem = { ...item };
+ delete newItem.mfi;
+ return newItem;
+ });
+ dispatch(setChartDatas(updatedDatas));
+ }
+ }, [isActive, MFIValue, isShow]);
+
+ const pricesDisplayFormat = format(",");
+
+ return (
+ <>
+
+
+
+ d.mfi} strokeStyle='#b05109' strokeWidth={1.3} />
+ d.mfi}
+ yLabel="MFI"
+ yDisplayFormat={format(",")}
+ labelFill='#b05109'
+ />
+ >
+ )
+}
diff --git a/src/components/invest/chart/Indicators/sub/MOMChart.jsx b/src/components/invest/chart/Indicators/sub/MOMChart.jsx
new file mode 100644
index 0000000..ff0ee51
--- /dev/null
+++ b/src/components/invest/chart/Indicators/sub/MOMChart.jsx
@@ -0,0 +1,56 @@
+import React, { useEffect } from 'react';
+import { LineSeries, SingleValueTooltip, StraightLine, XAxis, YAxis } from 'react-financial-charts';
+import { useDispatch, useSelector } from 'react-redux';
+import { setChartDatas } from '../../../../../store/reducers/Chart/chart';
+import { getMOMChart } from '../../../../../store/reducers/Chart/Indicators/sub';
+import { format } from 'd3-format';
+
+export default function MOMChart({ datas, isShow }) {
+ const dispatch = useDispatch();
+ const isActive = useSelector((state) => state.clickIndicator.MOM);
+ const MOMValue = useSelector((state) => state.indicatorValues.values.MOM);
+
+ const calculateMOM = (data) => {
+ dispatch(setChartDatas({
+ newData: datas,
+ data: data,
+ name: 'MOM'
+ }));
+ }
+
+ useEffect(() => {
+ if (isActive) {
+ const data = {
+ "chart": datas,
+ "Date" : MOMValue[0],
+ }
+ dispatch(getMOMChart(data))
+ .then((res) => calculateMOM(res.payload))
+ } else if (!isActive) {
+ const updatedDatas = datas.map(item => {
+ const newItem = { ...item };
+ delete newItem.mom;
+ return newItem;
+ });
+ dispatch(setChartDatas(updatedDatas));
+ }
+ }, [isActive, MOMValue, isShow]);
+
+ const pricesDisplayFormat = format(",");
+
+ return (
+ <>
+
+
+
+ d.mom} strokeStyle='#c215ae' strokeWidth={1.3} />
+ d.mom}
+ yLabel="모멘텀"
+ yDisplayFormat={format(",")}
+ labelFill='#c215ae'
+ />
+ >
+ )
+}
diff --git a/src/components/invest/chart/Indicators/sub/OBVChart.jsx b/src/components/invest/chart/Indicators/sub/OBVChart.jsx
new file mode 100644
index 0000000..7addf3d
--- /dev/null
+++ b/src/components/invest/chart/Indicators/sub/OBVChart.jsx
@@ -0,0 +1,54 @@
+import React, { useEffect } from 'react';
+import { LineSeries, SingleValueTooltip, StraightLine, XAxis, YAxis } from 'react-financial-charts';
+import { useDispatch, useSelector } from 'react-redux';
+import { setChartDatas } from '../../../../../store/reducers/Chart/chart';
+import { getOBVChart } from '../../../../../store/reducers/Chart/Indicators/sub';
+import { format } from 'd3-format';
+
+export default function OBVChart({ datas, isShow }) {
+ const dispatch = useDispatch();
+ const isActive = useSelector((state) => state.clickIndicator.OBV);
+
+ const calculateOBV = (data) => {
+ dispatch(setChartDatas({
+ newData: datas,
+ data: data,
+ name: 'OBV'
+ }));
+ }
+
+ useEffect(() => {
+ if (isActive) {
+ const data = {
+ "chart": datas,
+ }
+ dispatch(getOBVChart(data))
+ .then((res) => calculateOBV(res.payload))
+ } else if (!isActive) {
+ const updatedDatas = datas.map(item => {
+ const newItem = { ...item };
+ delete newItem.obv;
+ return newItem;
+ });
+ dispatch(setChartDatas(updatedDatas));
+ }
+ }, [isActive, isShow]);
+
+ const pricesDisplayFormat = format(",");
+
+ return (
+ <>
+
+
+
+ d.obv} strokeStyle='#b09409' strokeWidth={1.3} />
+ d.obv}
+ yLabel="OBV"
+ yDisplayFormat={format(",")}
+ labelFill='#b09409'
+ />
+ >
+ )
+}
diff --git a/src/components/invest/chart/Indicators/sub/PPOChart.jsx b/src/components/invest/chart/Indicators/sub/PPOChart.jsx
new file mode 100644
index 0000000..c08c012
--- /dev/null
+++ b/src/components/invest/chart/Indicators/sub/PPOChart.jsx
@@ -0,0 +1,57 @@
+import React, { useEffect } from 'react';
+import { LineSeries, SingleValueTooltip, StraightLine, XAxis, YAxis } from 'react-financial-charts';
+import { useDispatch, useSelector } from 'react-redux';
+import { setChartDatas } from '../../../../../store/reducers/Chart/chart';
+import { getPPOChart } from '../../../../../store/reducers/Chart/Indicators/sub';
+import { format } from 'd3-format';
+
+export default function PPOChart({ datas, isShow }) {
+ const dispatch = useDispatch();
+ const isActive = useSelector((state) => state.clickIndicator.PPO);
+ const PPOValue = useSelector((state) => state.indicatorValues.values.PPO);
+
+ const calculatePPO = (data) => {
+ dispatch(setChartDatas({
+ newData: datas,
+ data: data,
+ name: 'PPO'
+ }));
+ }
+
+ useEffect(() => {
+ if (isActive) {
+ const data = {
+ "chart": datas,
+ "longPeriod": PPOValue[0],
+ "shortPeriod": PPOValue[1]
+ }
+ dispatch(getPPOChart(data))
+ .then((res) => calculatePPO(res.payload))
+ } else if (!isActive) {
+ const updatedDatas = datas.map(item => {
+ const newItem = { ...item };
+ delete newItem.ppo;
+ return newItem;
+ });
+ dispatch(setChartDatas(updatedDatas));
+ }
+ }, [isActive, PPOValue, isShow]);
+
+ const pricesDisplayFormat = format(",");
+
+ return (
+ <>
+
+
+
+ d.ppo} strokeStyle='#0c5fad' strokeWidth={1.3} />
+ d.ppo}
+ yLabel="PPO"
+ yDisplayFormat={format(",")}
+ labelFill='#0c5fad'
+ />
+ >
+ )
+}
diff --git a/src/components/invest/chart/Indicators/sub/ROCChart.jsx b/src/components/invest/chart/Indicators/sub/ROCChart.jsx
new file mode 100644
index 0000000..6d94041
--- /dev/null
+++ b/src/components/invest/chart/Indicators/sub/ROCChart.jsx
@@ -0,0 +1,56 @@
+import React, { useEffect } from 'react';
+import { LineSeries, SingleValueTooltip, StraightLine, XAxis, YAxis } from 'react-financial-charts';
+import { useDispatch, useSelector } from 'react-redux';
+import { setChartDatas } from '../../../../../store/reducers/Chart/chart';
+import { getROCChart } from '../../../../../store/reducers/Chart/Indicators/sub';
+import { format } from 'd3-format';
+
+export default function ROCChart({ datas, isShow }) {
+ const dispatch = useDispatch();
+ const isActive = useSelector((state) => state.clickIndicator.ROC);
+ const ROCValue = useSelector((state) => state.indicatorValues.values.ROC);
+
+ const calculateROC = (data) => {
+ dispatch(setChartDatas({
+ newData: datas,
+ data: data,
+ name: 'ROC'
+ }));
+ }
+
+ useEffect(() => {
+ if (isActive) {
+ const data = {
+ "chart": datas,
+ "Date" : ROCValue[0],
+ }
+ dispatch(getROCChart(data))
+ .then((res) => calculateROC(res.payload))
+ } else if (!isActive) {
+ const updatedDatas = datas.map(item => {
+ const newItem = { ...item };
+ delete newItem.roc;
+ return newItem;
+ });
+ dispatch(setChartDatas(updatedDatas));
+ }
+ }, [isActive, ROCValue, isShow]);
+
+ const pricesDisplayFormat = format(",");
+
+ return (
+ <>
+
+
+
+ d.roc} strokeStyle='#c2155a' strokeWidth={1.3} />
+ d.roc}
+ yLabel="ROC"
+ yDisplayFormat={format(",")}
+ labelFill='#c2155a'
+ />
+ >
+ )
+}
diff --git a/src/components/invest/chart/Indicators/sub/RSIChart.jsx b/src/components/invest/chart/Indicators/sub/RSIChart.jsx
new file mode 100644
index 0000000..d28494a
--- /dev/null
+++ b/src/components/invest/chart/Indicators/sub/RSIChart.jsx
@@ -0,0 +1,56 @@
+import React, { useEffect } from 'react';
+import { LineSeries, SingleValueTooltip, StraightLine, XAxis, YAxis } from 'react-financial-charts';
+import { useDispatch, useSelector } from 'react-redux';
+import { setChartDatas } from '../../../../../store/reducers/Chart/chart';
+import { getRSIChart } from '../../../../../store/reducers/Chart/Indicators/sub';
+import { format } from 'd3-format';
+
+export default function RSIChart({ datas, isShow }) {
+ const dispatch = useDispatch();
+ const isActive = useSelector((state) => state.clickIndicator.RSI);
+ const RSIValue = useSelector((state) => state.indicatorValues.values.RSI);
+
+ const calculateRSI = (data) => {
+ dispatch(setChartDatas({
+ newData: datas,
+ data: data,
+ name: 'RSI'
+ }));
+ }
+
+ useEffect(() => {
+ if (isActive) {
+ const data = {
+ "chart": datas,
+ "Date" : RSIValue[0],
+ }
+ dispatch(getRSIChart(data))
+ .then((res) => calculateRSI(res.payload))
+ } else if (!isActive) {
+ const updatedDatas = datas.map(item => {
+ const newItem = { ...item };
+ delete newItem.rsi;
+ return newItem;
+ });
+ dispatch(setChartDatas(updatedDatas));
+ }
+ }, [isActive, RSIValue, isShow]);
+
+ const pricesDisplayFormat = format(",");
+
+ return (
+ <>
+
+
+
+ d.rsi} strokeStyle='#15857b' strokeWidth={1.3} />
+ d.rsi}
+ yLabel="RSI"
+ yDisplayFormat={format(",")}
+ labelFill='#15857b'
+ />
+ >
+ )
+}
diff --git a/src/components/invest/chart/Indicators/sub/STOCH.jsx b/src/components/invest/chart/Indicators/sub/STOCH.jsx
new file mode 100644
index 0000000..aa8989e
--- /dev/null
+++ b/src/components/invest/chart/Indicators/sub/STOCH.jsx
@@ -0,0 +1,67 @@
+import React, { useEffect } from 'react';
+import { LineSeries, SingleValueTooltip, StraightLine, XAxis, YAxis } from 'react-financial-charts';
+import { useDispatch, useSelector } from 'react-redux';
+import { setChartDatas } from '../../../../../store/reducers/Chart/chart';
+import { getSTOCHChart } from '../../../../../store/reducers/Chart/Indicators/sub';
+import { format } from 'd3-format';
+
+export default function STOCHChart({ datas, isShow }) {
+ const dispatch = useDispatch();
+ const isActive = useSelector((state) => state.clickIndicator.STOCH);
+ const STOCHValue = useSelector((state) => state.indicatorValues.values.STOCH);
+
+ const calculateSTOCH = (data) => {
+ dispatch(setChartDatas({
+ newData: datas,
+ data: data,
+ name: 'STOCH'
+ }));
+ }
+
+ useEffect(() => {
+ if (isActive) {
+ const data = {
+ "chart": datas,
+ "Date" : STOCHValue[0],
+ "period_K" : STOCHValue[1],
+ "period_D" : STOCHValue[2]
+ }
+ dispatch(getSTOCHChart(data))
+ .then((res) => calculateSTOCH(res.payload))
+ } else if (!isActive) {
+ const updatedDatas = datas.map(item => {
+ const newItem = { ...item };
+ delete newItem.outSlowK;
+ delete newItem.outSlowD;
+ return newItem;
+ });
+ dispatch(setChartDatas(updatedDatas));
+ }
+ }, [isActive, STOCHValue, isShow]);
+
+ const pricesDisplayFormat = format(",");
+
+ return (
+ <>
+
+
+
+ d.outSlowK} strokeStyle='#363602' strokeWidth={1.3} />
+ d.outSlowD} strokeStyle='#87870b' strokeWidth={1.3} />
+ d.outSlowK}
+ yLabel="Slow STO %K"
+ yDisplayFormat={format(",")}
+ labelFill='#363602'
+ />
+ d.outSlowD}
+ yLabel="Slow STO %D"
+ yDisplayFormat={format(",")}
+ labelFill='#87870b'
+ />
+ >
+ )
+}
diff --git a/src/components/invest/chart/Indicators/sub/STOCHFast.jsx b/src/components/invest/chart/Indicators/sub/STOCHFast.jsx
new file mode 100644
index 0000000..36e4b78
--- /dev/null
+++ b/src/components/invest/chart/Indicators/sub/STOCHFast.jsx
@@ -0,0 +1,66 @@
+import React, { useEffect } from 'react';
+import { LineSeries, SingleValueTooltip, StraightLine, XAxis, YAxis } from 'react-financial-charts';
+import { useDispatch, useSelector } from 'react-redux';
+import { setChartDatas } from '../../../../../store/reducers/Chart/chart';
+import { getSTOCHFChart } from '../../../../../store/reducers/Chart/Indicators/sub';
+import { format } from 'd3-format';
+
+export default function STOCHFChart({ datas, isShow }) {
+ const dispatch = useDispatch();
+ const isActive = useSelector((state) => state.clickIndicator.STOCHF);
+ const STOCHFValue = useSelector((state) => state.indicatorValues.values.STOCHF);
+
+ const calculateSTOCHF = (data) => {
+ dispatch(setChartDatas({
+ newData: datas,
+ data: data,
+ name: 'STOCHF'
+ }));
+ }
+
+ useEffect(() => {
+ if (isActive) {
+ const data = {
+ "chart": datas,
+ "period_K" : STOCHFValue[0],
+ "period_D" : STOCHFValue[1]
+ }
+ dispatch(getSTOCHFChart(data))
+ .then((res) => calculateSTOCHF(res.payload))
+ } else if (!isActive) {
+ const updatedDatas = datas.map(item => {
+ const newItem = { ...item };
+ delete newItem.outFastK;
+ delete newItem.outFastD;
+ return newItem;
+ });
+ dispatch(setChartDatas(updatedDatas));
+ }
+ }, [isActive, STOCHFValue, isShow]);
+
+ const pricesDisplayFormat = format(",");
+
+ return (
+ <>
+
+
+
+ d.outFastK} strokeStyle='#680A08' strokeWidth={1.3} />
+ d.outFastD} strokeStyle='#B87A80' strokeWidth={1.3} />
+ d.outFastK}
+ yLabel="Fast STO %K"
+ yDisplayFormat={format(",")}
+ labelFill='#680A08'
+ />
+ d.outFastD}
+ yLabel="Fast STO %D"
+ yDisplayFormat={format(",")}
+ labelFill='#B87A80'
+ />
+ >
+ )
+}
diff --git a/src/components/invest/chart/Indicators/sub/STOCHRSIChart.jsx b/src/components/invest/chart/Indicators/sub/STOCHRSIChart.jsx
new file mode 100644
index 0000000..284b55d
--- /dev/null
+++ b/src/components/invest/chart/Indicators/sub/STOCHRSIChart.jsx
@@ -0,0 +1,67 @@
+import React, { useEffect } from 'react';
+import { LineSeries, SingleValueTooltip, StraightLine, XAxis, YAxis } from 'react-financial-charts';
+import { useDispatch, useSelector } from 'react-redux';
+import { setChartDatas } from '../../../../../store/reducers/Chart/chart';
+import { getSTOCHRSIChart } from '../../../../../store/reducers/Chart/Indicators/sub';
+import { format } from 'd3-format';
+
+export default function STOCHRSIChart({ datas, isShow }) {
+ const dispatch = useDispatch();
+ const isActive = useSelector((state) => state.clickIndicator.STOCHRSI);
+ const STOCHRSIValue = useSelector((state) => state.indicatorValues.values.STOCHRSI);
+
+ const calculateSTOCHRSI = (data) => {
+ dispatch(setChartDatas({
+ newData: datas,
+ data: data,
+ name: 'STOCHRSI'
+ }));
+ }
+
+ useEffect(() => {
+ if (isActive) {
+ const data = {
+ "chart": datas,
+ "Date" : STOCHRSIValue[0],
+ "period_K" : STOCHRSIValue[1],
+ "period_D" : STOCHRSIValue[2]
+ }
+ dispatch(getSTOCHRSIChart(data))
+ .then((res) => calculateSTOCHRSI(res.payload))
+ } else if (!isActive) {
+ const updatedDatas = datas.map(item => {
+ const newItem = { ...item };
+ delete newItem.stochRsiK;
+ delete newItem.stochRsiD;
+ return newItem;
+ });
+ dispatch(setChartDatas(updatedDatas));
+ }
+ }, [isActive, STOCHRSIValue, isShow]);
+
+ const pricesDisplayFormat = format(",");
+
+ return (
+ <>
+
+
+
+ d.stochRsiK} strokeStyle='#04590b' strokeWidth={1.3} />
+ d.stochRsiD} strokeStyle='#55a35b' strokeWidth={1.3} />
+ d.stochRsiK}
+ yLabel="Stochastic RSI %K"
+ yDisplayFormat={format(",")}
+ labelFill='#04590b'
+ />
+ d.stochRsiD}
+ yLabel="Stochastic RSI %D"
+ yDisplayFormat={format(",")}
+ labelFill='#55a35b'
+ />
+ >
+ )
+}
diff --git a/src/components/invest/chart/Indicators/sub/TRIXChart.jsx b/src/components/invest/chart/Indicators/sub/TRIXChart.jsx
new file mode 100644
index 0000000..00b882a
--- /dev/null
+++ b/src/components/invest/chart/Indicators/sub/TRIXChart.jsx
@@ -0,0 +1,56 @@
+import React, { useEffect } from 'react';
+import { LineSeries, SingleValueTooltip, StraightLine, XAxis, YAxis } from 'react-financial-charts';
+import { useDispatch, useSelector } from 'react-redux';
+import { setChartDatas } from '../../../../../store/reducers/Chart/chart';
+import { getTRIXChart } from '../../../../../store/reducers/Chart/Indicators/sub';
+import { format } from 'd3-format';
+
+export default function TRIXChart({ datas, isShow }) {
+ const dispatch = useDispatch();
+ const isActive = useSelector((state) => state.clickIndicator.TRIX);
+ const TRIXValue = useSelector((state) => state.indicatorValues.values.TRIX);
+
+ const calculateTRIX = (data) => {
+ dispatch(setChartDatas({
+ newData: datas,
+ data: data,
+ name: 'TRIX'
+ }));
+ }
+
+ useEffect(() => {
+ if (isActive) {
+ const data = {
+ "chart": datas,
+ "Date": TRIXValue[0],
+ }
+ dispatch(getTRIXChart(data))
+ .then((res) => calculateTRIX(res.payload))
+ } else if (!isActive) {
+ const updatedDatas = datas.map(item => {
+ const newItem = { ...item };
+ delete newItem.trix;
+ return newItem;
+ });
+ dispatch(setChartDatas(updatedDatas));
+ }
+ }, [isActive, TRIXValue, isShow]);
+
+ const pricesDisplayFormat = format(",");
+
+ return (
+ <>
+
+
+
+ d.trix} strokeStyle='#b03609' strokeWidth={1.3} />
+ d.trix}
+ yLabel="TRIX"
+ yDisplayFormat={format(",")}
+ labelFill='#b03609'
+ />
+ >
+ )
+}
diff --git a/src/components/invest/chart/Indicators/sub/ULTOSCChart.jsx b/src/components/invest/chart/Indicators/sub/ULTOSCChart.jsx
new file mode 100644
index 0000000..d45b089
--- /dev/null
+++ b/src/components/invest/chart/Indicators/sub/ULTOSCChart.jsx
@@ -0,0 +1,58 @@
+import React, { useEffect } from 'react';
+import { LineSeries, SingleValueTooltip, StraightLine, XAxis, YAxis } from 'react-financial-charts';
+import { useDispatch, useSelector } from 'react-redux';
+import { setChartDatas } from '../../../../../store/reducers/Chart/chart';
+import { getULTOSCChart } from '../../../../../store/reducers/Chart/Indicators/sub';
+import { format } from 'd3-format';
+
+export default function ULTOSCChart({ datas, isShow }) {
+ const dispatch = useDispatch();
+ const isActive = useSelector((state) => state.clickIndicator.ULTOSC);
+ const ULTOSCValue = useSelector((state) => state.indicatorValues.values.ULTOSC);
+
+ const calculateULTOSC = (data) => {
+ dispatch(setChartDatas({
+ newData: datas,
+ data: data,
+ name: 'ULTOSC'
+ }));
+ }
+
+ useEffect(() => {
+ if (isActive) {
+ const data = {
+ "chart": datas,
+ "longPeriod" : ULTOSCValue[0],
+ "middlePeriod" : ULTOSCValue[1],
+ "shortPeriod" : ULTOSCValue[2]
+ }
+ dispatch(getULTOSCChart(data))
+ .then((res) => calculateULTOSC(res.payload))
+ } else if (!isActive) {
+ const updatedDatas = datas.map(item => {
+ const newItem = { ...item };
+ delete newItem.ultosc;
+ return newItem;
+ });
+ dispatch(setChartDatas(updatedDatas));
+ }
+ }, [isActive, ULTOSCValue, isShow]);
+
+ const pricesDisplayFormat = format(",");
+
+ return (
+ <>
+
+
+
+ d.ultosc} strokeStyle='#ada50c' strokeWidth={1.3} />
+ d.ultosc}
+ yLabel="Ultimate Oscillator"
+ yDisplayFormat={format(",")}
+ labelFill='#ada50c'
+ />
+ >
+ )
+}
diff --git a/src/components/invest/chart/Indicators/sub/WILLRChart.jsx b/src/components/invest/chart/Indicators/sub/WILLRChart.jsx
new file mode 100644
index 0000000..2548f9c
--- /dev/null
+++ b/src/components/invest/chart/Indicators/sub/WILLRChart.jsx
@@ -0,0 +1,56 @@
+import React, { useEffect } from 'react';
+import { LineSeries, SingleValueTooltip, StraightLine, XAxis, YAxis } from 'react-financial-charts';
+import { useDispatch, useSelector } from 'react-redux';
+import { setChartDatas } from '../../../../../store/reducers/Chart/chart';
+import { getWILLRChart } from '../../../../../store/reducers/Chart/Indicators/sub';
+import { format } from 'd3-format';
+
+export default function WILLRChart({ datas, isShow }) {
+ const dispatch = useDispatch();
+ const isActive = useSelector((state) => state.clickIndicator.WILLR);
+ const WILLRValue = useSelector((state) => state.indicatorValues.values.WILLR);
+
+ const calculateWILLR = (data) => {
+ dispatch(setChartDatas({
+ newData: datas,
+ data: data,
+ name: 'WILLR'
+ }));
+ }
+
+ useEffect(() => {
+ if (isActive) {
+ const data = {
+ "chart": datas,
+ "Date": WILLRValue[0],
+ }
+ dispatch(getWILLRChart(data))
+ .then((res) => calculateWILLR(res.payload))
+ } else if (!isActive) {
+ const updatedDatas = datas.map(item => {
+ const newItem = { ...item };
+ delete newItem.willr;
+ return newItem;
+ });
+ dispatch(setChartDatas(updatedDatas));
+ }
+ }, [isActive, WILLRValue, isShow]);
+
+ const pricesDisplayFormat = format(",");
+
+ return (
+ <>
+
+
+
+ d.willr} strokeStyle='#095db0' strokeWidth={1.3} />
+ d.willr}
+ yLabel="Williams %R"
+ yDisplayFormat={format(",")}
+ labelFill='#095db0'
+ />
+ >
+ )
+}
diff --git a/src/components/invest/chart/KospiContent.jsx b/src/components/invest/chart/KospiContent.jsx
new file mode 100644
index 0000000..c20feda
--- /dev/null
+++ b/src/components/invest/chart/KospiContent.jsx
@@ -0,0 +1,94 @@
+import React, { useEffect } from 'react'
+import { useDispatch, useSelector } from 'react-redux';
+import { getkospiKosdaqDatas } from '../../../store/reducers/Chart/kospiKosdaq';
+import styled from 'styled-components';
+import { CaretDownFill, CaretUpFill } from 'react-bootstrap-icons';
+
+export default function KospiContent() {
+ const dispatch = useDispatch();
+ const content = useSelector((state) => state.kospiKosdaq.data);
+
+ const kospiNum = parseFloat(content?.kospi?.upDown);
+ const kosdaqNum = parseFloat(content?.kosdaq?.upDown);
+ const kospiRate = parseFloat(content?.kospi?.rate);
+ const kosdaqRate = parseFloat(content?.kosdaq?.rate);
+
+ useEffect(() => {
+ dispatch(getkospiKosdaqDatas())
+ const timer = setInterval(() => {
+ dispatch(getkospiKosdaqDatas());
+ }, 3000)
+
+ return () => clearInterval(timer);
+ }, [])
+
+ return (
+
+
+
+ KOSPI
+ {content?.kospi?.price}
+
+
+
+ {kospiNum > 0 ?
+
+ : kospiNum < 0 ?
+
+ : null
+ }
+ {content?.kospi?.upDown}
+
+ ({content?.kospi?.rate}%)
+
+
+
+
+ KOSDAQ
+ {content?.kosdaq?.price}
+
+
+
+ {kosdaqNum > 0 ?
+
+ : kosdaqNum < 0 ?
+
+ : null
+ }
+ {content?.kosdaq?.upDown}
+
+ ({content?.kosdaq?.rate}%)
+
+
+
+ )
+}
+
+const Container = styled.div`
+ display: flex;
+`
+
+const SubContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ padding: 0 14px;
+ border-left: 1px solid rgba(0, 0, 0, 0.2);
+`
+
+const Content = styled.div`
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ padding: 0 15px;
+ gap: 7px;
+ font-size: 14px;
+`
+
+const MainFont = styled.span`
+ font-weight: 600;
+ font-size: 15px;
+`
+
+const NumFont = styled.span`
+ color: ${(props) => props.num > 0 ? "#c70606" : props.num < 0 ? "#0636c7" : "#000"};
+`
diff --git a/src/components/invest/chart/MainChart.jsx b/src/components/invest/chart/MainChart.jsx
new file mode 100644
index 0000000..0c94892
--- /dev/null
+++ b/src/components/invest/chart/MainChart.jsx
@@ -0,0 +1,769 @@
+import React, { useEffect, useState } from "react";
+import ReactDOM from "react-dom";
+import { format } from "d3-format";
+import { timeFormat } from "d3-time-format";
+import {
+ discontinuousTimeScaleProviderBuilder,
+ Chart,
+ ChartCanvas,
+ BarSeries,
+ CandlestickSeries,
+ OHLCTooltip,
+ lastVisibleItemBasedZoomAnchor,
+ XAxis,
+ YAxis,
+ CrossHairCursor,
+ EdgeIndicator,
+ MouseCoordinateX,
+ MouseCoordinateY,
+ ZoomButtons,
+ HoverTooltip,
+ SingleValueTooltip,
+} from "react-financial-charts";
+
+import { useSelector, useDispatch } from "react-redux";
+import styled from "styled-components";
+import {
+ getChartDatas,
+ setLiveData,
+} from "../../../store/reducers/Chart/chart";
+
+// 차트지표
+import SMAChart from "./Indicators/chart/SMAChart";
+import WMAChart from "./Indicators/chart/WMAChart";
+import EMAChart from "./Indicators/chart/EMAChart";
+import BBANDSChart from "./Indicators/chart/BBANDSChart";
+import SARChart from "./Indicators/chart/SARChart";
+import MACDChart from "./Indicators/sub/MACDChart";
+import STOCHFChart from "./Indicators/sub/STOCHFast";
+import STOCHChart from "./Indicators/sub/STOCH";
+import RSIChart from "./Indicators/sub/RSIChart";
+import CCIChart from "./Indicators/sub/CCIChart";
+import MOMChart from "./Indicators/sub/MOMChart";
+import ROCChart from "./Indicators/sub/ROCChart";
+import ADChart from "./Indicators/sub/ADChart";
+import ATRChart from "./Indicators/sub/ATRChart";
+import MFIChart from "./Indicators/sub/MFIChart";
+import OBVChart from "./Indicators/sub/OBVChart";
+import ADOSCChart from "./Indicators/sub/ADOSCChart";
+import TRIXChart from "./Indicators/sub/TRIXChart";
+import WILLRChart from "./Indicators/sub/WILLRChart";
+import DMIChart from "./Indicators/sub/DMIChart";
+import ADXChart from "./Indicators/sub/ADXChart";
+import ADXRChart from "./Indicators/sub/ADXRChart";
+import AROONChart from "./Indicators/sub/AROONChart";
+import AROONOSCChart from "./Indicators/sub/AROONOSCChart";
+import STOCHRSIChart from "./Indicators/sub/STOCHRSIChart";
+import ULTOSCChart from "./Indicators/sub/ULTOSCChart";
+import PPOChart from "./Indicators/sub/PPOChart";
+
+import { useWebSocket } from "../../../lib/hooks/useWebSocket";
+import ButtonContainer from "./ButtonContainer";
+import TitleContainer from "./TitleContainer";
+
+export default function MainChart({ toggleCharts, toggleIndicators, showCharts, showIndicators }) {
+ const dataList = useSelector((state) => state.chart.datas);
+ const clickDate = useSelector((state) => state.chart.date);
+ const company = useSelector((state) => state.company.data[1]);
+ const dispatch = useDispatch();
+ const [isShow, setIsShow] = useState(false);
+
+ // 클릭한 보조지표
+ const subIndi = useSelector((state) => state.clickIndicator.subIndi);
+ const chartIndi = useSelector((state) => state.clickIndicator.chartIndi);
+
+ const { askPrice, nowPrice } = useWebSocket();
+ const [upNum, setUpNum] = useState(0);
+
+ // 일, 주, 월, 년 데이터 불러오는 함수
+ function getData(format) {
+ const today = new Date();
+ const formattedDate = today.toISOString().slice(0, 10).replace(/-/g, "");
+ const data = {
+ code: company.code,
+ start_date: "19990101",
+ end_date: formattedDate,
+ time_format: format,
+ };
+
+ dispatch(getChartDatas(data)).then((res) => setIsShow((prev) => !prev));
+ }
+
+ useEffect(() => {
+ getData(clickDate);
+ }, [company]);
+
+ // socket 연결 시, 현재가 불러올때마다 data에 저장
+ useEffect(() => {
+ const today = new Date();
+ const formattedDate = today.toISOString().slice(0, 10).replace(/-/g, "");
+
+ if (nowPrice) {
+ setUpNum(parseFloat(nowPrice.message.close) - parseFloat(dataList[dataList.length - 2].close))
+ nowPrice.message['date'] = formattedDate;
+ dispatch(setLiveData(nowPrice.message));
+ }
+ }, [nowPrice]);
+
+ const ScaleProvider =
+ discontinuousTimeScaleProviderBuilder().inputDateAccessor((d) => {
+ const year = d?.date?.substr(0, 4);
+ const month = d?.date?.substr(4, 2);
+ const day = d?.date?.substr(6, 2);
+ const nDate = `${year}-${month}-${day}`;
+ return new Date(nDate);
+ });
+
+ const margin = { left: 5, right: 75, top: 10, bottom: 24 };
+
+ // window 사이즈에 맞춘 넓이/높이
+ const [height, setHeight] = useState(0);
+ const [width, setWidth] = useState(0);
+
+ useEffect(() => {
+ // 브라우저 높이에서 178px을 빼서 height 상태 업데이트
+ const updateHeight = () => {
+ const calculatedHeight = window.innerHeight - 178;
+ setHeight(calculatedHeight);
+ };
+
+ // 브라우저 넓이에서 650px을 빼서 width 상태 업데이트
+ const updateWidth = () => {
+ const calculatedWidth = window.innerWidth - 650;
+ setWidth(calculatedWidth);
+ };
+
+ // 컴포넌트 마운트 시 높이, 넓이 계산
+ updateHeight();
+ updateWidth();
+
+ // 브라우저 창 크기가 변경될 때마다 높이 재계산
+ window.addEventListener('resize', updateHeight);
+ window.addEventListener('resize', updateWidth);
+
+ // cleanup 함수
+ return () => {
+ window.removeEventListener('resize', updateHeight);
+ window.removeEventListener('resize', updateWidth);
+ };
+ }, []);
+
+ const { data, xScale, xAccessor, displayXAccessor } = ScaleProvider(dataList);
+
+ // 소수점 이하 둘째짜리까지만 표현
+ const pricesDisplayFormat = format(",");
+
+ const x_max = xAccessor(data[data.length - 1]);
+ const x_min = xAccessor(data[Math.max(0, data.length - 100)]);
+ const xExtents = [x_min, x_max + 2];
+
+ const gridHeight = height - margin.top - margin.bottom;
+
+ // default : 4
+ const barChartHeight = subIndi.length < 3 ? gridHeight / 4 : gridHeight / 6;
+
+ // 차트 추가될 때마다 origin 변경
+ const barChartOrigin = (_, h) => [
+ 0,
+ gridHeight - (subIndi.length + 1) * barChartHeight,
+ ];
+
+ // * (차트 개수)
+ const chartHeight = gridHeight - barChartHeight * (subIndi.length + 1);
+
+ const dateTimeFormat = "%Y/%m/%d";
+ const timeDisplayFormat = timeFormat(dateTimeFormat);
+
+ const hoverTimeFormat = "%Y년 %m월 %d일";
+ const HoverDisplayFormat = timeFormat(hoverTimeFormat);
+
+ const barChartExtents = (data) => {
+ return data?.volume;
+ };
+
+ const candleChartExtents = (data) => {
+ return [data?.high, data?.low];
+ };
+
+ const yEdgeIndicator = (data) => {
+ return data?.close;
+ };
+
+ const volumeColor = (data) => {
+ return data?.close > data?.open
+ ? "rgba(239, 83, 80, 0.3)"
+ : "rgba(38, 166, 154, 0.3)";
+ };
+
+ const volumeSeries = (data) => {
+ return data?.volume;
+ };
+
+ const openCloseColor = (data) => {
+ return data?.close > data?.open ? "#ef5350" :"#26a69a";
+ };
+
+ // hover 했을 때 보여줄 데이터
+ function tooltipContent() {
+ return ({ currentItem, xAccessor }) => {
+ return {
+ x: HoverDisplayFormat(xAccessor(currentItem)),
+ y: [
+ {
+ label: "시가",
+ value: currentItem?.open && pricesDisplayFormat(currentItem?.open),
+ },
+ {
+ label: "종가",
+ value:
+ currentItem?.close && pricesDisplayFormat(currentItem?.close),
+ },
+ {
+ label: "고가",
+ value: currentItem?.high && pricesDisplayFormat(currentItem?.high),
+ },
+ {
+ label: "저가",
+ value: currentItem?.low && pricesDisplayFormat(currentItem?.low),
+ },
+ {
+ label: "거래량",
+ value:
+ currentItem?.volume && pricesDisplayFormat(currentItem?.volume),
+ },
+ ],
+ };
+ };
+ }
+
+ return (
+
+ {dataList?.length > 0 ? (
+ <>
+
+
+ {/* 차트 */}
+
+ {/* 주식 캔들 차트 및 차트 지표 */}
+
+ {/* 분봉 호버했을 때, 날짜/시가/종가/고가/저가 표시 */}
+
+
+
+
+
+ {/* 차트지표 */}
+ {chartIndi.includes("SMA") && (
+
+ )}
+ {chartIndi.includes("WMA") && (
+
+ )}
+ {chartIndi.includes("EMA") && (
+
+ )}
+ {chartIndi.includes("BBANDS") && (
+
+ )}
+ {chartIndi.includes("SAR") && (
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ {/* 거래량 차트 */}
+
+
+
+
+
+ d.volume}
+ yLabel="거래량"
+ yDisplayFormat={format(",")}
+ />
+
+
+ {/* MACD 차트 */}
+ {subIndi.includes("MACD") && (
+ d.macd}
+ padding={20}
+ origin={(_, h) => [
+ 0,
+ gridHeight -
+ (subIndi.length - subIndi.indexOf("MACD")) * barChartHeight,
+ ]}
+ >
+
+
+ )}
+
+ {/* STOCHF 차트 */}
+ {subIndi.includes("STOCHF") && (
+ data.outFastK}
+ padding={20}
+ origin={(_, h) => [
+ 0,
+ gridHeight -
+ (subIndi.length - subIndi.indexOf("STOCHF")) *
+ barChartHeight,
+ ]}
+ >
+
+
+ )}
+
+ {/* STOCHF 차트 */}
+ {subIndi.includes("STOCH") && (
+ data.outSlowK}
+ padding={20}
+ origin={(_, h) => [
+ 0,
+ gridHeight -
+ (subIndi.length - subIndi.indexOf("STOCH")) *
+ barChartHeight,
+ ]}
+ >
+
+
+ )}
+
+ {/* RSI 차트 */}
+ {subIndi.includes("RSI") && (
+ data.rsi}
+ padding={20}
+ origin={(_, h) => [
+ 0,
+ gridHeight -
+ (subIndi.length - subIndi.indexOf("RSI")) * barChartHeight,
+ ]}
+ >
+
+
+ )}
+
+ {/* CCI 차트 */}
+ {subIndi.includes("CCI") && (
+ data.cci}
+ padding={20}
+ origin={(_, h) => [
+ 0,
+ gridHeight -
+ (subIndi.length - subIndi.indexOf("CCI")) * barChartHeight,
+ ]}
+ >
+
+
+ )}
+
+ {/* MOM 차트 */}
+ {subIndi.includes("MOM") && (
+ data.mom}
+ padding={20}
+ origin={(_, h) => [
+ 0,
+ gridHeight -
+ (subIndi.length - subIndi.indexOf("MOM")) * barChartHeight,
+ ]}
+ >
+
+
+ )}
+
+ {/* ROC 차트 */}
+ {subIndi.includes("ROC") && (
+ data.roc}
+ padding={20}
+ origin={(_, h) => [
+ 0,
+ gridHeight -
+ (subIndi.length - subIndi.indexOf("ROC")) * barChartHeight,
+ ]}
+ >
+
+
+ )}
+
+ {/* AD 차트 */}
+ {subIndi.includes("AD") && (
+ data.ad}
+ padding={20}
+ origin={(_, h) => [
+ 0,
+ gridHeight -
+ (subIndi.length - subIndi.indexOf("AD")) * barChartHeight,
+ ]}
+ >
+
+
+ )}
+
+ {/* ATR 차트 */}
+ {subIndi.includes("ATR") && (
+ data.atr}
+ padding={20}
+ origin={(_, h) => [
+ 0,
+ gridHeight -
+ (subIndi.length - subIndi.indexOf("ATR")) * barChartHeight,
+ ]}
+ >
+
+
+ )}
+
+ {/* MFI 차트 */}
+ {subIndi.includes("MFI") && (
+ data.mfi}
+ padding={20}
+ origin={(_, h) => [
+ 0,
+ gridHeight -
+ (subIndi.length - subIndi.indexOf("MFI")) * barChartHeight,
+ ]}
+ >
+
+
+ )}
+
+ {/* OBV 차트 */}
+ {subIndi.includes("OBV") && (
+ data.obv}
+ padding={20}
+ origin={(_, h) => [
+ 0,
+ gridHeight -
+ (subIndi.length - subIndi.indexOf("OBV")) * barChartHeight,
+ ]}
+ >
+
+
+ )}
+
+ {/* ADOSC 차트 */}
+ {subIndi.includes("ADOSC") && (
+ data.adosc}
+ padding={20}
+ origin={(_, h) => [
+ 0,
+ gridHeight -
+ (subIndi.length - subIndi.indexOf("ADOSC")) *
+ barChartHeight,
+ ]}
+ >
+
+
+ )}
+
+ {/* TRIX 차트 */}
+ {subIndi.includes("TRIX") && (
+ data.trix}
+ padding={20}
+ origin={(_, h) => [
+ 0,
+ gridHeight -
+ (subIndi.length - subIndi.indexOf("TRIX")) * barChartHeight,
+ ]}
+ >
+
+
+ )}
+
+ {/* WILLR 차트 */}
+ {subIndi.includes("WILLR") && (
+ data.willr}
+ padding={20}
+ origin={(_, h) => [
+ 0,
+ gridHeight -
+ (subIndi.length - subIndi.indexOf("WILLR")) *
+ barChartHeight,
+ ]}
+ >
+
+
+ )}
+
+ {/* DMI (DX) 차트 */}
+ {/* 값 제대로 받아오는지 확인 필요 */}
+ {subIndi.includes("DX") && (
+ data.dx}
+ padding={20}
+ origin={(_, h) => [
+ 0,
+ gridHeight -
+ (subIndi.length - subIndi.indexOf("DX")) * barChartHeight,
+ ]}
+ >
+
+
+ )}
+
+ {/* ADX 차트 */}
+ {subIndi.includes("ADX") && (
+ data.adx}
+ padding={20}
+ origin={(_, h) => [
+ 0,
+ gridHeight -
+ (subIndi.length - subIndi.indexOf("ADX")) * barChartHeight,
+ ]}
+ >
+
+
+ )}
+
+ {/* ADXR 차트 */}
+ {subIndi.includes("ADXR") && (
+ data.adxr}
+ padding={20}
+ origin={(_, h) => [
+ 0,
+ gridHeight -
+ (subIndi.length - subIndi.indexOf("ADXR")) * barChartHeight,
+ ]}
+ >
+
+
+ )}
+
+ {/* AROON 차트 */}
+ {subIndi.includes("AROON") && (
+ data.aroonDown}
+ padding={20}
+ origin={(_, h) => [
+ 0,
+ gridHeight -
+ (subIndi.length - subIndi.indexOf("AROON")) *
+ barChartHeight,
+ ]}
+ >
+
+
+ )}
+
+ {/* AROONOSC 차트 */}
+ {subIndi.includes("AROONOSC") && (
+ data.aroonosc}
+ padding={20}
+ origin={(_, h) => [
+ 0,
+ gridHeight -
+ (subIndi.length - subIndi.indexOf("AROONOSC")) *
+ barChartHeight,
+ ]}
+ >
+
+
+ )}
+
+ {/* STOCHRSI 차트 */}
+ {subIndi.includes("STOCHRSI") && (
+ data.stochRsiK}
+ padding={20}
+ origin={(_, h) => [
+ 0,
+ gridHeight -
+ (subIndi.length - subIndi.indexOf("STOCHRSI")) *
+ barChartHeight,
+ ]}
+ >
+
+
+ )}
+
+ {/* ULTOSC 차트 */}
+ {subIndi.includes("ULTOSC") && (
+ data.ultosc}
+ padding={20}
+ origin={(_, h) => [
+ 0,
+ gridHeight -
+ (subIndi.length - subIndi.indexOf("ULTOSC")) *
+ barChartHeight,
+ ]}
+ >
+
+
+ )}
+
+ {/* PPO 차트 */}
+ {/* PPO Signal 데이터인듯 */}
+ {subIndi.includes("PPO") && (
+ data.ppo}
+ padding={20}
+ origin={(_, h) => [
+ 0,
+ gridHeight -
+ (subIndi.length - subIndi.indexOf("PPO")) * barChartHeight,
+ ]}
+ >
+
+
+ )}
+
+ >
+ ) : null}
+
+ );
+}
+
+const Container = styled.div`
+ display: flex;
+ flex-direction: column;
+ width: calc(100vw - 650px);
+ // padding: 0 10px;
+`;
+
diff --git a/src/components/invest/chart/TitleContainer.jsx b/src/components/invest/chart/TitleContainer.jsx
new file mode 100644
index 0000000..f7edad2
--- /dev/null
+++ b/src/components/invest/chart/TitleContainer.jsx
@@ -0,0 +1,142 @@
+import React from 'react'
+import styled from 'styled-components';
+import KospiContent from './KospiContent';
+import { CaretDownFill, CaretUpFill } from 'react-bootstrap-icons';
+import {getLogoFileName, onErrorImg} from "~/util/getLogoFileName";
+
+export default function TitleContainer({ nowPrice, company, upNum, dataList }) {
+
+ // 장이 열려있을 때, 현재가 대비 종가 등락
+ const nowRate = upNum / parseFloat(dataList[dataList.length - 2].close) * 100;
+
+ // 장이 마감됐을 때, 전일 대비 종가 등락
+ const upDown = parseFloat(dataList[dataList.length - 1].close) - parseFloat(dataList[dataList.length - 2].close)
+ const close = dataList[dataList.length - 1].close.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+ const stockRate = upDown / parseFloat(dataList[dataList.length - 2].close) * 100;
+
+ return (
+
+
+
+
+
+ {company.name}
+
+ {company.code} {company.market}
+
+
+
+
+ {/* 실시간 데이터가 있을 때 (장이 열려있을 때) */}
+ {nowPrice?.message ? (
+ <>
+ {nowPrice?.message.close.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}
+
+ {upNum > 0 ?
+
+ : upNum < 0 ?
+
+ : null
+ }
+ {upNum.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}
+ (
+ {nowRate > 0 && (
+ +
+ )}
+ {nowRate.toFixed(2)}%)
+
+ >
+ ) : (
+ <>
+ {close}
+
+ {upDown > 0 ?
+
+ : upDown < 0 ?
+
+ : null
+ }
+ {upDown.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}
+ (
+ {stockRate > 0 && (
+ +
+ )}
+ {stockRate.toFixed(2)}%)
+
+ >
+ )}
+
+
+
+
+ )
+}
+
+const MainContainer = styled.div`
+ display: flex;
+ align-items: center;
+ padding: 0 10px;
+ justify-content: space-between;
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1)
+`
+
+const SubContainer = styled.div`
+ display: flex;
+ gap: 20px;
+`
+
+const CompanyContainer = styled.div`
+ display: flex;
+ align-items: center;
+ padding: 10px 5px;
+`;
+
+const CompanyLogo = styled.img`
+ width: 40px;
+ height: 40px;
+ border-radius: 999px;
+ margin-right: 10px;
+`;
+
+const StockInfo = styled.div`
+ display: flex;
+ align-items: center;
+ gap: 5px;
+`
+
+const StockDiv = styled.div`
+ display: flex;
+ align-items: center;
+ margin-top: 5px;
+ gap: 5px;
+`
+
+const StockFont = styled.span`
+ padding-right: 15px;
+ font-size: 20px;
+
+ color: ${(props) => props.num > 0 ? "#c70606" : props.num < 0 ? "#0636c7" : "#000"};
+`
+
+const StockFont2 = styled.span`
+ color: ${(props) => props.num > 0 ? "#c70606" : props.num < 0 ? "#0636c7" : "#000"};
+`
+
+const FontContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+`;
+
+const MainFont = styled.span`
+ font-weight: 700;
+`;
+
+const SubFont = styled.span`
+ font-size: 12px;
+`;
diff --git a/src/components/invest/left-bar/ChartIndicators.jsx b/src/components/invest/left-bar/ChartIndicators.jsx
new file mode 100644
index 0000000..e59d51d
--- /dev/null
+++ b/src/components/invest/left-bar/ChartIndicators.jsx
@@ -0,0 +1,200 @@
+import React, { useState } from "react";
+import styled from "styled-components";
+import { useDispatch, useSelector } from "react-redux";
+import {
+ setActiveSub,
+ setChartIndi,
+ setDisactiveSub,
+} from "../../../store/reducers/Chart/Indicators/clickIndicators";
+import IndicatorDetail from "./IndicatorDetail";
+import chartData from "../../../Json/chartData.json";
+
+const ChartIndicators = ({ onClose }) => {
+ const dispatch = useDispatch();
+
+ const [showDetail, setShowDetail] = useState(
+ Array(chartData.length).fill(false)
+ );
+
+ const toggleDetail = (index) => {
+ setShowDetail((prevShowDetail) => {
+ const newShowDetail = [...prevShowDetail];
+ newShowDetail[index] = !newShowDetail[index];
+ return newShowDetail;
+ });
+ };
+
+ const isActive = useSelector((state) => state.clickIndicator);
+ const chartIndi = useSelector((state) => state.clickIndicator.chartIndi);
+
+ // 1️⃣ onChange함수를 사용하여 이벤트 감지, 필요한 값 받아오기
+ const onCheckedElement = (checked, item) => {
+ if (chartIndi.length < 3) {
+ if (checked) {
+ dispatch(setActiveSub(item));
+ dispatch(setChartIndi([...chartIndi, item]));
+ }
+ } else {
+ if (checked) {
+ dispatch(setActiveSub(item));
+ dispatch(
+ setChartIndi([...chartIndi.filter((el, idx) => idx !== 0), item])
+ );
+ dispatch(setDisactiveSub(chartIndi[0]));
+ }
+ }
+
+ if (!checked) {
+ dispatch(setDisactiveSub(item));
+ dispatch(setChartIndi(chartIndi.filter((el) => el !== item)));
+ }
+ };
+
+ return (
+
+
+ 차트지표
+
+
+
+ {chartData.map((item, idx) => (
+
+ {
+ onCheckedElement(e.target.checked, e.target.value);
+ }}
+ >
+ {item.showName}
+ toggleDetail(idx)}>설정
+
+ {showDetail[idx] ? (
+ toggleDetail(idx)}
+ initialValues={item.vars.map((item) => item.default)}
+ />
+ ) : (
+ <>>
+ )}
+
+
+ ))}
+
+
+ );
+};
+
+const Container = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+`;
+
+const CloseButton = styled.img`
+ position: absolute;
+ right: 10px;
+ top: 8px;
+ width: 25px;
+ cursor: pointer;
+`;
+
+const IndicatorsWrapper = styled.div`
+ display: flex;
+ flex-direction: row;
+ width: 100%;
+ border-bottom: 1px solid #c9c9c9;
+ justify-content: center;
+`;
+
+const IndicatorDiv = styled.div`
+ color: #000;
+ text-align: center;
+ font-size: 18px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: normal;
+ padding-top: 10px;
+ padding-bottom: 10px;
+`;
+
+const ItemContainer = styled.div`
+ width: 100%;
+`;
+
+const ItemWrapper = styled.div`
+ display: flex;
+ height: 45px;
+ width: 100%;
+ align-items: center;
+ border-bottom: 1px solid #c9c9c9;
+ padding-right: 10px;
+`;
+
+const CheckBox = styled.input`
+ position: relative;
+ -moz-appearance: none;
+ -webkit-appearance: none;
+ appearance: none;
+ margin: 0px 10px;
+ width: 20px;
+ height: 20px;
+ border: 1.5px solid gainsboro;
+ border-radius: 5px;
+ background-color: white;
+ transition: background-color 0.3s, border-color 0.3s;
+
+ &:checked {
+ border-color: transparent;
+ background-size: 100% 100%;
+ background-position: 50%;
+ background-repeat: no-repeat;
+ background-color: #fc6d6d;
+
+ &::before {
+ content: "✓";
+ font-size: 14px;
+ color: white;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ }
+ }
+`;
+
+const ItemDiv = styled.div``;
+
+const DetailBtn = styled.button`
+ margin-left: auto;
+ background: none;
+ border: 1px solid #474747;
+ border-radius: 100px;
+
+ &:hover {
+ border: 1px solid #474747;
+ background-color: #474747;
+ color: white;
+ }
+`;
+
+const DetailContainer = styled.div`
+ width: 250px;
+ height: 100%;
+ background-color: #fff;
+ box-shadow: 0px 0px 2px rgba(0, 0, 0, 0.1);
+ transform: translateX(${(props) => (props.$showdetail ? "0" : "-100%")});
+ transition: transform 0.3s ease;
+ position: absolute;
+ top: 0;
+ left: 0;
+ overflow: hidden;
+ z-index: 999;
+`;
+
+export default ChartIndicators;
diff --git a/src/components/invest/left-bar/IndicatorDetail.jsx b/src/components/invest/left-bar/IndicatorDetail.jsx
new file mode 100644
index 0000000..c1d4820
--- /dev/null
+++ b/src/components/invest/left-bar/IndicatorDetail.jsx
@@ -0,0 +1,266 @@
+import React from "react";
+import styled from "styled-components";
+import { useDispatch, useSelector } from "react-redux";
+import * as chartValuesModule from "../../../store/reducers/Trading/chartValues";
+import * as indicatorValuesModule from "../../../store/reducers/Trading/indicatorValues";
+
+const IndicatorDetail = ({
+ indiType,
+ showName,
+ name,
+ vars,
+ onClose,
+ initialValues,
+}) => {
+ const dispatch = useDispatch();
+
+ let setValuesReducer;
+ if (indiType === "chart") {
+ setValuesReducer = chartValuesModule;
+ } else if (indiType === "indi") {
+ setValuesReducer = indicatorValuesModule;
+ }
+ const { setValues } = setValuesReducer;
+
+ const values = useSelector((state) => {
+ if (indiType === "chart") {
+ return state.chartValues.values[name];
+ } else if (indiType === "indi") {
+ return state.indicatorValues.values[name];
+ }
+ });
+
+ const decreaseValue = (idx) => {
+ const newValues = [...values];
+ if (newValues[idx] > vars[idx].min) {
+ newValues[idx] = Math.round((newValues[idx] - vars[idx].val) * 100) / 100;
+ const payload = { [name]: newValues };
+ dispatch(setValues(payload));
+ }
+ };
+
+ const increaseValue = (idx) => {
+ const newValues = [...values];
+ if (newValues[idx] < vars[idx].max) {
+ newValues[idx] = Math.round((newValues[idx] + vars[idx].val) * 100) / 100;
+ const payload = { [name]: newValues };
+ dispatch(setValues(payload));
+ }
+ };
+
+ return (
+
+
+ {showName}
+
+
+
+ {values.length !== 0 ? (
+
+ 변수 설정
+ {
+ const payload = { [name]: initialValues };
+ dispatch(setValues(payload));
+ }}
+ >
+ 초기화
+
+
+ ) : (
+
+ 변수가 없습니다.
+
+ )}
+ {vars.map((item, idx) => (
+
+ {item.name}
+
+ {
+ decreaseValue(idx);
+ }}
+ >
+
+
+
+ {
+ const newValue =
+ e.target.value.trim() === ""
+ ? ""
+ : parseFloat(e.target.value);
+ const newValues = [...values];
+ newValues[idx] = newValue;
+ const payload = { [name]: newValues };
+ dispatch(setValues(payload));
+ }}
+ onBlur={(e) => {
+ const newValue =
+ e.target.value.trim() === ""
+ ? item.default
+ : parseFloat(e.target.value);
+ const newValues = [...values];
+ newValues[idx] = newValue;
+ const payload = { [name]: newValues };
+ dispatch(setValues(payload));
+ }}
+ />
+ {
+ increaseValue(idx);
+ }}
+ >
+
+
+
+
+
+ ))}
+
+
+ );
+};
+
+const Container = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+`;
+
+const CloseButton = styled.img`
+ position: absolute;
+ right: 10px;
+ top: 8px;
+ width: 25px;
+ cursor: pointer;
+`;
+
+const IndicatorsWrapper = styled.div`
+ display: flex;
+ flex-direction: row;
+ width: 100%;
+ border-bottom: 1px solid #c9c9c9;
+ justify-content: center;
+`;
+
+const IndicatorDiv = styled.div`
+ color: #000;
+ text-align: center;
+ font-size: 18px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: normal;
+ padding-top: 10px;
+ padding-bottom: 10px;
+`;
+
+const ItemContainer = styled.div`
+ width: 100%;
+`;
+
+const RowDiv = styled.div`
+ display: flex;
+ align-items: center;
+ margin: 10px 10px 0px 0px;
+`;
+
+const VarDiv = styled.div`
+ width: 100%;
+ margin-left: 10px;
+ font-size: 14px;
+ color: gray;
+`;
+
+const ResetBtn = styled.button`
+ background: none;
+ font-size: 14px;
+ width: 80px;
+ height: 30px;
+ box-sizing: border-box;
+ border: none;
+ color: gray;
+ &:hover {
+ background-color: #ececec;
+ color: black;
+ }
+`;
+
+const ItemWrapper = styled.div`
+ display: flex;
+ height: 50px;
+ width: 100%;
+ align-items: center;
+ border-bottom: 1px solid #c9c9c9;
+ padding-right: 10px;
+ justify-content: space-between;
+`;
+
+const ItemDiv = styled.div`
+ margin-left: 10px;
+ font-size: 17px;
+`;
+
+const ValueContainer = styled.div`
+ display: flex;
+ align-items: center;
+`;
+
+const VarInput = styled.input`
+ width: 80px;
+ margin: 0 5px;
+ padding: 2px 0;
+ font-size: 17px;
+ border: 0.5px solid black;
+ text-align: center;
+
+ -webkit-appearance: none;
+ -moz-appearance: textfield;
+ appearance: none;
+ &::-webkit-inner-spin-button,
+ &::-webkit-outer-spin-button {
+ -webkit-appearance: none;
+ margin: 0;
+ }
+`;
+
+const Img1 = styled.img`
+ width: 12px;
+`;
+
+const Img2 = styled.img`
+ width: 12px;
+ display: none;
+`;
+
+const Btn = styled.button`
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: 20px;
+ height: 20px;
+ background: none;
+ border: 1px solid black;
+ border-radius: 100px;
+ text-align: center;
+
+ &:hover {
+ background-color: black;
+ border: 1px solid black;
+ }
+
+ &:hover > ${Img2} {
+ display: block;
+ }
+
+ &:hover > ${Img1} {
+ display: none;
+ }
+`;
+
+export default IndicatorDetail;
diff --git a/src/components/invest/left-bar/Indicators.jsx b/src/components/invest/left-bar/Indicators.jsx
new file mode 100644
index 0000000..bbc7968
--- /dev/null
+++ b/src/components/invest/left-bar/Indicators.jsx
@@ -0,0 +1,213 @@
+import React, { useState } from "react";
+import styled from "styled-components";
+import { useDispatch, useSelector } from "react-redux";
+import {
+ setActiveSub,
+ setDisactiveSub,
+ setSubIndi,
+} from "../../../store/reducers/Chart/Indicators/clickIndicators";
+import IndicatorDetail from "./IndicatorDetail";
+import indiData from "../../../Json/indiData.json";
+
+const Indicators = ({ onClose }) => {
+ const dispatch = useDispatch();
+ const [showDetail, setShowDetail] = useState(
+ Array(indiData.length).fill(false)
+ );
+
+ const toggleDetail = (index) => {
+ setShowDetail((prevShowDetail) => {
+ const newShowDetail = [...prevShowDetail];
+ newShowDetail[index] = !newShowDetail[index];
+ return newShowDetail;
+ });
+ };
+
+ const isActive = useSelector((state) => state.clickIndicator);
+ const subIndi = useSelector((state) => state.clickIndicator.subIndi);
+
+ // 1️⃣ onChange함수를 사용하여 이벤트 감지, 필요한 값 받아오기
+ const onCheckedElement = (checked, item) => {
+ if (subIndi.length < 3) {
+ if (checked) {
+ dispatch(setActiveSub(item));
+ dispatch(setSubIndi([...subIndi, item]));
+ }
+ } else {
+ if (checked) {
+ dispatch(setActiveSub(item));
+ dispatch(setSubIndi([...subIndi.filter((el, idx) => idx !== 0), item]));
+ dispatch(setDisactiveSub(subIndi[0]));
+ }
+ }
+
+ if (!checked) {
+ dispatch(setDisactiveSub(item));
+ dispatch(setSubIndi(subIndi.filter((el) => el !== item)));
+ }
+ };
+
+ return (
+
+
+ 보조지표
+
+
+
+ {indiData.map((item, idx) => (
+
+ {
+ onCheckedElement(e.target.checked, e.target.value);
+ }}
+ >
+ {item.showName}
+ toggleDetail(idx)}>설정
+
+ {showDetail[idx] ? (
+ toggleDetail(idx)}
+ initialValues={item.vars.map((item) => item.default)}
+ />
+ ) : (
+ <>>
+ )}
+
+
+ ))}
+
+
+ );
+};
+
+const Container = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+`;
+
+const CloseButton = styled.img`
+ position: absolute;
+ right: 10px;
+ top: 8px;
+ width: 25px;
+ cursor: pointer;
+`;
+
+const IndicatorsWrapper = styled.div`
+ display: flex;
+ flex-direction: row;
+ width: 100%;
+ border-bottom: 1px solid #c9c9c9;
+ justify-content: center;
+`;
+
+const IndicatorDiv = styled.div`
+ color: #000;
+ text-align: center;
+ font-size: 18px;
+ font-weight: 400;
+ line-height: normal;
+ padding-top: 10px;
+ padding-bottom: 10px;
+`;
+
+const ItemContainer = styled.div`
+ width: 100%;
+ height: calc(100vh - 106px);
+ overflow: auto;
+
+ &::-webkit-scrollbar {
+ width: 3px;
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background: rgba(0, 0, 0, 0.2);
+ border-radius: 20px;
+ }
+
+ &::-webkit-scrollbar-track {
+ background: rgba(0, 0, 0, 0.05);
+ border-radius: 20px;
+ }
+`;
+
+const ItemWrapper = styled.div`
+ display: flex;
+ height: 45px;
+ width: 100%;
+ align-items: center;
+ border-bottom: 1px solid #c9c9c9;
+ padding-right: 10px;
+`;
+
+const CheckBox = styled.input`
+ position: relative;
+ -moz-appearance: none;
+ -webkit-appearance: none;
+ appearance: none;
+ margin: 0px 10px;
+ width: 20px;
+ height: 20px;
+ border: 1.5px solid gainsboro;
+ border-radius: 5px;
+ background-color: white;
+ transition: background-color 0.3s, border-color 0.3s;
+
+ &:checked {
+ border-color: transparent;
+ background-size: 100% 100%;
+ background-position: 50%;
+ background-repeat: no-repeat;
+ background-color: #fc6d6d;
+
+ &::before {
+ content: "✓";
+ font-size: 14px;
+ color: white;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ }
+ }
+`;
+
+const ItemDiv = styled.div``;
+
+const DetailBtn = styled.button`
+ margin-left: auto;
+ background: none;
+ border: 1px solid #474747;
+ color: #474747;
+ border-radius: 100px;
+
+ &:hover {
+ border: 1px solid #474747;
+ background-color: #474747;
+ color: white;
+ }
+`;
+
+const DetailContainer = styled.div`
+ width: 250px;
+ height: 100%;
+ background-color: #fff;
+ box-shadow: 0px 0px 2px rgba(0, 0, 0, 0.1);
+ transform: translateX(${(props) => (props.$showdetail ? "0" : "-100%")});
+ transition: transform 0.3s ease;
+ position: absolute;
+ top: 0;
+ left: 0;
+ overflow: hidden;
+ z-index: 999;
+`;
+
+export default Indicators;
diff --git a/src/components/invest/left-bar/MyStockList.jsx b/src/components/invest/left-bar/MyStockList.jsx
new file mode 100644
index 0000000..ef0498d
--- /dev/null
+++ b/src/components/invest/left-bar/MyStockList.jsx
@@ -0,0 +1,95 @@
+import React, { useState, useEffect, useRef } from "react";
+import styled from "styled-components";
+import { useDispatch, useSelector } from "react-redux";
+import {
+ postSearchUser,
+ postLikeStock,
+ fetchLikeStock,
+ fetchLikeStockArr,
+} from "~/store/reducers/Trading/search";
+import { setFavoriteArr } from "~/store/reducers/Trading/search";
+import { getCookie } from "~/lib/apis/cookie";
+import StockItem from "./MyStockList/StockItem";
+import SearchStock from "./MyStockList/SearchStock";
+
+const MyStockList = () => {
+ const isLogin = !!getCookie("token");
+ const [searchInput, setSearchInput] = useState("");
+ const [isClick, setIsClick] = useState(false);
+
+ const dispatch = useDispatch();
+ const user = useSelector((state) => state.user.user);
+ const favoriteArr = useSelector((state) => state.search.favoriteArr);
+ const myFavoriteArr = useSelector((state) => state.search.myFavoriteArr);
+
+ useEffect(() => {
+ if (isLogin) {
+ dispatch(fetchLikeStock(user.id));
+ dispatch(fetchLikeStockArr(user.id));
+ }
+ }, [dispatch, isClick, user.id]);
+
+ const addToFavorites = (result) => {
+ setIsClick((prev) => !prev);
+ if (!result.isLike) {
+ const updatedArr = [...favoriteArr, result.code];
+ dispatch(setFavoriteArr(updatedArr));
+ dispatch(postLikeStock({ likeStock: updatedArr, userId: user.id }));
+ dispatch(postSearchUser({ searchQuery: searchInput, userId: user.id }));
+ } else {
+ const updatedFavoriteArr = favoriteArr.filter(
+ (item) => item !== result.code
+ );
+ dispatch(setFavoriteArr(updatedFavoriteArr));
+ dispatch(
+ postLikeStock({ likeStock: updatedFavoriteArr, userId: user.id })
+ );
+ dispatch(postSearchUser({ searchQuery: searchInput, userId: user.id }));
+ }
+ };
+
+ return (
+
+
+ 관심 종목
+
+
+
+
+ );
+};
+
+const LeftContainer = styled.section`
+ min-width: 250px;
+ width: 250px;
+ height: calc(100vh - 57px);
+ border-right: 1px solid #e2e2e2;
+`;
+
+const MyItemContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+`;
+
+const MyItemDiv = styled.div`
+ color: #000;
+ text-align: center;
+ font-size: 18px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: normal;
+ padding-top: 10px;
+ padding-bottom: 10px;
+ width: 100%;
+ border-bottom: 1px solid #c9c9c9;
+`;
+
+export default MyStockList;
diff --git a/src/components/invest/left-bar/MyStockList/SearchStock.jsx b/src/components/invest/left-bar/MyStockList/SearchStock.jsx
new file mode 100644
index 0000000..c49ac9e
--- /dev/null
+++ b/src/components/invest/left-bar/MyStockList/SearchStock.jsx
@@ -0,0 +1,289 @@
+import React, { useEffect, useRef, useState } from "react";
+import { useDispatch, useSelector } from "react-redux";
+import { styled } from "styled-components";
+import {
+ postSearch,
+ postSearchUser,
+} from "../../../../store/reducers/Trading/search";
+import { getLogoFileName, onErrorImg } from "../../../../util/getLogoFileName";
+import { getCookie } from "../../../../lib/apis/cookie";
+import { setClickCompany } from "../../../../store/reducers/Chart/clickCompany";
+
+const SearchStock = ({ addToFavorites, searchInput, setSearchInput }) => {
+ const searchRef = useRef(null);
+ const isLogin = !!getCookie("token");
+ const [showSearch, setShowSearch] = useState(false);
+
+ const dispatch = useDispatch();
+ const user = useSelector((state) => state.user.user);
+ const searchResults = useSelector((state) => state.search.searchResults);
+
+ const searchFunc = (prop) => {
+ const actionToDispatch = isLogin
+ ? postSearchUser({ searchQuery: prop.searchQuery, userId: user.id })
+ : postSearch({ searchQuery: prop.searchQuery });
+ dispatch(actionToDispatch);
+ };
+
+ useEffect(() => {
+ searchFunc({ searchQuery: "" });
+ setSearchInput("");
+ setShowSearch(false);
+ }, []);
+
+ const onChangeInput = async (e) => {
+ setSearchInput(e.target.value);
+ searchFunc({ searchQuery: e.target.value });
+ };
+
+ return (
+ {
+ setShowSearch(true);
+ }}
+ >
+ {showSearch ? (
+
+
+
+ onChangeInput(e)}
+ >
+ {searchInput && (
+ {
+ setSearchInput("");
+ searchFunc({ searchQuery: "" });
+ }}
+ >
+
+
+ )}
+ {
+ e.stopPropagation();
+ setSearchInput("");
+ setShowSearch(false);
+ searchFunc({ searchQuery: "" });
+ }}
+ >
+ 닫기
+
+
+ {searchResults?.length > 0 && (
+
+ {searchResults?.map((result) => (
+ {
+ dispatch(setClickCompany(result));
+ }}
+ >
+
+
+ {result.name}
+
+ {result.code}
+
+ {result.market === "kospi" ? "코스피" : "코스닥"}
+
+
+
+ {isLogin ? (
+ addToFavorites(result)}
+ src={
+ result.isLike
+ ? "/icon/FilledStar.svg"
+ : "/icon/BlankStar.svg"
+ }
+ alt={result.isLike ? "filledstar" : "blankstar"}
+ $hover="hover"
+ />
+ ) : (
+ <>>
+ )}
+
+ ))}
+
+ )}
+
+ ) : (
+
+
+ 종목 검색
+
+ )}
+
+ );
+};
+
+const SearchContainer = styled.div`
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: center;
+ color: #000;
+ text-align: center;
+ font-size: 22px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: normal;
+ width: 100%;
+`;
+
+const SearchWrapper = styled.label`
+ position: relative;
+ display: flex;
+ width: 100%;
+ padding: 10px;
+ height: 44px;
+ background-color: #f3f3f3;
+`;
+
+const Img = styled.img`
+ position: absolute;
+ top: 15.2px;
+ left: 18px;
+ color: grey;
+ width: 15px;
+`;
+
+const SearchInput = styled.input`
+ width: 85%;
+ padding-left: 30px;
+ font-size: 15px;
+ border: none;
+ background-color: #f3f3f3;
+`;
+
+const SearchClearBtn = styled.button`
+ position: absolute;
+ right: 50px;
+ top: 14px;
+
+ border: none;
+ background: none;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ cursor: pointer;
+`;
+
+const SearchOutBtn = styled.button`
+ width: 17%;
+ margin-left: 5%;
+ border: none;
+ background: none;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ cursor: pointer;
+`;
+
+const SearchDiv = styled.div`
+ font-size: 18px;
+ width: 100%;
+ padding: 10px 30px;
+ height: 44px;
+
+ &:hover {
+ background-color: #f3f3f3;
+ }
+`;
+
+const SearchResults = styled.ul`
+ position: relative;
+ width: 100%;
+ padding-left: 0;
+ list-style: none;
+ max-height: calc(100vh - 151px);
+ height: calc(100vh - 151px);
+ overflow: auto;
+ padding-bottom: 5px;
+ box-shadow: 1px 1px 5px 0px rgba(0, 0, 0, 0.25);
+ background-color: white;
+ z-index: 98;
+ &::-webkit-scrollbar {
+ display: none;
+ }
+`;
+
+const SearchResult = styled.li`
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ padding: 10px 10px;
+ text-align: left;
+ min-height: 60px;
+
+ cursor: pointer;
+ &:hover {
+ background-color: #f0f0f0;
+ }
+`;
+
+const StockDiv = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: start;
+ width: 100%;
+`;
+const StockName = styled.div`
+ color: #000;
+ font-size: 16px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: normal;
+ word-break: break-all;
+`;
+
+const RowDiv = styled.div`
+ display: flex;
+ flex-direction: row;
+ color: #8c8c8c;
+ font-size: 12px;
+ font-style: normal;
+ font-weight: 300;
+ line-height: normal;
+`;
+
+const StarImg = styled.img`
+ &:hover {
+ transform: ${(props) =>
+ props.$hover == "hover" ? `scale(1.5)` : "scale(1)"};
+ }
+`;
+
+const StockCode = styled.div`
+ margin-right: 3px;
+`;
+
+const StockIndex = styled.div``;
+
+export default SearchStock;
diff --git a/src/components/invest/left-bar/MyStockList/StockItem.jsx b/src/components/invest/left-bar/MyStockList/StockItem.jsx
new file mode 100644
index 0000000..6d93aca
--- /dev/null
+++ b/src/components/invest/left-bar/MyStockList/StockItem.jsx
@@ -0,0 +1,125 @@
+import React from "react";
+import styled from "styled-components";
+import { getLogoFileName, onErrorImg } from "../../../../util/getLogoFileName";
+import { getCookie } from "../../../../lib/apis/cookie";
+import { useDispatch } from "react-redux";
+import { setClickCompany } from "../../../../store/reducers/Chart/clickCompany";
+
+const StockItem = ({ myFavoriteArr, addToFavorites }) => {
+ const dispatch = useDispatch();
+ const isLogin = !!getCookie("token");
+ return (
+
+ {isLogin &&
+ myFavoriteArr?.map((item, idx) => (
+ {
+ dispatch(setClickCompany(item));
+ }}
+ >
+
+
+
+
+ {item.name}
+
+ {item.code}
+ {item.market}
+
+
+ addToFavorites(item)}
+ src="/icon/FilledStar.svg"
+ alt="filledstar"
+ $hover="hover"
+ />
+
+ ))}
+
+ );
+};
+
+const MyListContainer = styled.div`
+ overflow: auto;
+ height: calc(100vh - 145px);
+ padding-bottom: 5px;
+ background-color: white;
+
+ &::-webkit-scrollbar {
+ display: none;
+ }
+`;
+
+const MyListDiv = styled.div`
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ padding: 10px 10px;
+ text-align: left;
+ min-height: 60px;
+ width: 249px;
+ cursor: pointer;
+ &:hover {
+ background-color: #f0f0f0;
+ }
+`;
+
+const ImgDiv = styled.div`
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: 30px;
+ margin: 0px 10px;
+`;
+
+const StockDiv = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: start;
+ width: 100%;
+`;
+const StockName = styled.div`
+ color: #000;
+ font-size: 16px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: normal;
+ word-break: break-all;
+`;
+
+const RowDiv = styled.div`
+ display: flex;
+ flex-direction: row;
+ color: #8c8c8c;
+ font-size: 12px;
+ font-style: normal;
+ font-weight: 300;
+ line-height: normal;
+`;
+
+const StockCode = styled.div`
+ margin-right: 3px;
+`;
+
+const StockIndex = styled.div``;
+
+const StarImg = styled.img`
+ &:hover {
+ transform: ${(props) =>
+ props.$hover == "hover" ? `scale(1.5)` : "scale(1)"};
+ }
+`;
+
+export default StockItem;
diff --git a/src/components/invest/right-bar/OrderHistory/CancleIcon.jsx b/src/components/invest/right-bar/OrderHistory/CancleIcon.jsx
new file mode 100644
index 0000000..9b84273
--- /dev/null
+++ b/src/components/invest/right-bar/OrderHistory/CancleIcon.jsx
@@ -0,0 +1,24 @@
+const CancleIcon = () => {
+ return (
+
+ ×
+
+ );
+};
+
+export default CancleIcon;
diff --git a/src/components/invest/right-bar/OrderHistory/NewIcon.jsx b/src/components/invest/right-bar/OrderHistory/NewIcon.jsx
new file mode 100644
index 0000000..35eb831
--- /dev/null
+++ b/src/components/invest/right-bar/OrderHistory/NewIcon.jsx
@@ -0,0 +1,25 @@
+const NewIcon = () => {
+ return (
+
+ N
+
+ );
+};
+
+export default NewIcon;
diff --git a/src/components/invest/right-bar/OrderHistory/OrderFilledItem.jsx b/src/components/invest/right-bar/OrderHistory/OrderFilledItem.jsx
new file mode 100644
index 0000000..403bbdb
--- /dev/null
+++ b/src/components/invest/right-bar/OrderHistory/OrderFilledItem.jsx
@@ -0,0 +1,111 @@
+import React, { useState } from "react";
+import { postOrder } from "../../../../lib/apis/feed";
+
+const OrderFilledItem = ({ order, name, userId }) => {
+ const [isHovered, setIsHovered] = useState(false);
+ const [date, time] = order.time.split(" ");
+ const formattedDate = date.replace(/^\d{4}-/, "");
+
+ const handlePostButton = () => {
+ const isConfirmed = window.confirm("주문 내역을 공유하시겠습니까?");
+
+ if (isConfirmed) {
+ postOrder(userId, order.ownedShare, name, order.buyOrSell, order.quantity)
+ .then(() => {
+ window.alert("주문 내역이 공유되었습니다. 피드를 확인해주세요!");
+ })
+ .catch((error) => {
+ console.error("주문 내역 공유 오류:", error);
+ window.alert("주문 내역을 공유하는 동안 오류가 발생했습니다.");
+ });
+ }
+ };
+
+ return (
+ setIsHovered(true)}
+ onMouseLeave={() => setIsHovered(false)}
+ >
+
+
+ {formattedDate}
+
+ {time}
+
+
+
+
+
{name}
+
+
+
+ {order.buyOrSell === "buy" ? "매수" : "매도"}
+
+
{order.price.toLocaleString()}원
+
·
+
{order.quantity}주
+
+
+
+
+
+ {(order.price * order.quantity).toLocaleString()}원
+
+
+
+
+
+ );
+};
+
+export default OrderFilledItem;
diff --git a/src/components/invest/right-bar/OrderHistory/OrderHistoryList.css b/src/components/invest/right-bar/OrderHistory/OrderHistoryList.css
new file mode 100644
index 0000000..db9fd2a
--- /dev/null
+++ b/src/components/invest/right-bar/OrderHistory/OrderHistoryList.css
@@ -0,0 +1,32 @@
+@keyframes spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+.refresh-icon-container {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin-right: 0.9rem;
+}
+
+.spin {
+ animation: spin 0.8s infinite linear;
+}
+
+.scroll-container::-webkit-scrollbar {
+ width: 6px; /* 스크롤바의 너비 조절 */
+}
+
+.scroll-container::-webkit-scrollbar-thumb {
+ background-color: rgba(0, 0, 0, 0.2); /* 스크롤바 색상 조절 */
+ border-radius: 3px; /* 스크롤바 모서리 둥글게 만들기 */
+}
+
+.scroll-container::-webkit-scrollbar-track {
+ background-color: transparent; /* 스크롤바 트랙의 배경색 투명으로 설정 */
+}
diff --git a/src/components/invest/right-bar/OrderHistory/OrderHistoryList.jsx b/src/components/invest/right-bar/OrderHistory/OrderHistoryList.jsx
new file mode 100644
index 0000000..e68e1c3
--- /dev/null
+++ b/src/components/invest/right-bar/OrderHistory/OrderHistoryList.jsx
@@ -0,0 +1,191 @@
+import React, { useState, useEffect, useRef } from "react";
+import { MdRefresh } from "react-icons/md";
+import OrderPendingItem from "./OrderPendingItem";
+import OrderFilledItem from "./OrderFilledItem";
+import { v4 as uuidv4 } from "uuid";
+import { useDispatch, useSelector } from "react-redux";
+// import { getOrderHistory } from "../../../../lib/apis/order";
+import "./OrderHistoryList.css";
+import {
+ getOrderHistory,
+ setUniqueCompletedHistory,
+} from "../../../../store/reducers/User/order";
+
+const OrderHistoryList = () => {
+ // const [completedHistory, setCompletedHistory] = useState([]);
+ // const [reservedHistory, setReservedHistory] = useState([]);
+ const [refreshTime, setRefreshTime] = useState("");
+ const refreshIconContainerRef = useRef(null);
+ const company = useSelector((state) => state.company.data[1]);
+ const user = useSelector((state) => state.user.user);
+ const reservedHistory = useSelector((state) => state.order.reservedHistory);
+ const completedHistory = useSelector((state) => state.order.completedHistory);
+ const uniqueCompletedHistory = useSelector(
+ (state) => state.order.uniqueCompletedHistory
+ );
+ const dispatch = useDispatch();
+ const fetchOrderHistory = async () => {
+ dispatch(getOrderHistory({ code: company.code, userId: user.id }));
+ const currentTime = new Date().toLocaleString("ko-KR", {
+ hour: "numeric",
+ minute: "numeric",
+ second: "numeric",
+ hour12: true, // 오후/오후 표시
+ });
+ setRefreshTime(currentTime);
+ setRefreshTime(currentTime);
+ };
+
+ const handleRefreshIcon = () => {
+ fetchOrderHistory();
+ refreshIconContainerRef.current.classList.add("spin");
+ setTimeout(() => {
+ refreshIconContainerRef.current.classList.remove("spin");
+ }, 800);
+ };
+
+ useEffect(() => {
+ fetchOrderHistory();
+ }, [company, user]);
+
+ useEffect(() => {
+ // 중복된 time 필드 제거
+ const uniqueTimes = new Set();
+ const uniqueCompletedHistory = completedHistory.filter((order) => {
+ if (!uniqueTimes.has(order.time)) {
+ uniqueTimes.add(order.time);
+ return true;
+ }
+ return false;
+ });
+ dispatch(setUniqueCompletedHistory(uniqueCompletedHistory));
+ }, [completedHistory]);
+
+ return (
+ <>
+
+
+ {refreshTime} 기준
+
+
+
+
+
+
+
+ 미체결
+
+ {reservedHistory.length !== 0 ? (
+
+ {reservedHistory.map((order) => (
+
+ ))}
+
+ ) : (
+
+ 미체결 내역이 없습니다.
+
+ )}
+
+
+
+
+ 체결
+
+
+ {uniqueCompletedHistory?.length !== 0 ? (
+
+ {uniqueCompletedHistory?.map((order) => (
+
+ ))}
+
+ ) : (
+
+ 체결 내역이 없습니다.
+
+ )}
+
+ >
+ );
+};
+
+export default OrderHistoryList;
diff --git a/src/components/invest/right-bar/OrderHistory/OrderPendingItem.jsx b/src/components/invest/right-bar/OrderHistory/OrderPendingItem.jsx
new file mode 100644
index 0000000..1feaa53
--- /dev/null
+++ b/src/components/invest/right-bar/OrderHistory/OrderPendingItem.jsx
@@ -0,0 +1,70 @@
+import React, { useState } from "react";
+
+const OrderPendingItem = ({ order, name }) => {
+ const [isHovered, setIsHovered] = useState(false);
+
+ return (
+ setIsHovered(true)}
+ onMouseLeave={() => setIsHovered(false)}
+ >
+
+

+
+
+
+
{name}
+
+
+
+ {order.buyOrSell === "buy" ? "매수" : "매도"}
+
+
{order?.price?.toLocaleString()}원
+
·
+
{order.quantity}주
+
+
+
+
+
+ );
+};
+
+export default OrderPendingItem;
diff --git a/src/components/invest/right-bar/OrderManagement/Button/OrderBuySellButton.jsx b/src/components/invest/right-bar/OrderManagement/Button/OrderBuySellButton.jsx
new file mode 100644
index 0000000..3b722d1
--- /dev/null
+++ b/src/components/invest/right-bar/OrderManagement/Button/OrderBuySellButton.jsx
@@ -0,0 +1,64 @@
+import React from "react";
+import { useSelector } from "react-redux";
+
+const OrderBuySellButton = ({
+ quantity,
+ openModal,
+ openErrorModal,
+ maxQuantity,
+ user,
+}) => {
+ const { selectedTab } = useSelector((state) => state.trading);
+
+ const handleModal = () => {
+ const currentTime = new Date();
+ const currentHour = currentTime.getHours();
+ const currentMinute = currentTime.getMinutes();
+ const quantityAsString = String(quantity).replace(/,/g, "");
+ const maxQuantityAsString = String(maxQuantity).replace(/,/g, "");
+
+ if (!user || Object.keys(user).length === 0) {
+ openErrorModal("로그인을 해주세요.");
+ } else if (
+ currentHour < 9 ||
+ (currentHour === 15 && currentMinute > 30) ||
+ currentHour > 15
+ ) {
+ openErrorModal("현재는 주문 가능한 시간이 아닙니다.");
+ } else if (quantity === 0) {
+ openErrorModal("주문 수량이 없습니다.");
+ } else if (parseInt(quantityAsString) > parseInt(maxQuantityAsString)) {
+ openErrorModal("최대 주문 수량을 초과하였습니다.");
+ } else {
+ openModal();
+ }
+ };
+
+ return (
+
+ );
+};
+
+export default OrderBuySellButton;
diff --git a/src/components/invest/right-bar/OrderManagement/Button/OrderTypeButton.jsx b/src/components/invest/right-bar/OrderManagement/Button/OrderTypeButton.jsx
new file mode 100644
index 0000000..688756c
--- /dev/null
+++ b/src/components/invest/right-bar/OrderManagement/Button/OrderTypeButton.jsx
@@ -0,0 +1,25 @@
+import React, { useState } from "react";
+
+const OrderTypeButton = ({ type, selectedType, onClick }) => {
+ return (
+ onClick(type)}
+ style={{
+ backgroundColor: selectedType === type ? "#FFE3D7" : "#F5F5F5",
+ padding: "0.3rem 0.6rem",
+ borderRadius:
+ type === "지정가" ? "0.3rem 0 0 0.3rem" : "0 0.3rem 0.3rem 0",
+ display: "flex",
+ flexDirection: "row",
+ justifyContent: "center",
+ alignItems: "center",
+ fontSize: "0.9rem",
+ cursor: "pointer",
+ }}
+ >
+ {type}
+
+ );
+};
+
+export default OrderTypeButton;
diff --git a/src/components/invest/right-bar/OrderManagement/Button/TradingInputButton.jsx b/src/components/invest/right-bar/OrderManagement/Button/TradingInputButton.jsx
new file mode 100644
index 0000000..139dc2c
--- /dev/null
+++ b/src/components/invest/right-bar/OrderManagement/Button/TradingInputButton.jsx
@@ -0,0 +1,38 @@
+import React from "react";
+
+const TradingInputButton = ({ onClick, children, borderRadius, disabled }) => {
+ return (
+
+ );
+};
+
+export default TradingInputButton;
diff --git a/src/components/invest/right-bar/OrderManagement/Modal/ErrorOrderModal.jsx b/src/components/invest/right-bar/OrderManagement/Modal/ErrorOrderModal.jsx
new file mode 100644
index 0000000..cd1a33c
--- /dev/null
+++ b/src/components/invest/right-bar/OrderManagement/Modal/ErrorOrderModal.jsx
@@ -0,0 +1,35 @@
+import { Modal, Button } from "react-bootstrap";
+
+const ErrorOrderModal = ({ isOpen, onClose, content }) => {
+ return (
+
+
+ {content}
+
+
+
+ );
+};
+
+export default ErrorOrderModal;
diff --git a/src/components/invest/right-bar/OrderManagement/Modal/OrderModal.jsx b/src/components/invest/right-bar/OrderManagement/Modal/OrderModal.jsx
new file mode 100644
index 0000000..e98f189
--- /dev/null
+++ b/src/components/invest/right-bar/OrderManagement/Modal/OrderModal.jsx
@@ -0,0 +1,207 @@
+import React, { useState, useEffect } from "react";
+import { postOrderStock } from "../../../../../lib/apis/order.jsx";
+import { useDispatch, useSelector } from "react-redux";
+import { joinRoom } from "../../../../../store/webSocket/nowPrice.js";
+import {
+ getLogoFileName,
+ onErrorImg,
+} from "../../../../../util/getLogoFileName.jsx";
+import { setIsNew } from "../../../../../store/reducers/Trading/trading.jsx";
+import { Modal } from "react-bootstrap";
+
+const OrderModal = ({ isOpen, onClose, userOrderType, price, quantity }) => {
+ const company = useSelector((state) => state.company.data[1]);
+ const user = useSelector((state) => state.user.user);
+ const dispatch = useDispatch();
+
+ const handleOrderConfirmation = async () => {
+ await postOrderStock(
+ company.code,
+ userOrderType === "매수" ? "buy" : "sell",
+ price,
+ quantity,
+ user.id
+ );
+ dispatch(setIsNew(true));
+ joinRoom(company.code);
+ onClose();
+ };
+
+ return (
+
+
+
+

+
+
{company.name}
+
+ {userOrderType}
+
+
+
+
+
+
+
주문단가
+
+
+ {price !== null ? parseInt(price).toLocaleString() : ""}
+
+
원
+
+
+
+
+
+
+
+
+
총 주문금액
+
+
{(price * quantity).toLocaleString()}
+
원
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default OrderModal;
diff --git a/src/components/invest/right-bar/OrderManagement/OrderBook.css b/src/components/invest/right-bar/OrderManagement/OrderBook.css
new file mode 100644
index 0000000..561b3db
--- /dev/null
+++ b/src/components/invest/right-bar/OrderManagement/OrderBook.css
@@ -0,0 +1,27 @@
+.selected-type-container {
+ position: relative; /* 상대적 위치 설정 */
+ display: inline-block; /* 선택 상자와 텍스트를 인라인 요소로 표시 */
+}
+
+.selected-type-animation {
+ position: absolute; /* 절대적 위치 설정 */
+ top: -25px; /* 텍스트가 상자 위로 올라가도록 설정 */
+ left: 0;
+ width: 50px; /* 선택 상자의 너비에 맞게 조정하세요 */
+ height: 25px; /* 선택 상자의 높이에 맞게 조정하세요 */
+ background-color: rgba(255, 227, 215, 0.5); /* 선택 상자의 투명도 추가 */
+ transition: left 0.5s ease;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ font-size: 0.9rem; /* 선택 상자의 텍스트 크기 설정 */
+}
+
+.selected-type-animation.limit {
+ left: 54px; /* 지정가 선택 시 위치 */
+ border-radius: 0.3rem;
+}
+
+.selected-type-animation.market {
+ left: 100px; /* 시장가 선택 시 위치 */
+}
diff --git a/src/components/invest/right-bar/OrderManagement/OrderBook.jsx b/src/components/invest/right-bar/OrderManagement/OrderBook.jsx
new file mode 100644
index 0000000..bc7e210
--- /dev/null
+++ b/src/components/invest/right-bar/OrderManagement/OrderBook.jsx
@@ -0,0 +1,305 @@
+import React, { useState, useEffect } from "react";
+import OrderModal from "./Modal/OrderModal";
+import ErrorOrderModal from "./Modal/ErrorOrderModal";
+import OrderInput from "./OrderInput";
+import OrderTypeButton from "./Button/OrderTypeButton";
+import OrderBuySellButton from "./Button/OrderBuySellButton";
+import { useDispatch, useSelector } from "react-redux";
+import { useWebSocket } from "../../../../lib/hooks/useWebSocket";
+import {
+ setSelectedPrice,
+ setOrderType,
+ setSelectedQuantity,
+ increaseSelectedQuantity,
+ decreaseSelectedQuantity,
+} from "../../../../store/reducers/Trading/trading";
+import { getBalance, getHoldingQuantity } from "../../../../lib/apis/holding";
+
+const OrderBook = () => {
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ const [isErrorModalOpen, setIsErrorModalOpen] = useState(false);
+ const [balance, setBalance] = useState(0); // 잔고
+ const [maxQuantity, setMaxQuantity] = useState(0);
+ const [selectedType, setSelectedType] = useState("지정가"); // 지정가, 시장가
+ const { askPrice, nowPrice, ready } = useWebSocket();
+ const [content, setContent] = useState("");
+
+ const dispatch = useDispatch();
+ const { selectedPrice, selectedTab, orderType, selectedQuantity } =
+ useSelector((state) => state.trading);
+ const user = useSelector((state) => state.user.user);
+ const company = useSelector((state) => state.company.data[1]);
+
+ const openModal = () => setIsModalOpen(true);
+ const closeModal = () => setIsModalOpen(false);
+ const openErrorModal = (content) => {
+ setContent(content);
+ setIsErrorModalOpen(true);
+ };
+ const closeErrorModal = () => setIsErrorModalOpen(false);
+
+ const handleTypeChange = (type) => {
+ setSelectedType(type);
+ if (type === "시장가") {
+ dispatch(setSelectedQuantity(0));
+ dispatch(setSelectedPrice(0));
+ dispatch(setOrderType("시장가"));
+ } else {
+ dispatch(setSelectedQuantity(0));
+ dispatch(setSelectedPrice(nowPrice?.message?.close));
+ dispatch(setOrderType("지정가"));
+ }
+ };
+
+ const increasePrice = () => {
+ const priceDiff =
+ parseInt(askPrice?.message?.sellPrice[1]) -
+ parseInt(askPrice?.message?.sellPrice[0]);
+ dispatch(setSelectedPrice(parseInt(selectedPrice) + priceDiff));
+ };
+
+ const decreasePrice = () => {
+ const priceDiff =
+ parseFloat(askPrice.message.sellPrice[1]) -
+ parseFloat(askPrice.message.sellPrice[0]);
+ const newPrice = selectedPrice - priceDiff;
+ dispatch(setSelectedPrice(newPrice < 0 ? 0 : newPrice));
+ };
+
+ const increaseQuantity = () => {
+ dispatch(increaseSelectedQuantity());
+ };
+
+ const decreaseQuantity = () => {
+ dispatch(decreaseSelectedQuantity());
+ };
+
+ useEffect(() => {
+ const calculateMaxQuantity = async () => {
+ if (selectedTab === "매수") {
+ if (selectedType === "지정가") {
+ // 지정가
+ return Math.floor(balance / selectedPrice).toLocaleString();
+ } else {
+ // 시장가
+ return Math.floor(
+ parseInt(balance) / askPrice?.message?.sellPrice[0]
+ ).toLocaleString();
+ }
+ } else {
+ if (!user || Object.keys(user).length === 0) {
+ return "0";
+ } else {
+ const quantity = await getHoldingQuantity(user.id, company.code);
+ return parseInt(quantity).toLocaleString();
+ }
+ }
+ };
+
+ calculateMaxQuantity()
+ .then((result) => {
+ setMaxQuantity(result);
+ })
+ .catch((error) => {
+ console.error("Error calculating max quantity:", error);
+ });
+ }, [selectedTab, selectedType, balance, askPrice, user, company]);
+
+ useEffect(() => {
+ setSelectedType("지정가");
+ }, [user]);
+
+ useEffect(() => {
+ dispatch(setSelectedPrice(nowPrice?.message?.close));
+ }, [ready]);
+
+ useEffect(() => {
+ const fetchBalance = async () => {
+ if (user && Object.keys(user).length !== 0) {
+ const balance = await getBalance(user.id);
+ setBalance(parseInt(balance));
+ } else {
+ setBalance(0);
+ }
+ };
+
+ fetchBalance();
+ }, [isModalOpen, user]);
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
가격
+
+ dispatch(
+ setSelectedPrice(
+ e.target.value === "" ? 0 : parseFloat(e.target.value)
+ )
+ )
+ }
+ increase={increasePrice}
+ decrease={decreasePrice}
+ disabled={orderType === "시장가" ? true : false}
+ />
+
+
+
+
+
수량
+
+ 최대
+
+ {maxQuantity}
+
+ 주
+
+
+
+ dispatch(
+ setSelectedQuantity(
+ e.target.value === "" ? 0 : parseFloat(e.target.value)
+ )
+ )
+ }
+ increase={increaseQuantity}
+ decrease={decreaseQuantity}
+ />
+
+
+
+ {selectedTab === "매수" ? (
+
+
최대
+
+ {balance.toLocaleString()}
+
+
원
+
+ ) : null}
+
+
+ 주문총액
+
+
+
{(selectedPrice * selectedQuantity).toLocaleString()}
+
원
+
+
+
+
+
+
+ >
+ );
+};
+
+export default OrderBook;
diff --git a/src/components/invest/right-bar/OrderManagement/OrderInput.jsx b/src/components/invest/right-bar/OrderManagement/OrderInput.jsx
new file mode 100644
index 0000000..a91850d
--- /dev/null
+++ b/src/components/invest/right-bar/OrderManagement/OrderInput.jsx
@@ -0,0 +1,86 @@
+import React from "react";
+import TradingInputButton from "./Button/TradingInputButton";
+
+const OrderInput = ({
+ label,
+ value,
+ onChange,
+ increase,
+ decrease,
+ disabled,
+}) => {
+ const handleIncrease = () => {
+ if (!disabled) {
+ increase();
+ }
+ };
+
+ const handleDecrease = () => {
+ if (!disabled) {
+ decrease();
+ }
+ };
+ return (
+
+
+
+ {label}
+
+
+
+ ▲
+
+
+ ▼
+
+
+
+ );
+};
+
+export default OrderInput;
diff --git a/src/components/invest/right-bar/OrderManagement/PriceBook.jsx b/src/components/invest/right-bar/OrderManagement/PriceBook.jsx
new file mode 100644
index 0000000..97b318c
--- /dev/null
+++ b/src/components/invest/right-bar/OrderManagement/PriceBook.jsx
@@ -0,0 +1,82 @@
+import React, { useState, useEffect, useRef } from "react";
+import { v4 as uuidv4 } from "uuid";
+import { useWebSocket } from "../../../../lib/hooks/useWebSocket";
+import PriceItem from "./PriceItem";
+import {
+ setScrollPosition,
+ setSelectedPrice,
+} from "../../../../store/reducers/Trading/trading";
+import { useDispatch, useSelector } from "react-redux";
+
+const PriceBook = () => {
+ const containerRef = useRef(null);
+ const dispatch = useDispatch();
+ const { scrollPosition, disabledPriceInput, selectedPrice } = useSelector(
+ (state) => state.trading
+ );
+ const { askPrice, nowPrice } = useWebSocket();
+
+ useEffect(() => {
+ if (containerRef.current) {
+ containerRef.current.scrollTop = scrollPosition;
+ }
+ }, [scrollPosition, askPrice]);
+
+ const handleScroll = (e) => {
+ dispatch(setScrollPosition(e.target.scrollTop));
+ };
+
+ const handlePriceSelect = (price) => {
+ if (!disabledPriceInput) {
+ dispatch(setSelectedPrice(price));
+ }
+ };
+
+ return (
+
+
+ {askPrice?.message?.sellPrice
+ .map((price, index) => (
+
+ ))
+ .reverse()}
+
+
+
+ {askPrice?.message?.buyPrice.map((price, index) => (
+
+ ))}
+
+
+ );
+};
+
+export default PriceBook;
diff --git a/src/components/invest/right-bar/OrderManagement/PriceItem.jsx b/src/components/invest/right-bar/OrderManagement/PriceItem.jsx
new file mode 100644
index 0000000..04aaae7
--- /dev/null
+++ b/src/components/invest/right-bar/OrderManagement/PriceItem.jsx
@@ -0,0 +1,58 @@
+import React, { useEffect, useState } from "react";
+
+const PriceItem = ({
+ price,
+ amount,
+ backgroundColor,
+ textColor,
+ nowPrice,
+ onPriceSelect,
+ selectedPrice,
+}) => {
+ const isSelected = price.toString() === selectedPrice?.toString();
+
+ return (
+ {
+ onPriceSelect(price);
+ }}
+ style={{
+ width: "150px",
+ height: "2.3rem",
+ backgroundColor: backgroundColor,
+ marginBottom: "0.15rem",
+ display: "flex",
+ flexDirection: "row",
+ alignItems: "center",
+ justifyContent: "space-between",
+ padding: "0 1rem",
+ border: price === nowPrice ? "1px solid black" : "none",
+ position: "relative",
+ cursor: "pointer",
+ }}
+ >
+ {isSelected && (
+
+ )}
+ {price !== "" && (
+ <>
+
{price.toLocaleString()}
+
+ {amount.toLocaleString()}
+
+ >
+ )}
+
+ );
+};
+
+export default PriceItem;
diff --git a/src/components/side-bar/PopularStock.jsx b/src/components/side-bar/PopularStock.jsx
new file mode 100644
index 0000000..148f4eb
--- /dev/null
+++ b/src/components/side-bar/PopularStock.jsx
@@ -0,0 +1,46 @@
+import React from "react";
+import styled from "styled-components";
+import { getLogoFileName } from "~/util/getLogoFileName";
+import { onErrorImg } from "~/util/getLogoFileName";
+
+export default function PopularStock({ idx, item }) {
+ return (
+
+ {idx + 1}위
+
+
+ {item.stbd_nm}
+
+
+ );
+}
+
+const RankDiv = styled.div`
+ background-color: ${(props) =>
+ props.num % 2 ? "rgba(255, 227, 215, 0.4)" : "rgba(0, 0, 0, 0.03)"};
+ display: flex;
+ align-items: center;
+ padding: 13px 0 13px 20px;
+ gap: 10px;
+ box-shadow: 3px 3px 5px rgba(0, 0, 0, 0.05);
+ border-radius: 10px;
+ margin-bottom: 8px;
+`;
+
+const RankFont = styled.span`
+ font-size: 16px;
+ font-weight: 700;
+`;
+
+const CompanyLogo = styled.img`
+ width: 35px;
+ height: 35px;
+ border-radius: 999px;
+ margin-right: 10px;
+`;
diff --git a/src/components/side-bar/SideNavbar.jsx b/src/components/side-bar/SideNavbar.jsx
new file mode 100644
index 0000000..381b881
--- /dev/null
+++ b/src/components/side-bar/SideNavbar.jsx
@@ -0,0 +1,129 @@
+import React from "react";
+import { Link, useLocation } from "react-router-dom";
+import styled from "styled-components";
+
+const SideNavbar = () => {
+ const location = useLocation();
+
+ return (
+
+ );
+};
+
+const Nav = styled.nav`
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+ /* z-index: 1000; */
+ height: 62px;
+ border-top: 1px solid #e2e2e2;
+ img {
+ width: 22px;
+ }
+
+ ul {
+ list-style: none;
+ display: flex;
+ justify-content: space-around;
+ width: 100%;
+ padding-left: 0px;
+ margin-bottom: 0px;
+ }
+
+ li {
+ flex: 6;
+ }
+
+ a {
+ text-decoration: none;
+ color: #4e5968;
+ font-size: 12px;
+
+ &.active {
+ color: #ff8049;
+ }
+ }
+`;
+
+export default SideNavbar;
diff --git a/src/components/side-bar/webSocketTest.jsx b/src/components/side-bar/webSocketTest.jsx
new file mode 100644
index 0000000..9e48920
--- /dev/null
+++ b/src/components/side-bar/webSocketTest.jsx
@@ -0,0 +1,80 @@
+import React, { useState, useEffect, useRef } from "react";
+import {
+ joinRoom,
+ leaveRoom,
+ subscribeAskPrice,
+ subscribeNowPrice,
+} from "../../store/webSocket/nowPrice"; // 경로는 실제 프로젝트 구조에 따라 조정해주세요.
+
+const WebSocketTest = () => {
+ const containerRef = useRef(null);
+ const [nowPrice, setNowPrice] = useState(null);
+ const [askPrice, setAskPrice] = useState(null);
+ const [stockCode, setStockCode] = useState("");
+
+ useEffect(() => {
+ const settingNowPrice = subscribeNowPrice((nowPriceMessage) => {
+ setNowPrice(nowPriceMessage);
+ });
+
+ const settingAskPrice = subscribeAskPrice((askPriceMessage) => {
+ setAskPrice(askPriceMessage);
+ });
+
+ return () => {
+ settingNowPrice();
+ settingAskPrice();
+ };
+ }, [nowPrice, askPrice]);
+
+ useEffect(() => {
+ const adjustScroll = () => {
+ const container = containerRef.current;
+ if (container) {
+ const scrollTop = (container.scrollHeight - container.clientHeight) / 2;
+ container.scrollTop = scrollTop;
+ }
+ };
+
+ adjustScroll(); // 스크롤 조정
+ }, []);
+
+ // joinRoom 및 leaveRoom 함수는 여기에서 직접 호출합니다.
+
+ return (
+
+
setStockCode(e.target.value)}
+ placeholder="Enter stock code"
+ />
+
+
+
+
Real-time Data:
+ {nowPrice ? (
+
{JSON.stringify(nowPrice, null, 2)}
+ ) : (
+
데이터 없음
+ )}
+
+
+
Real-time Data2:
+ {askPrice ? (
+
{JSON.stringify(askPrice, null, 2)}
+ ) : (
+
데이터 없음
+ )}
+
+
+ );
+};
+
+export default WebSocketTest;
diff --git a/src/index.css b/src/index.css
new file mode 100644
index 0000000..b7a38ac
--- /dev/null
+++ b/src/index.css
@@ -0,0 +1,16 @@
+@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
+
+#root {
+ font-family: "Pretendard Variable";
+}
+
+.modal {
+ font-family: "Pretendard Variable";
+}
+
+body {
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
diff --git a/src/lib/apis/api.jsx b/src/lib/apis/api.jsx
new file mode 100644
index 0000000..4958496
--- /dev/null
+++ b/src/lib/apis/api.jsx
@@ -0,0 +1,39 @@
+import axios from "axios";
+import { getCookie } from "./cookie";
+
+export const BASE_URL = "/api";
+
+export const chartInstance = axios.create({
+ baseURL: BASE_URL + '/stockPrice',
+});
+
+export const subChartInstance = axios.create({
+ baseURL: BASE_URL + "/subChart",
+});
+
+export const shinhanInstance = axios.create({
+ baseURL: BASE_URL + "/shinhanInfo",
+});
+
+export const marketCloseInstance = axios.create({
+ baseURL: BASE_URL + "/marketClose",
+});
+
+export const baseInstance = axios.create({
+ baseURL: BASE_URL,
+});
+
+export const baseUserInstance = axios.create({
+ baseURL: BASE_URL,
+ headers: {
+ Authorization: `Bearer ${getCookie("token")}`,
+ },
+});
+
+export const formdataInstance = axios.create({
+ baseURL: BASE_URL,
+ headers: {
+ "Content-Type": "multipart/form-data",
+ Authorization: `Bearer ${getCookie("token")}`,
+ },
+});
diff --git a/src/lib/apis/chart.jsx b/src/lib/apis/chart.jsx
new file mode 100644
index 0000000..c425ebd
--- /dev/null
+++ b/src/lib/apis/chart.jsx
@@ -0,0 +1,121 @@
+import { chartInstance, subChartInstance } from './api';
+
+export async function getChartData(data) {
+ return await chartInstance.post('/', data)
+}
+
+export async function getMinuteData(data) {
+ return await chartInstance.post('/minute', data)
+}
+
+export async function getkospiKosdaq(data) {
+ return await chartInstance.get('/kospiKosdaq')
+}
+
+export async function getSMA(data) {
+ return await subChartInstance.post('/SMA', data)
+}
+
+export async function getWMA(data) {
+ return await subChartInstance.post('/WMA', data)
+}
+
+export async function getEMA(data) {
+ return await subChartInstance.post('/EMA', data)
+}
+
+export async function getBBANDS(data) {
+ return await subChartInstance.post('/BBANDS', data)
+}
+
+export async function getSAR(data) {
+ return await subChartInstance.post('/SAR', data)
+}
+
+export async function getMACD(data) {
+ return await subChartInstance.post('/MACD', data)
+}
+
+export async function getSTOCHF(data) {
+ return await subChartInstance.post('/STOCHF', data)
+}
+
+export async function getSTOCH(data) {
+ return await subChartInstance.post('/STOCH', data)
+}
+
+export async function getRSI(data) {
+ return await subChartInstance.post('/RSI', data)
+}
+
+export async function getCCI(data) {
+ return await subChartInstance.post('/CCI', data)
+}
+
+export async function getMOM(data) {
+ return await subChartInstance.post('/MOM', data)
+}
+
+export async function getROC(data) {
+ return await subChartInstance.post('/ROC', data)
+}
+
+export async function getAD(data) {
+ return await subChartInstance.post('/AD', data)
+}
+
+export async function getATR(data) {
+ return await subChartInstance.post('/ATR', data)
+}
+
+export async function getMFI(data) {
+ return await subChartInstance.post('/MFI', data)
+}
+
+export async function getOBV(data) {
+ return await subChartInstance.post('/OBV', data)
+}
+
+export async function getADOSC(data) {
+ return await subChartInstance.post('/ADOSC', data)
+}
+
+export async function getTRIX(data) {
+ return await subChartInstance.post('/TRIX', data)
+}
+
+export async function getWILLR(data) {
+ return await subChartInstance.post('/WILLR', data)
+}
+
+export async function getDX(data) {
+ return await subChartInstance.post('/DX', data)
+}
+
+export async function getADX(data) {
+ return await subChartInstance.post('/ADX', data)
+}
+
+export async function getADXR(data) {
+ return await subChartInstance.post('/ADXR', data)
+}
+
+export async function getAROON(data) {
+ return await subChartInstance.post('/AROON', data)
+}
+
+export async function getAROONOSC(data) {
+ return await subChartInstance.post('/AROONOSC', data)
+}
+
+export async function getSTOCHRSI(data) {
+ return await subChartInstance.post('/STOCHRSI', data)
+}
+
+export async function getULTOSC(data) {
+ return await subChartInstance.post('/ULTOSC', data)
+}
+
+export async function getPPO(data) {
+ return await subChartInstance.post('/PPO', data)
+}
\ No newline at end of file
diff --git a/src/lib/apis/chatBot.jsx b/src/lib/apis/chatBot.jsx
new file mode 100644
index 0000000..cf2c3e6
--- /dev/null
+++ b/src/lib/apis/chatBot.jsx
@@ -0,0 +1,12 @@
+import { baseInstance } from "./api";
+
+export async function fetchChatBot(input) {
+ const url = `/chatBot`;
+ try {
+ const response = await baseInstance.post(url, { prompt: input });
+ const data = response.data;
+ return data;
+ } catch (err) {
+ console.error(err);
+ }
+}
diff --git a/src/lib/apis/comment.jsx b/src/lib/apis/comment.jsx
new file mode 100644
index 0000000..f1fdab2
--- /dev/null
+++ b/src/lib/apis/comment.jsx
@@ -0,0 +1,45 @@
+import { baseUserInstance } from "./api";
+
+export const fetchComments = async (feedId) => {
+ const baseUrl = `/comment/${feedId}`;
+ try {
+ const response = await baseUserInstance.get(baseUrl);
+ const data = response.data;
+ return data;
+ } catch (err) {
+ console.error(err);
+ }
+};
+
+export const postComment = async (feedId, content) => {
+ const baseUrl = `/comment/${feedId}`;
+ try {
+ const response = await baseUserInstance.post(baseUrl, { content });
+ const data = response.data;
+ return data;
+ } catch (err) {
+ console.error(err);
+ }
+};
+
+export const putComment = async (commentId, content) => {
+ const baseUrl = `/comment/${commentId}`;
+ try {
+ const response = await baseUserInstance.put(baseUrl, { content });
+ const data = response.data;
+ return data;
+ } catch (err) {
+ console.error(err);
+ }
+};
+
+export const deleteComment = async (commentId) => {
+ const baseUrl = `/comment/${commentId}`;
+ try {
+ const response = await baseUserInstance.delete(baseUrl);
+ const data = response.data;
+ return data;
+ } catch (err) {
+ console.error(err);
+ }
+};
diff --git a/src/lib/apis/cookie.jsx b/src/lib/apis/cookie.jsx
new file mode 100644
index 0000000..21053fe
--- /dev/null
+++ b/src/lib/apis/cookie.jsx
@@ -0,0 +1,17 @@
+import { Cookies } from "react-cookie";
+
+const cookies = new Cookies();
+
+export const setCookie = (name, value, option) => {
+ const tokenValue = value.replace("Bearer ", "");
+ return cookies.set(name, tokenValue, { ...option });
+};
+
+export const getCookie = (name) => {
+ const cookieValue = cookies.get(name);
+ return cookieValue;
+};
+
+export const removeCookie = (name) => {
+ return cookies.remove(name);
+};
diff --git a/src/lib/apis/feed.jsx b/src/lib/apis/feed.jsx
new file mode 100644
index 0000000..64dc8a7
--- /dev/null
+++ b/src/lib/apis/feed.jsx
@@ -0,0 +1,155 @@
+import { baseUserInstance, formdataInstance } from "./api";
+
+export const fetchAFeed = async (feedId) => {
+ const baseUrl = `/feed/${feedId}`;
+ try {
+ const response = await baseUserInstance.get(baseUrl);
+ const data = response.data;
+ return data;
+ } catch (err) {
+ console.error(err);
+ }
+};
+
+export const fetchMyFeed = async (userId, page) => {
+ const baseUrl = `/feed/user/${userId}?page=${page}&limit=10`;
+ try {
+ const response = await baseUserInstance.get(baseUrl);
+ const data = response.data;
+ return data;
+ } catch (err) {
+ console.error(err);
+ }
+};
+
+export const fetchOtherFeed = async (userId, page) => {
+ const baseUrl = `/feed/anotherUser/${userId}?page=${page}&limit=100`;
+ try {
+ const response = await baseUserInstance.get(baseUrl);
+ const data = response.data;
+ return data;
+ } catch (err) {
+ console.error(err);
+ }
+};
+
+export const fetchAllFeed = async (page) => {
+ const baseUrl = `/feed?page=${page}&limit=10`;
+ try {
+ const response = await baseUserInstance.get(baseUrl);
+ const data = response.data;
+ return data;
+ } catch (err) {
+ console.error(err);
+ }
+};
+
+export const postBoardFeed = async (formData) => {
+ const baseUrl = "/feed";
+ try {
+ const response = await formdataInstance.post(baseUrl, formData);
+ const data = response.data;
+ return data;
+ } catch (err) {
+ console.error(err);
+ }
+};
+
+export const deleteFeed = async (feedId) => {
+ const baseUrl = `/feed/${feedId}`;
+ try {
+ const response = await baseUserInstance.delete(baseUrl);
+ const data = response.data;
+ return data;
+ } catch (err) {
+ console.error(err);
+ }
+};
+
+export const postVoteFeed = async (body) => {
+ const baseUrl = "/feed/vote";
+ try {
+ const response = await baseUserInstance.post(baseUrl, {
+ body,
+ });
+ const data = response.data;
+ return data;
+ } catch (err) {
+ console.error(err);
+ }
+};
+
+export const postVote = async (feedId, voteResult) => {
+ const baseUrl = "/feed/voted";
+ try {
+ const response = await baseUserInstance.post(baseUrl, {
+ feedId,
+ voteResult,
+ });
+ const data = response.data;
+ return data;
+ } catch (err) {
+ console.error(err);
+ }
+};
+
+export const postLike = async (feedId) => {
+ const baseUrl = `/feed/${feedId}/like`;
+ try {
+ const response = await baseUserInstance.post(baseUrl);
+ const data = response.data;
+ return data;
+ } catch (err) {
+ console.error(err);
+ }
+};
+
+export const postUnlike = async (feedId) => {
+ const baseUrl = `/feed/${feedId}/unlike`;
+ try {
+ const response = await baseUserInstance.post(baseUrl);
+ const data = response.data;
+ return data;
+ } catch (err) {
+ console.error(err);
+ }
+};
+
+export const fetchMyFeedCount = async (userId) => {
+ const baseUrl = `/feed/user/${userId}/post-count`;
+ try {
+ const response = await baseUserInstance.get(baseUrl);
+ const data = response.data;
+ return data;
+ } catch (err) {
+ console.error(err);
+ }
+};
+
+export const postOrder = async (userId, code, name, buyOrSell, quantity) => {
+ const baseUrl = `/feed/order/${userId}`;
+ try {
+ const response = await baseUserInstance.post(baseUrl, {
+ code,
+ name,
+ buyOrSell,
+ quantity,
+ });
+ return response.data;
+ } catch (err) {
+ console.error(err);
+ }
+};
+
+export const postMyProfit = async (profit) => {
+ const baseUrl = "/feed/profit";
+ try {
+ const response = await baseUserInstance.post(baseUrl, {
+ profit,
+ });
+ const data = response.data;
+ return data;
+ } catch (err) {
+ console.error(err);
+ }
+};
diff --git a/src/lib/apis/friend.jsx b/src/lib/apis/friend.jsx
new file mode 100644
index 0000000..c29f712
--- /dev/null
+++ b/src/lib/apis/friend.jsx
@@ -0,0 +1,111 @@
+import { baseUserInstance, baseInstance } from "./api";
+
+export const fetchFriends = async () => {
+ const baseUrl = `/friend`;
+ try {
+ const response = await baseUserInstance.get(baseUrl);
+ const data = response.data;
+ return data;
+ } catch (err) {
+ console.error(err);
+ }
+};
+
+export const fetchPendingFriends = async () => {
+ const baseUrl = `/friend/pending`;
+ try {
+ const response = await baseUserInstance.get(baseUrl);
+ const data = response.data;
+ return data;
+ } catch (err) {
+ console.error(err);
+ }
+};
+
+export const fetchFriendsState = async (friendId) => {
+ const baseUrl = `/friend/state/${friendId}`;
+ try {
+ const response = await baseUserInstance.get(baseUrl);
+ const data = response.data;
+ return data;
+ } catch (err) {
+ console.error(err);
+ }
+};
+
+export const fetchFriendCount = async (userId) => {
+ const baseUrl = `/friend/friend-count/${userId}`;
+ try {
+ const response = await baseUserInstance.get(baseUrl);
+ const data = response.data;
+ return data;
+ } catch (err) {
+ console.error(err);
+ }
+};
+
+export const postFriendRequest = async (friendId) => {
+ const baseUrl = "/friend/request";
+ try {
+ const response = await baseUserInstance.post(baseUrl, { friendId });
+ const data = response.data;
+ return data;
+ } catch (err) {
+ console.error(err);
+ }
+};
+
+export const postFriendAccept = async (friendId) => {
+ const baseUrl = "/friend/accept";
+ try {
+ const response = await baseUserInstance.post(baseUrl, { friendId });
+ const data = response.data;
+ return data;
+ } catch (err) {
+ console.error(err);
+ }
+};
+
+export const deleteFriendRequest = async (friendId) => {
+ const baseUrl = `/friend/reject/${friendId}`;
+ try {
+ const response = await baseUserInstance.delete(baseUrl);
+ const data = response.data;
+ return data;
+ } catch (err) {
+ console.error(err);
+ }
+};
+
+export const deleteFriend = async (friendId) => {
+ const baseUrl = `/friend/${friendId}`;
+ try {
+ const response = await baseUserInstance.delete(baseUrl);
+ const data = response.data;
+ return data;
+ } catch (err) {
+ console.error(err);
+ }
+};
+
+export const deleteMyRequest = async (friendId) => {
+ const baseUrl = `/friend/request/${friendId}`;
+ try {
+ const response = await baseUserInstance.delete(baseUrl);
+ const data = response.data;
+ return data;
+ } catch (err) {
+ console.error(err);
+ }
+};
+
+export const postSearchFriend = async (nickname) => {
+ const baseUrl = "/friend/search";
+ try {
+ const response = await baseInstance.post(baseUrl, { nickname });
+ const data = response.data;
+ return data;
+ } catch (err) {
+ console.error(err);
+ }
+};
diff --git a/src/lib/apis/holding.jsx b/src/lib/apis/holding.jsx
new file mode 100644
index 0000000..57de252
--- /dev/null
+++ b/src/lib/apis/holding.jsx
@@ -0,0 +1,21 @@
+import { baseUserInstance } from "./api";
+
+export async function getBalance(userId) {
+ try {
+ const response = await baseUserInstance.get(`/holding/balance/${userId}`);
+ return response.data;
+ } catch (err) {
+ console.error(err);
+ }
+}
+
+export async function getHoldingQuantity(userId, code) {
+ try {
+ const response = await baseUserInstance.get(
+ `/holding/quantity/${userId}/${code}`
+ );
+ return response.data;
+ } catch (err) {
+ console.error(err);
+ }
+}
diff --git a/src/lib/apis/hot.jsx b/src/lib/apis/hot.jsx
new file mode 100644
index 0000000..0762028
--- /dev/null
+++ b/src/lib/apis/hot.jsx
@@ -0,0 +1,9 @@
+import { shinhanInstance } from './api';
+
+export async function getPopularStock() {
+ return await shinhanInstance.get('/popularStock')
+}
+
+export async function getHotStock(paramId) {
+ return await shinhanInstance.get(`/hotStock/${paramId}`)
+}
\ No newline at end of file
diff --git a/src/lib/apis/marketClose.jsx b/src/lib/apis/marketClose.jsx
new file mode 100644
index 0000000..e406827
--- /dev/null
+++ b/src/lib/apis/marketClose.jsx
@@ -0,0 +1,10 @@
+import { marketCloseInstance } from "./api";
+
+export async function getMarketClosePrice(code) {
+ try {
+ const response = await marketCloseInstance.get(`/${code}`);
+ return response;
+ } catch (err) {
+ console.error(err);
+ }
+}
diff --git a/src/lib/apis/order.jsx b/src/lib/apis/order.jsx
new file mode 100644
index 0000000..58aed1c
--- /dev/null
+++ b/src/lib/apis/order.jsx
@@ -0,0 +1,39 @@
+import { baseUserInstance } from "./api";
+
+export async function postOrderStock(code, buyOrSell, price, quantity, userId) {
+ try {
+ const response = await baseUserInstance.post(`/order/buyOrSell/${userId}`, {
+ ownedShare: code,
+ buyOrSell,
+ price,
+ quantity,
+ });
+ const data = response.data;
+ return data;
+ } catch (err) {
+ console.error(err);
+ }
+}
+
+export async function getOrderHistory(code, userId) {
+ try {
+ const response = await baseUserInstance.get(
+ `/order/myHistory/${userId}/${code}`
+ );
+ const data = response.data;
+ return data;
+ } catch (err) {
+ console.error(err);
+ }
+}
+
+export const fetchMyOrder = async () => {
+ const baseUrl = `/order/myOrder`;
+ try {
+ const response = await baseUserInstance.get(baseUrl);
+ const data = response.data;
+ return data;
+ } catch (err) {
+ console.error(err);
+ }
+};
diff --git a/src/lib/apis/search.jsx b/src/lib/apis/search.jsx
new file mode 100644
index 0000000..70ba47d
--- /dev/null
+++ b/src/lib/apis/search.jsx
@@ -0,0 +1,64 @@
+import { baseInstance, baseUserInstance } from "./api";
+
+export const postSearch = async (searchQuery) => {
+ const baseUrl = "/stockCode/search";
+ try {
+ const response = await baseInstance.post(baseUrl, {
+ searchQuery,
+ });
+ const data = response.data;
+ return data;
+ } catch (err) {
+ console.error(err);
+ }
+};
+
+export const postSearchUser = async (searchQuery, userId) => {
+ const baseUrl = "/stockCode/userSearch";
+ try {
+ const response = await baseInstance.post(baseUrl, {
+ searchQuery,
+ userId,
+ });
+ const data = response.data;
+ return data;
+ } catch (err) {
+ console.error(err);
+ }
+};
+
+export const postLikeStock = async (likeStock, userId) => {
+ const baseUrl = "/stockCode/likeStock";
+ try {
+ const response = await baseInstance.post(baseUrl, {
+ likeStock,
+ userId,
+ });
+ const data = response.data.likeStock;
+ return data;
+ } catch (err) {
+ console.error(err);
+ }
+};
+
+export const fetchLikeStock = async (userId) => {
+ const baseUrl = `/stockCode/likeStock/${userId}`;
+ try {
+ const response = await baseInstance.get(baseUrl);
+ const data = response.data;
+ return data;
+ } catch (err) {
+ console.error(err);
+ }
+};
+
+export const fetchLikeStockArr = async (userId) => {
+ const baseUrl = `/stockCode/likeStockArr/${userId}`;
+ try {
+ const response = await baseInstance.get(baseUrl);
+ const data = response.data;
+ return data;
+ } catch (err) {
+ console.error(err);
+ }
+};
diff --git a/src/lib/apis/strategy.jsx b/src/lib/apis/strategy.jsx
new file mode 100644
index 0000000..b5d907a
--- /dev/null
+++ b/src/lib/apis/strategy.jsx
@@ -0,0 +1,5 @@
+import { shinhanInstance } from './api';
+
+export async function getStrategy() {
+ return await shinhanInstance.get('/strategy')
+}
diff --git a/src/lib/apis/user.jsx b/src/lib/apis/user.jsx
new file mode 100644
index 0000000..6aed77e
--- /dev/null
+++ b/src/lib/apis/user.jsx
@@ -0,0 +1,52 @@
+import { baseInstance, baseUserInstance } from "./api";
+
+export const signup = async (email, password, nickname) => {
+ const baseUrl = "/user/sign-up";
+ try {
+ const response = await baseInstance.post(baseUrl, {
+ email: email,
+ password: password,
+ nickname: nickname,
+ });
+
+ return response;
+ } catch (err) {
+ console.error(err);
+ return err;
+ }
+};
+
+export const login = async (email, password) => {
+ const baseUrl = "/user/login";
+ try {
+ const response = await baseInstance.post(baseUrl, {
+ email: email,
+ password: password,
+ });
+
+ return response;
+ } catch (err) {
+ console.error(err);
+ return err;
+ }
+};
+
+export const logout = async (token) => {
+ const baseUrl = "/user/logout";
+ try {
+ const response = await baseUserInstance.post(
+ baseUrl,
+ {},
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ }
+ );
+
+ return response;
+ } catch (err) {
+ console.error(err);
+ return err;
+ }
+};
diff --git a/src/lib/hooks/.gitkeep b/src/lib/hooks/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/src/lib/hooks/useWebSocket.jsx b/src/lib/hooks/useWebSocket.jsx
new file mode 100644
index 0000000..449d181
--- /dev/null
+++ b/src/lib/hooks/useWebSocket.jsx
@@ -0,0 +1,59 @@
+import React, {
+ createContext,
+ useCallback,
+ useContext,
+ useEffect,
+ useState,
+} from "react";
+import {
+ joinRoom,
+ leaveRoom,
+ subscribeNowPrice,
+ subscribeAskPrice,
+} from "../../store/webSocket/nowPrice";
+import { useSelector } from "react-redux";
+
+const useWebSocketConnection = () => {
+ const [askPrice, setAskPrice] = useState(null);
+ const [nowPrice, setNowPrice] = useState(null);
+ const before = useSelector((state) => state.company.data[0]?.code);
+ const now = useSelector((state) => state.company.data[1]?.code);
+
+ useEffect(() => {
+ leaveRoom(before);
+ joinRoom(now);
+ setNowPrice(null);
+ }, [before, now]);
+
+ useEffect(() => {
+ const settingAskPrice = subscribeAskPrice((askPriceMessage) => {
+ setAskPrice(askPriceMessage);
+ });
+
+ const settingNowPrice = subscribeNowPrice((nowPriceMessage) => {
+ setNowPrice(nowPriceMessage);
+ });
+
+ return () => {
+ settingAskPrice();
+ settingNowPrice();
+ };
+ }, []);
+
+ return { askPrice, nowPrice, ready: !!nowPrice };
+};
+
+const WebSocketContext = createContext();
+
+export const WebSocketProvider = ({ children }) => {
+ const webSocketData = useWebSocketConnection();
+
+ return (
+
+ {children}
+
+ );
+};
+
+// 커스텀 훅
+export const useWebSocket = () => useContext(WebSocketContext);
diff --git a/src/main.jsx b/src/main.jsx
new file mode 100644
index 0000000..febb347
--- /dev/null
+++ b/src/main.jsx
@@ -0,0 +1,11 @@
+import React from "react";
+import ReactDOM from "react-dom/client";
+import App from "./App.jsx";
+import "./index.css";
+import "bootstrap/dist/css/bootstrap.min.css";
+
+ReactDOM.createRoot(document.getElementById("root")).render(
+ //
+
+ //
+);
diff --git a/src/router/main-router.jsx b/src/router/main-router.jsx
new file mode 100644
index 0000000..52b8ca0
--- /dev/null
+++ b/src/router/main-router.jsx
@@ -0,0 +1,67 @@
+import * as React from "react";
+import { createBrowserRouter } from "react-router-dom";
+import MainLayout from "../routes/mainLayout";
+
+// pages
+import TradingPage from "../routes/Trading/TradingPage";
+import InvestStrategyPage from "../routes/InvestStrategy/InvestStrategyPage";
+import HotStockPage from "../routes/HotStock/HotStockPage";
+import FeedPage from "../routes/Feed/FeedPage";
+import MyPage from "../routes/MyPage/MyPage";
+import SideLayout from "../routes/SideLayout";
+import SignIn from "../routes/Auth/SignIn";
+import SignUp from "../routes/Auth/SignUp";
+
+export const mainRoutes = [
+ {
+ path: "/",
+ element: ,
+ children: [
+ {
+ path: "",
+ element: ,
+ children: [
+ {
+ path: "",
+ element: ,
+ index: true,
+ },
+ {
+ path: "/strategy",
+ element: ,
+ index: true,
+ },
+ {
+ path: "/hot",
+ element: ,
+ index: true,
+ },
+ {
+ path: "/feed",
+ element: ,
+ index: true,
+ },
+ {
+ path: "/mypage",
+ element: ,
+ index: true,
+ },
+ ],
+ },
+ ],
+ },
+ {
+ path: "/signup",
+ element: ,
+ children: [],
+ },
+ {
+ path: "/signin",
+ element: ,
+ children: [],
+ },
+];
+
+const router = createBrowserRouter(mainRoutes);
+
+export default router;
diff --git a/src/routes/Auth/SignIn.jsx b/src/routes/Auth/SignIn.jsx
new file mode 100644
index 0000000..a0421d4
--- /dev/null
+++ b/src/routes/Auth/SignIn.jsx
@@ -0,0 +1,156 @@
+import React, { useState } from "react";
+import { useNavigate } from "react-router-dom";
+import styled from "styled-components";
+import LogoIcon from "../../../public/icon/logo.svg";
+import { useDispatch, useSelector } from "react-redux";
+import { postLogin, setUser } from "../../store/reducers/User/user";
+import { getCookie } from "../../lib/apis/cookie";
+
+const SignIn = () => {
+ const dispatch = useDispatch();
+ const navigate = useNavigate();
+ const [email, setEmail] = useState("");
+ const [password, setPassword] = useState("");
+ const [error, setError] = useState("");
+
+ const onLogin = (e) => {
+ e.preventDefault();
+ const data = {
+ email,
+ password,
+ };
+ dispatch(postLogin(data)).then((res) => {
+ if (res.payload.status === 201) {
+ dispatch(setUser(res.payload.data));
+ navigate("/");
+ } else {
+ setError("이메일 혹은 비밀번호가 옳지 않습니다.");
+ }
+ });
+ setEmail("");
+ setPassword("");
+ };
+
+ return (
+
+
+
+ StockMate
+
+
+ {error && 이메일 혹은 비밀번호가 옳지 않습니다.}
+
+ navigate("/")}>홈으로
+ |
+ navigate("/signup")}>회원가입
+
+
+ );
+};
+export default SignIn;
+
+const SigninContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ height: 100vh;
+ gap: 32px;
+
+ @media (max-width: 500px) {
+ transform: scale(0.8);
+ }
+`;
+
+const Form = styled.form`
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ gap: 32px;
+`;
+
+const Label = styled.label`
+ display: flex;
+ align-items: center;
+ background: #f4f5f7;
+`;
+
+const StyledInput = styled.input`
+ width: 400px;
+ height: 60px;
+ background: #f4f5f7;
+ border: none;
+ box-shadow: 2px 1px 5px rgba(0, 0, 0, 0.1);
+ font-size: 18px;
+ font-weight: 400;
+ padding: 0 30px;
+
+ &::placeholder {
+ color: rgba(160, 160, 160, 0.8);
+ }
+
+ &:focus {
+ outline: 2px solid #ffd4c2;
+ }
+`;
+
+const StyledButton = styled.button`
+ border-radius: 16px;
+ border: none;
+ background: #FFE3D7;
+ width: 404px;
+ height: 55px;
+ color: #000;
+ text-align: center;
+ font-size: 18px;
+ font-weight: 400;
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15);
+
+ &:hover {
+ background-color: #ffcfba;
+ }
+`;
+
+const NavDiv = styled.div`
+ color: #8b8b8b;
+ text-align: center;
+ font-weight: 400;
+ margin-top: 30px;
+
+ &:hover {
+ cursor: pointer;
+ color: #FF7D75;
+ }
+`;
+
+const LogoDiv = styled.div`
+ display: flex;
+ align-items: center;
+ font-size: 32px;
+ font-weight: 200;
+ gap: 10px;
+`;
+
+const ErrorFont = styled.span`
+ color: #ff3333;
+`;
diff --git a/src/routes/Auth/SignUp.jsx b/src/routes/Auth/SignUp.jsx
new file mode 100644
index 0000000..df3a533
--- /dev/null
+++ b/src/routes/Auth/SignUp.jsx
@@ -0,0 +1,228 @@
+import React, { useState } from "react";
+import { useNavigate } from "react-router-dom";
+import styled from "styled-components";
+import LogoIcon from "../../../public/icon/logo.svg";
+import { useDispatch } from "react-redux";
+import { postSignup } from "../../store/reducers/User/user";
+import { Button, Modal } from "react-bootstrap";
+
+const SignUp = () => {
+ const [nickname, setNickname] = useState("");
+ const [email, setEmail] = useState("");
+ const [password, setPassword] = useState("");
+ const [passwordCheck, setPasswordCheck] = useState("");
+ const navigate = useNavigate();
+ const dispatch = useDispatch();
+
+ const [error1, setError1] = useState("");
+ const [error2, setError2] = useState("");
+
+ const [isAlert, setIsAlert] = useState(false);
+ const [alertModal, setAlertModal] = useState("");
+
+ const onSignup = (e) => {
+ e.preventDefault();
+ if (emailCheck(email)) {
+ setError1("유효한 이메일 형식으로 입력해주세요.");
+ setError2("");
+ } else if (password === passwordCheck) {
+ const data = {
+ email,
+ password,
+ nickname,
+ };
+ dispatch(postSignup(data)).then((res) => {
+ if (res.payload.status === 201) {
+ setIsAlert(true);
+ setAlertModal("회원가입이 완료되었습니다. \n 🎉 해당 계정의 계좌로 1억원이 지급되었습니다 🎉 \n 로그인을 해보세요!");
+ } else {
+ setIsAlert(true);
+ setAlertModal("해당 이메일은 이미 사용 중 입니다.");
+ }
+ });
+
+ setEmail("");
+ setPassword("");
+ setPasswordCheck("");
+ setNickname("");
+ setError1("");
+ setError2("");
+ } else {
+ setError2("비밀번호가 일치하지 않습니다.");
+ setPassword("");
+ setPasswordCheck("");
+ setError1("");
+ }
+ };
+
+ function emailCheck(email_address) {
+ const email_regex = new RegExp(/^[\w-]+(\.[\w-]+)*@([\w-]+\.)+[a-zA-Z]+$/);
+ return !email_regex.test(email_address);
+ }
+
+ return (
+
+
+
+ StockMate
+
+
+
+ navigate("/")}>홈으로
+ |
+ navigate("/signin")}>로그인
+
+
+ {/* Signup 모달 */}
+ setIsAlert(false)} centered>
+
+ {alertModal}
+
+
+
+
+
+
+ );
+};
+export default SignUp;
+
+const SignupContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ height: 100vh;
+ gap: 32px;
+
+ @media (max-width: 500px) {
+ transform: scale(0.8);
+ }
+`;
+
+const Form = styled.form`
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ gap: 22px;
+`;
+
+const Label = styled.label`
+ display: flex;
+ align-items: center;
+ background: #f4f5f7;
+`;
+
+const StyledInput = styled.input`
+ width: 400px;
+ height: 60px;
+ background: #f4f5f7;
+ box-shadow: 2px 1px 5px rgba(0, 0, 0, 0.1);
+ border: none;
+ font-size: 18px;
+ font-weight: 400;
+ padding: 0 30px;
+
+ &::placeholder {
+ color: rgba(160, 160, 160, 0.8);
+ }
+
+ &:focus {
+ outline: 2px solid #ffd4c2;
+ }
+`;
+
+const StyledButton = styled.button`
+ border-radius: 16px;
+ border: none;
+ background: #FFE3D7;
+ width: 404px;
+ height: 55px;
+ color: #000;
+ text-align: center;
+ font-size: 18px;
+ font-weight: 400;
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15);
+
+ &:hover {
+ background-color: #ffcfba;
+ }
+`;
+
+const NavDiv = styled.div`
+ color: #8b8b8b;
+ text-align: center;
+ font-weight: 400;
+ margin-top: 30px;
+
+ &:hover {
+ cursor: pointer;
+ color: #FF7D75;
+ }
+`;
+
+const LogoDiv = styled.div`
+ display: flex;
+ align-items: center;
+ font-size: 32px;
+ font-weight: 200;
+ gap: 10px;
+`;
+
+const Error = styled.span`
+ color: #ff3333;
+ font-size: 13px;
+`;
diff --git a/src/routes/Feed/FeedPage.jsx b/src/routes/Feed/FeedPage.jsx
new file mode 100644
index 0000000..2d9a864
--- /dev/null
+++ b/src/routes/Feed/FeedPage.jsx
@@ -0,0 +1,88 @@
+import React, { useState } from "react";
+import styled from "styled-components";
+import * as S from "../../style/GlobalStyle";
+import FeedWriting from "~/components/Feed/FeedWriting";
+import Feed from "../../components/Feed/FeedShow/Feed";
+import { useLocation, useNavigate } from "react-router-dom";
+import { getCookie } from "../../lib/apis/cookie";
+import LogoIcon from "../../../public/icon/logo.svg";
+
+const FeedPage = () => {
+ const [isWrite, setIsWrite] = useState(false);
+ const location = useLocation();
+ const path = location.pathname;
+ const isLogin = !!getCookie("token");
+ const navigate = useNavigate();
+ const write = () => {
+ setIsWrite(true);
+ };
+
+ return (
+
+ {isLogin ? (
+ <>
+ {isWrite ? (
+
+ ) : (
+
+
+ 무슨 생각을 하고 계신가요?
+
+ )}
+
+
+
+ >
+ ) : (
+
+
navigate("/signin")} />
+ 로그인을 해주세요.
+
+ )}
+
+ );
+};
+
+const WritingContainer = styled.div`
+ display: flex;
+ flex-direction: row;
+ padding: 20px 35px;
+ gap: 15px;
+ cursor: pointer;
+ background-color: white;
+ height: 80px;
+ width: 100%;
+ margin-bottom: 5px;
+`;
+
+const InputDiv = styled.div`
+ width: 361px;
+ height: 40px;
+ border-radius: 20px;
+ background: #f3f3f3;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ padding-left: 22px;
+
+ color: #7e7e7e;
+ font-size: 15px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: normal;
+`;
+
+const FeedContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ overflow: auto;
+ &::-webkit-scrollbar {
+ display: none;
+ }
+`;
+
+export default FeedPage;
diff --git a/src/routes/HotStock/HotStockPage.jsx b/src/routes/HotStock/HotStockPage.jsx
new file mode 100644
index 0000000..ba6bd3d
--- /dev/null
+++ b/src/routes/HotStock/HotStockPage.jsx
@@ -0,0 +1,189 @@
+import React, { useEffect, useState } from "react";
+import styled from "styled-components";
+import * as S from "../../style/GlobalStyle";
+import { useDispatch, useSelector } from "react-redux";
+import { getHotDatas, getPopularDatas } from "../../store/reducers/Hot/getStockInfo";
+import restart_Img from '../../../public/icon/restart.svg';
+import { PuffLoader } from "react-spinners";
+import PopularStock from "../../components/side-bar/PopularStock";
+
+export default function HotStockPage() {
+ const dispatch = useDispatch();
+
+ const popularData = useSelector((state) => state.hot.popularData);
+ const hotData = useSelector((state) => state.hot.hotData);
+ const loading = useSelector((state) => state.hot.loading);
+ const [selectNum, setSelectNum] = useState(1);
+
+ useEffect(() => {
+ dispatch(getPopularDatas());
+ dispatch(getHotDatas(selectNum));
+ }, [selectNum]);
+
+ return (
+
+
+ {/* 인기 주식 */}
+
+
+
+ 실시간 인기 주식
+
+
+ {popularData.length > 0 ? (
+ popularData.map((item, idx) =>
+
+ )
+ ) : (
+
+ dispatch(getPopularDatas())}
+ />
+
+ )}
+
+
+ {/* 핫이슈 종목 */}
+
+
+
+ 핫이슈 종목
+
+
+
+ setSelectNum(1)}>
+ 거래량
+
+ setSelectNum(2)}>
+ 주가상승률
+
+ setSelectNum(3)}>
+ 외국인순매수
+
+ setSelectNum(4)}>
+ 기관순매수
+
+
+ {loading ? (
+
+ ) : (
+ hotData.length > 0 ? (
+ hotData.map((item, idx) =>
+
+ )
+ ) : (
+
+ 조건에 맞는 데이터가 없습니다.
+
+ )
+ )}
+
+
+
+ );
+}
+
+const Container = styled.div`
+ background-color: #fff;
+ height: 100vh;
+ overflow-y: scroll;
+
+ &::-webkit-scrollbar {
+ width: 7px;
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background: rgba(0, 0, 0, 0.2);
+ border-radius: 20px;
+ }
+
+ &::-webkit-scrollbar-track {
+ background: rgba(0, 0, 0, 0.05);
+ border-radius: 20px;
+ }
+`
+
+const TitleDiv = styled.div`
+ display: flex;
+ align-items: center;
+ padding: 20px 5px;
+ gap: 8px;
+`;
+
+const MainFont = styled.span`
+ font-size: 18px;
+ font-weight: 700;
+`;
+
+const IconImg = styled.img`
+ width: 35px;
+ height: 35px;
+`;
+
+const RankFont = styled.span`
+ font-size: 16px;
+ font-weight: 700;
+`;
+
+const BtnDiv = styled.div`
+ display: flex;
+ justify-content: space-evenly;
+ padding: 0 0 20px 0;
+`;
+
+const SelectBtn = styled.div`
+ padding: 8px 15px;
+ border-radius: 999px;
+ background-color: #d9d9d9;
+ font-size: 13px;
+ font-weight: 600;
+
+ &:hover {
+ cursor: pointer;
+ }
+
+ &:nth-child(${(props) => props.num}) {
+ background-color: #ffd4c2;
+ }
+`;
+
+const ErrorDiv1 = styled.div`
+ display: flex;
+ padding: 90px 0;
+ justify-content: center;
+ align-items: center;
+`;
+
+const ErrorDiv2 = styled.div`
+ display: flex;
+ padding: 150px 0;
+ justify-content: center;
+ align-items: center;
+`;
+
+const ReIcon = styled.img`
+ width: 20px;
+`;
diff --git a/src/routes/InvestStrategy/InvestStrategyPage.jsx b/src/routes/InvestStrategy/InvestStrategyPage.jsx
new file mode 100644
index 0000000..9c93feb
--- /dev/null
+++ b/src/routes/InvestStrategy/InvestStrategyPage.jsx
@@ -0,0 +1,170 @@
+import React, { useEffect } from "react";
+import styled from "styled-components";
+import * as S from "../../style/GlobalStyle";
+import { useDispatch, useSelector } from "react-redux";
+import { getStrategyDatas } from "../../store/reducers/Strategy/getStrategy";
+import { decode } from "html-entities";
+import { PuffLoader } from 'react-spinners';
+
+export default function InvestStrategyPage() {
+ const dispatch = useDispatch();
+ const strategy = useSelector((state) => state.strategy.strategy);
+ const loading = useSelector((state) => state.strategy.loading);
+
+ useEffect(() => {
+ dispatch(getStrategyDatas())
+ }, [])
+
+ const ecsummary = strategy.economicAnalysis?.content?.split('\n\n\n')[0];
+ const eccontent = strategy.economicAnalysis?.content?.split('\n\n\n').splice(1);
+
+ const corsummary = strategy.corporateAnalysis?.content?.split('\n\n\n')[0];
+ const corcontent = strategy.corporateAnalysis?.content?.split('\n\n\n').splice(1);
+
+ return (
+
+
+ {loading ? (
+
+ ) : (
+ strategy && (
+ <>
+
+
+
+ {strategy.shinhanDaily?.bbs_name}
+
+
+ {strategy.shinhanDaily?.title}.pdf
+
+
+
+
+
+ {strategy.marketIssue?.bbs_name}
+
+
+ {strategy.marketIssue?.title}.pdf
+
+
+
+
+
+ {strategy.economicAnalysis?.bbs_name}
+
+
+ {strategy.economicAnalysis?.title}.pdf
+
+ {ecsummary?.split('\n').map((item, idx) =>
+
+ •
+ {decode(item)}
+
+ )}
+
+ {eccontent?.map((item, idx) =>
+ {decode(item)}
+ )}
+
+
+
+
+ {strategy.corporateAnalysis?.bbs_name}
+
+
+ {strategy.corporateAnalysis?.title}.pdf
+
+ {corsummary?.split('\n').map((item, idx) =>
+
+ •
+ {decode(item)}
+
+ )}
+
+ {corcontent?.map((item, idx) =>
+ {decode(item)}
+ )}
+
+ >
+ )
+ )}
+
+
+ )
+}
+
+const Container = styled.div`
+ background-color: #FFF;
+ overflow-y: scroll;
+ height: 100vh;
+
+ &::-webkit-scrollbar {
+ width: 10px;
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background: rgba(0, 0, 0, 0.2);
+ border-radius: 20px;
+ }
+
+ &::-webkit-scrollbar-track {
+ background: rgba(0, 0, 0, 0.05);
+ border-radius: 20px;
+ }
+`
+
+const Content = styled.div`
+ padding: 0 0 20px 0;
+`
+
+const MainDiv = styled.div`
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ padding: 20px 15px;
+ border-bottom: 1px solid rgba(0, 0, 0, 0.2);
+`
+
+const IconImg = styled.img`
+ width: 30px;
+ height: 30px;
+`
+
+const MainFont = styled.div`
+ font-weight: 600;
+ font-size: 18px;
+`
+
+const FileDiv = styled.a`
+ color: #FF7D75;
+`
+
+const FileFont = styled.div`
+ color: #FF7D75;
+ font-weight: 600;
+ padding: 20px;
+`
+
+const SubDiv = styled.div`
+ display: flex;
+ align-items: center;
+ padding: 8px 20px;
+ gap: 10px;
+`
+
+const SubFont = styled.div`
+ // word-break: keep-all;
+ font-weight: 600;
+ font-size: 15px;
+`
+
+const ContentFont = styled.div`
+ // box-shadow: 3px 3px 4px rgba(0, 0, 0, 0.3);
+ white-space: pre-line;
+ padding: 10px 25px;
+ font-size: 15px;
+ font-weight: 600;
+ color: rgba(0, 0, 0, 0.8)
+`
\ No newline at end of file
diff --git a/src/routes/MarketInfo/MarketInfoPage.jsx b/src/routes/MarketInfo/MarketInfoPage.jsx
new file mode 100644
index 0000000..a0113ed
--- /dev/null
+++ b/src/routes/MarketInfo/MarketInfoPage.jsx
@@ -0,0 +1,7 @@
+import React from "react";
+import styled from "styled-components";
+import * as S from "../../style/GlobalStyle";
+
+export default function MarketInfoPage() {
+ return MarketInfoPage;
+}
diff --git a/src/routes/MyPage/MyPage.jsx b/src/routes/MyPage/MyPage.jsx
new file mode 100644
index 0000000..b28d0ab
--- /dev/null
+++ b/src/routes/MyPage/MyPage.jsx
@@ -0,0 +1,235 @@
+import React, { useEffect, useState } from "react";
+import styled from "styled-components";
+import * as S from "../../style/GlobalStyle";
+import { useDispatch, useSelector } from "react-redux";
+import { fetchMyPageFeedCount } from "../../store/reducers/Feed/feed";
+import {
+ fetchFriendCount,
+ fetchFriends,
+ fetchPendingFriends,
+} from "../../store/reducers/User/friend";
+import Feed from "../../components/Feed/FeedShow/Feed";
+import FriendList from "../../components/MyPage/FriendList";
+import FriendSearch from "../../components/MyPage/FriendSearch";
+import MyAccount from "../../components/MyAccount/MyAccount";
+import LogoIcon from "../../../public/icon/logo.svg";
+import { useLocation, useNavigate } from "react-router-dom";
+import { getCookie } from "../../lib/apis/cookie";
+
+export default function MyPage() {
+ const isLogin = !!getCookie("token");
+ const [selectedTab, setSelectedTab] = useState("내 피드");
+
+ const handleTabClick = (tab) => {
+ setSelectedTab(tab);
+ };
+ const location = useLocation();
+ const path = location.pathname;
+ const dispatch = useDispatch();
+
+ useEffect(() => {
+ dispatch(fetchMyPageFeedCount(userId));
+ dispatch(fetchFriendCount(userId));
+ dispatch(fetchFriends());
+ dispatch(fetchPendingFriends());
+ }, []);
+
+ const pendingFriends = useSelector((state) => state.friend.pendingFriends);
+ const userId = useSelector((state) => state.user.user.id);
+ const userNickname = useSelector((state) => state.user.user.nickname);
+ const feedCount = useSelector((state) => state.feed.mypageFeedCount);
+ const friendCount = useSelector((state) => state.friend.friends);
+ // const friendCounts = useSelector((state) => state.friend.friendCount);
+
+ useEffect(() => {
+ dispatch(fetchFriendCount(userId));
+ }, [friendCount]);
+
+ const [showDetail, setShowDetail] = useState(false);
+
+ const toggleDetail = () => {
+ setShowDetail(!showDetail);
+ };
+
+ const [showFriendSearch, setShowFriendSearch] = useState(false);
+
+ const toggleFriendSearch = () => {
+ setShowFriendSearch(!showFriendSearch);
+ };
+
+ const navigate = useNavigate();
+ return (
+
+ {isLogin ? (
+ <>
+
+
handleTabClick("내 피드")}
+ >
+ 내 피드
+
+
handleTabClick("내 계좌")}
+ >
+ 내 계좌
+
+
+
+ 친구 검색
+
+ {showFriendSearch ? (
+
+ ) : (
+ <>>
+ )}
+
+
+
+ {userNickname}
+
+
+ 게시물
+ {feedCount}
+
+
+ {pendingFriends.length != 0 && (
+
+ )}
+ 친구
+ {friendCount?.length}
+
+
+ {showDetail ? : <>>}
+
+
+
+ {selectedTab === "내 피드" ? (
+
+ ) : (
+ <>>
+ )}
+ {selectedTab === "내 계좌" ? : <>>}
+
+ >
+ ) : (
+
+
navigate("/signin")} />
+ 로그인을 해주세요.
+
+ )}
+
+ );
+}
+
+const UserContainer = styled.div`
+ display: flex;
+ flex-direction: row;
+ padding: 35px 40px;
+ justify-content: space-around;
+ align-items: center;
+ background-color: white;
+ margin-bottom: 5px;
+`;
+
+const FriendDiv = styled.div`
+ position: absolute;
+ top: 45px;
+ right: 30px;
+ color: #a2a2a2;
+ font-size: smaller;
+ cursor: pointer;
+
+ &:hover {
+ color: #525252;
+ }
+`;
+
+const ImgDiv = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ word-break: break-all;
+`;
+
+const NicknameDiv = styled.div`
+ font-size: 17px;
+ max-width: 98px;
+`;
+
+const ColumnDiv = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ width: 60px;
+ font-size: 18px;
+
+ cursor: ${(props) => props.$pointer || "default"};
+`;
+
+const ItemDiv = styled.div``;
+
+const NumDiv = styled.div``;
+
+const ContentContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ overflow: auto;
+ &::-webkit-scrollbar {
+ display: none;
+ }
+`;
+
+const DetailContainer = styled.div`
+ width: 400px;
+ height: 100%;
+ background-color: #fff;
+ box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
+ transform: translateX(${(props) => (props.$showdetail ? "0" : "100%")});
+ transition: transform 0.3s ease;
+ position: absolute;
+ top: 0;
+ left: 0;
+ overflow: hidden;
+ z-index: 999;
+`;
diff --git a/src/routes/SideLayout.jsx b/src/routes/SideLayout.jsx
new file mode 100644
index 0000000..d881550
--- /dev/null
+++ b/src/routes/SideLayout.jsx
@@ -0,0 +1,23 @@
+import React from "react";
+import { Outlet } from "react-router-dom";
+import styled from "styled-components";
+import SideNavbar from "~/components/side-bar/SideNavbar";
+const SideLayout = () => {
+ return (
+
+
+
+
+ );
+};
+
+const Container = styled.section`
+ display: flex;
+ flex-direction: column;
+ width: 400px;
+ position: relative;
+ z-index: 999;
+ background-color: white;
+ border-left: 1px solid #e2e2e2;
+`;
+export default SideLayout;
diff --git a/src/routes/Trading/TradingPage.jsx b/src/routes/Trading/TradingPage.jsx
new file mode 100644
index 0000000..5c88841
--- /dev/null
+++ b/src/routes/Trading/TradingPage.jsx
@@ -0,0 +1,165 @@
+import React, { useState, useEffect } from "react";
+import PriceBook from "../../components/invest/right-bar/OrderManagement/PriceBook";
+import OrderBook from "../../components/invest/right-bar/OrderManagement/OrderBook";
+import OrderList from "../../components/invest/right-bar/OrderHistory/OrderHistoryList";
+import NewIcon from "../../components/invest/right-bar/OrderHistory/NewIcon";
+import { useDispatch, useSelector } from "react-redux";
+import {
+ setSelectedTab,
+ setSelectedPrice,
+ setSelectedQuantity,
+ setIsNew,
+} from "../../store/reducers/Trading/trading";
+import { useWebSocket } from "../../lib/hooks/useWebSocket";
+import { getLogoFileName, onErrorImg } from "../../util/getLogoFileName";
+
+export default function TradingPage() {
+ const dispatch = useDispatch();
+ const { selectedTab, orderType, isNew } = useSelector(
+ (state) => state.trading
+ );
+ const { nowPrice } = useWebSocket();
+ const company = useSelector((state) => state.company.data[1]);
+
+ const handleTabClick = (tab) => {
+ dispatch(setSelectedTab(tab));
+ dispatch(setSelectedQuantity(0));
+ dispatch(setSelectedPrice(nowPrice?.message?.close));
+
+ if (orderType === "지정가") {
+ dispatch(setSelectedPrice(nowPrice?.message?.close));
+ }
+ };
+
+ return (
+
+
+
+

+
+
+
{company.name}
+
+
+ {company.code}
+
+ 코스피
+
+
+
+
+
handleTabClick("매수")}
+ >
+ 매수
+
+
handleTabClick("매도")}
+ >
+ 매도
+
+
{
+ handleTabClick("주문내역");
+ dispatch(setIsNew(false));
+ }}
+ >
+ 주문내역
+ {isNew && }
+
+
+ {selectedTab === "매수" || selectedTab === "매도" ? (
+ <>
+
+ >
+ ) : (
+
+
+
+ )}
+
+ );
+}
diff --git a/src/routes/chatBot/chatBot.css b/src/routes/chatBot/chatBot.css
new file mode 100644
index 0000000..b07b724
--- /dev/null
+++ b/src/routes/chatBot/chatBot.css
@@ -0,0 +1,130 @@
+.App-header {
+ background-color: #282c34;
+ min-height: 20vh;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ font-size: calc(10px + 2vmin);
+ color: white;
+}
+
+.chat-box {
+ margin: 0px;
+ padding: 10px;
+ border: 1px solid #ddd;
+ height: 50vh;
+ overflow-y: auto;
+}
+
+.message {
+ margin: 5px;
+ padding: 10px;
+ border-radius: 10px;
+}
+
+.user {
+ background-color: #007bff;
+ color: white;
+ text-align: right; /* 사용자 메시지 오른쪽 정렬을 위해 text-align 사용 */
+ margin-left: auto; /* 메시지 박스를 오른쪽으로 밀어내기 */
+ width: fit-content; /* 메시지 내용에 맞게 너비 조절 */
+ max-width: 60%; /* 최대 너비 설정 */
+}
+
+.bot {
+ background-color: #d3d2d2;
+ color: black;
+ text-align: left; /* 챗봇 메시지 왼쪽 정렬을 위해 text-align 사용 */
+ margin-right: auto; /* 메시지 박스를 왼쪽으로 밀어내기 */
+ width: fit-content; /* 메시지 내용에 맞게 너비 조절 */
+ max-width: 60%; /* 최대 너비 설정 */
+}
+
+.loading-message {
+ padding: 10px 10px; /* 말풍선 내부의 여백을 조정 */
+ min-width: 80px; /* 말풍선의 최소 너비를 설정 */
+ /* 필요에 따라 추가 스타일링 */
+ }
+
+
+.loader10:before{
+ content: "";
+ position: absolute;
+ top: 0px;
+ left: -25px;
+ height: 12px;
+ width: 12px;
+ border-radius: 12px;
+ -webkit-animation: loader10g 3s ease-in-out infinite;
+ animation: loader10g 3s ease-in-out infinite;
+}
+
+.loader10{
+ position: relative;
+ width: 12px;
+ height: 12px;
+ top: 46%;
+ left: 46%;
+ border-radius: 12px;
+ -webkit-animation: loader10m 3s ease-in-out infinite;
+ animation: loader10m 3s ease-in-out infinite;
+}
+
+
+.loader10:after{
+ content: "";
+ position: absolute;
+ top: 0px;
+ left: 25px;
+ height: 10px;
+ width: 10px;
+ border-radius: 10px;
+ -webkit-animation: loader10d 3s ease-in-out infinite;
+ animation: loader10d 3s ease-in-out infinite;
+}
+
+@-webkit-keyframes loader10g{
+ 0%{background-color: rgba(255, 255, 255, .2);}
+ 25%{background-color: rgba(255, 255, 255, 1);}
+ 50%{background-color: rgba(255, 255, 255, .2);}
+ 75%{background-color: rgba(255, 255, 255, .2);}
+ 100%{background-color: rgba(255, 255, 255, .2);}
+}
+@keyframes loader10g{
+ 0%{background-color: rgba(255, 255, 255, .2);}
+ 25%{background-color: rgba(255, 255, 255, 1);}
+ 50%{background-color: rgba(255, 255, 255, .2);}
+ 75%{background-color: rgba(255, 255, 255, .2);}
+ 100%{background-color: rgba(255, 255, 255, .2);}
+}
+
+@-webkit-keyframes loader10m{
+ 0%{background-color: rgba(255, 255, 255, .2);}
+ 25%{background-color: rgba(255, 255, 255, .2);}
+ 50%{background-color: rgba(255, 255, 255, 1);}
+ 75%{background-color: rgba(255, 255, 255, .2);}
+ 100%{background-color: rgba(255, 255, 255, .2);}
+}
+@keyframes loader10m{
+ 0%{background-color: rgba(255, 255, 255, .2);}
+ 25%{background-color: rgba(255, 255, 255, .2);}
+ 50%{background-color: rgba(255, 255, 255, 1);}
+ 75%{background-color: rgba(255, 255, 255, .2);}
+ 100%{background-color: rgba(255, 255, 255, .2);}
+}
+
+@-webkit-keyframes loader10d{
+ 0%{background-color: rgba(255, 255, 255, .2);}
+ 25%{background-color: rgba(255, 255, 255, .2);}
+ 50%{background-color: rgba(255, 255, 255, .2);}
+ 75%{background-color: rgba(255, 255, 255, 1);}
+ 100%{background-color: rgba(255, 255, 255, .2);}
+}
+@keyframes loader10d{
+ 0%{background-color: rgba(255, 255, 255, .2);}
+ 25%{background-color: rgba(255, 255, 255, .2);}
+ 50%{background-color: rgba(255, 255, 255, .2);}
+ 75%{background-color: rgba(255, 255, 255, 1);}
+ 100%{background-color: rgba(255, 255, 255, .2);}
+}
\ No newline at end of file
diff --git a/src/routes/chatBot/chatBot.jsx b/src/routes/chatBot/chatBot.jsx
new file mode 100644
index 0000000..fa8829b
--- /dev/null
+++ b/src/routes/chatBot/chatBot.jsx
@@ -0,0 +1,107 @@
+import React, { useState, useRef, useEffect } from 'react';
+import { fetchChatBot } from '../../lib/apis/chatBot';
+import { Container, Form, Button, InputGroup, FormControl, Row, Col} from 'react-bootstrap';
+import './chatBot.css';
+import { commander } from '../../service/commander';
+import chatbotImg from '../../../public/icon/chat_mate.jpg';
+
+function ChatBot() {
+ const [messages, setMessages] = useState([]);
+ const [input, setInput] = useState('');
+ const [isWaitingForResponse, setIsWaitingForResponse] = useState(false);
+ const chatBoxRef = useRef(null);
+
+ useEffect(() => {
+ // 챗봇의 초기 메시지를 추가
+ const initialMessage = {
+ text: '안녕하세요? 궁금하신 사항이 있나요?',
+ sender: 'bot'
+ };
+ setMessages([initialMessage]);
+ }, []);
+
+ const sendMessage = async (e) => {
+ e.preventDefault();
+ if (!input.trim()) return;
+ if (input[0] === '/') {
+ try {
+ const response = commander(input);
+ const userMessage = { text: input, sender: 'user' };
+ setMessages(messages => [...messages, userMessage]);
+ const botMessage = { text: response, sender: 'bot' };
+ setMessages(messages => [...messages, botMessage]);
+ setInput('');
+ } catch (err) {
+ console.error(err);
+ } finally {
+ setIsWaitingForResponse(false);
+ }
+ }else{
+ const userMessage = { text: input, sender: 'user' };
+ setMessages((messages) => [...messages, userMessage]);
+ setInput('');
+ setIsWaitingForResponse(true);
+
+ try {
+ const response = await fetchChatBot(input);
+ const botMessage = { text: response.gpt, sender: 'bot' };
+ setMessages((messages) => [...messages, botMessage]);
+ } catch (err) {
+ console.error(err);
+ } finally {
+ setIsWaitingForResponse(false);
+ }
+ }
+
+ };
+
+ useEffect(() => {
+ if (chatBoxRef.current) {
+ chatBoxRef.current.scrollTop = chatBoxRef.current.scrollHeight;
+ }
+ }, [messages]);
+
+ return (
+
+
+
+
+ {messages.map((message, index) => (
+
+
+ {message.sender === 'bot' && (
+

+ )}
+
+
+ {message.text}
+
+
+ ))}
+ {isWaitingForResponse && (
+
+ )}
+
+
+
+
+
+
+ 활용 Tip💡 /설명 /가이드 /매수매도 /차트지표 /보조지표 /SNS 등의 커멘더키를 사용해보세요!
+
+ );
+}
+
+export default ChatBot;
diff --git a/src/routes/mainLayout.jsx b/src/routes/mainLayout.jsx
new file mode 100644
index 0000000..cdc1c28
--- /dev/null
+++ b/src/routes/mainLayout.jsx
@@ -0,0 +1,107 @@
+import React, { useState } from "react";
+import { Outlet } from "react-router-dom";
+import MyNavbar from "../components/MyNavbar";
+import MainChart from "../components/invest/chart/MainChart";
+import MyStockList from "../components/invest/left-bar/MyStockList";
+import styled from "styled-components";
+import ChartIndicators from "../components/invest/left-bar/ChartIndicators";
+import Indicators from "../components/invest/left-bar/Indicators";
+
+export default function MainLayout() {
+ const [showIndicators, setShowIndicators] = useState(false);
+ const [showCharts, setShowCharts] = useState(false);
+
+ const toggleIndicators = () => {
+ setShowIndicators(!showIndicators);
+ setShowCharts(false);
+ };
+
+ const toggleCharts = () => {
+ setShowCharts(!showCharts);
+ setShowIndicators(false);
+ };
+
+ return (
+ <>
+
+
+
+
+
+
+ {showCharts ? : <>>}
+
+
+ {showIndicators ? (
+
+ ) : (
+ <>>
+ )}
+
+
+
+
+
+
+ >
+ );
+}
+
+const Container = styled.div`
+ display: flex;
+ height: calc(100vh - 65px);
+ overflow: hidden;
+`;
+
+const LeftContainer = styled.div`
+ display: flex;
+ flex-direction: row;
+ width: fit-content;
+ height: 100%;
+ position: relative;
+ overflow: hidden;
+ box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.1);
+`;
+
+const ContentContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ transition: margin-left 0.3s ease;
+ z-index: 99;
+`;
+
+const Content = styled.div`
+ display: flex;
+ align-items: center;
+`;
+
+const ChartsContainer = styled.div`
+ width: 250px;
+ height: 100%;
+ background-color: #fff;
+ box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
+ transform: translateX(${(props) => (props.$showcharts ? "0" : "-100%")});
+ transition: transform 0.3s ease;
+ position: absolute;
+ top: 0;
+ left: 0;
+ overflow: hidden;
+`;
+
+const IndicatorsContainer = styled.div`
+ width: 250px;
+ height: 100%;
+ background-color: #fff;
+ box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
+ transform: translateX(${(props) => (props.$showindicators ? "0" : "-100%")});
+ transition: transform 0.3s ease;
+ position: absolute;
+ top: 0;
+ left: 0;
+ overflow: hidden;
+`;
diff --git a/src/service/commander.jsx b/src/service/commander.jsx
new file mode 100644
index 0000000..b8e3da6
--- /dev/null
+++ b/src/service/commander.jsx
@@ -0,0 +1,19 @@
+
+export function commander(prompt){
+ const text = prompt.slice(0,5);
+ if(text === '/가이드'){
+ return "다음은 stock mate 사이트 이용에 대한 기본적인 가이드 입니다. \n\n 1. 오른쪽 상단의 로그인 및 회원가입을 실행합니다. \n 2. 왼쪽 사이드 바의 검색을 통하여 종목을 선정합니다 \n 3. 선정한 종목의 주가 및 보조 지표를 확인합니다. \n 4. 오른쪽 사이드바의 매수/매도를 통하여 선택종목을 거래합니다."
+ }else if(text ==='/설명'){
+ return "안녕하세요? Stock mate 의 설명을 도와드릴 챗 메이트 에요. 저희는 주식투자가 어려운 주린이들이나 나만의 투자전략을 수립하고 싶은 분들께 도움을 드리고 있어요. 저희 stock mate 사이트는 크게 4가지 기능을 가지고 있어요. \n\n 1. 모의투자 기능 \n 저희는 모의투자 기능을 제공해요. 일명 교육비(?)라고 불리우는 초기 투자 비용을 절감하여 부담없이 투자해보실 수 있어요. 여러 지표나 핫 테마 등을 활용하여 '나의 투자 전략'을 수립하고, 검증해볼 수도 있죠. \n\n 2. 보조지표와 차트지표 \n 투자자 여러분은 보조지표와 투자지표를 활용하여 주식 데이터를 분석하고, 전략을 수립하실 수 있어요! 지표의 설정을 통해서 세부적인 가중치를 정할 수 있고, 어려우시면 기본값으로 진행해도 좋아요. \n\n 3. SNS 기능 \n 저희는 기본적인 sns기능을 제공해요. 투자자 여러분은 자신의 투자전략이나 수익률 등을 공유하고, 공유받을 수 있어요. 원하는 투자고수를 팔로우하여 전략을 공유받으세요! \n\n 4. chat bot 서비스 \n 바로 저 '챗 메이트' 기능이에요. 보조지표나 차트지표에 대해서 궁금하신점을 물어보시거나 커멘더키(/기능)를 활용해서 사이트 활용 방법을 손쉽게 배울 수 있어요. \n\n 그럼 즐거운 이용되시길 바랍니다 *^^*"
+ }else if(text === '/매수매도'){
+ return "매수와 매도는 오른쪽 사이드바에서 진행하실 수 있습니다. 매수는 종목 '구매', 매도는 종목 '판매' 라고 이해하셔도 됩니다. \n 선택한 종목의 호가와 실시간 거래가를 확인한 후, 매수 매도를 진행해주세요!"
+ }else if(text === '/차트지표'){
+ return "차트 지표는 차트 지표는 주가 또는 거래량 등의 데이터를 분석하여 시장의 흐름과 추세를 파악하는 데 도움을 주는 기술적 지표들을 말합니다. 주로 주가 차트에 함께 표시되어 사용되며, 주가의 움직임을 그래픽으로 표현하여 투자자들이 시장의 동향을 파악하고 결정을 내릴 때 유용하게 활용됩니다. \n\n 차트 위쪽의 차트지표를 클릭하면 현재 보고있는 종목의 차트지표를 제공합니다. \n 각 차트지표에 대한 설명이 궁금하시면 'ex) AD Line은 뭐에요?' 와 같이 질문해주세요."
+ }else if(text === '/보조지표'){
+ return "보조 지표는 주가 차트의 움직임을 분석하고 예측하기 위해 가격 데이터를 기반으로 계산된 보조적인 지표를 말합니다. 보조 지표는 가격 데이터의 변화에 대한 신호를 제공하고, 추세의 방향성, 강도, 변화 등을 확인하는 데 도움을 줍니다. \n\n 차트 위쪽의 보조지표를 클릭하면 현재 보고있는 종목의 보조지표를 제공합니다. \n 각 보조지표에 대한 설명이 궁금하시면 'ex) AD Line은 뭐에요?' 와 같이 질문해주세요."
+ }else if(text === '/sns' || text==='/SNS'){
+ return "SNS 기능은 투자 고수와 초보 등 다양한 투자자들을 연결해주는 소셜 네트워크 입니다. 자신의 투자 수익률과 투자 전략을 공유하고, 인플루언서에 도전해보세요! 마음에 드는 인플루언서를 팔로우 하여 투자 전략을 참고할 수 있습니다! \n\n 오른쪽 사이드바 하단의 '피드' 와 '마이페이지' 를 통하여 이용하실 수 있습니다. 투표나 게시글을 생성하여 여러 유저와의 소통도 가능합니다."
+ }else{
+ return "잘못된 커멘더 키 입니다. 하단의 커멘더 키를 확인하시거나, 질문사항을 등록해주세요."
+ }
+}
diff --git a/src/service/taLib.js b/src/service/taLib.js
new file mode 100644
index 0000000..c820c32
--- /dev/null
+++ b/src/service/taLib.js
@@ -0,0 +1,878 @@
+import talib from 'talib';
+
+function processData(stockData){
+ let marketData = {open:[], high:[], low:[], close:[],volume:[]}
+ stockData.map(el=>{
+ marketData.open.push(el.open)
+ marketData.high.push(el.high)
+ marketData.low.push(el.low)
+ marketData.close.push(el.close)
+ marketData.volume.push(el.volume)
+ })
+ return marketData;
+}
+
+function SMA(stockData, lineTime){
+ const marketData = processData(stockData);
+
+ var indicatorParams = {
+ name: "SMA",
+ startIdx: 0,
+ endIdx: marketData.close.length - 1,
+ inReal: marketData.close,
+ optInTimePeriod: lineTime,
+ };
+
+ talib.execute(indicatorParams, function (err, result) {
+ if (err) {
+ console.error("Error:", err);
+ } else {
+ return result;
+ }
+ });
+}
+
+function WMA(stockData, lineTime){
+ const marketData = processData(stockData);
+ var indicatorParams = {
+ name: "WMA",
+ startIdx: 0,
+ endIdx: marketData.close.length - 1,
+ inReal: marketData.close,
+ optInTimePeriod: lineTime,
+ };
+ talib.execute(indicatorParams, function (err, result) {
+ if (err) {
+ console.error("Error:", err);
+ } else {
+ console.log(result);
+ return result;
+ }
+ });
+}
+
+function BBANDS(stockData, lineTime, stdev){
+ const marketData = processData(stockData);
+ var indicatorParams = {
+ name: "BBANDS",
+ startIdx: 0,
+ endIdx: marketData.close.length - 1,
+ inReal: marketData.close,
+ optInTimePeriod: lineTime,
+ optInNbDevUp: stdev,
+ optInNbDevDn: stdev,
+ optInMAType: 0,
+ };
+ talib.execute(indicatorParams, function (err, result) {
+ if (err) {
+ console.error("Error:", err);
+ } else {
+ return result;
+ }
+ });
+}
+
+WMA(
+ [
+ {
+ "open": 60600,
+ "close": 60000,
+ "high": 60700,
+ "low": 59600,
+ "volume": 18251170,
+ "date": "20220809"
+ },
+ {
+ "open": 61400,
+ "close": 60800,
+ "high": 61400,
+ "low": 60600,
+ "volume": 11313150,
+ "date": "20220808"
+ },
+ {
+ "open": 61700,
+ "close": 61500,
+ "high": 61900,
+ "low": 61200,
+ "volume": 9567620,
+ "date": "20220805"
+ },
+ {
+ "open": 61700,
+ "close": 61500,
+ "high": 61800,
+ "low": 61200,
+ "volume": 9125439,
+ "date": "20220804"
+ },
+ {
+ "open": 61600,
+ "close": 61300,
+ "high": 61600,
+ "low": 61000,
+ "volume": 10053861,
+ "date": "20220803"
+ },
+ {
+ "open": 61200,
+ "close": 61700,
+ "high": 61900,
+ "low": 61000,
+ "volume": 13614895,
+ "date": "20220802"
+ },
+ {
+ "open": 61000,
+ "close": 61300,
+ "high": 61700,
+ "low": 60300,
+ "volume": 13154816,
+ "date": "20220801"
+ },
+ {
+ "open": 62400,
+ "close": 61400,
+ "high": 62600,
+ "low": 61300,
+ "volume": 15093120,
+ "date": "20220729"
+ },
+ {
+ "open": 62300,
+ "close": 61900,
+ "high": 62600,
+ "low": 61600,
+ "volume": 10745302,
+ "date": "20220728"
+ },
+ {
+ "open": 61300,
+ "close": 61800,
+ "high": 61900,
+ "low": 61200,
+ "volume": 7320997,
+ "date": "20220727"
+ },
+ {
+ "open": 60800,
+ "close": 61700,
+ "high": 61900,
+ "low": 60800,
+ "volume": 6597211,
+ "date": "20220726"
+ },
+ {
+ "open": 60900,
+ "close": 61100,
+ "high": 61900,
+ "low": 60800,
+ "volume": 9193681,
+ "date": "20220725"
+ },
+ {
+ "open": 61800,
+ "close": 61300,
+ "high": 62200,
+ "low": 61200,
+ "volume": 10261310,
+ "date": "20220722"
+ },
+ {
+ "open": 61100,
+ "close": 61800,
+ "high": 61900,
+ "low": 60700,
+ "volume": 12291374,
+ "date": "20220721"
+ },
+ {
+ "open": 61800,
+ "close": 60500,
+ "high": 62100,
+ "low": 60500,
+ "volume": 16782238,
+ "date": "20220720"
+ },
+ {
+ "open": 61400,
+ "close": 60900,
+ "high": 61500,
+ "low": 60200,
+ "volume": 15248261,
+ "date": "20220719"
+ },
+ {
+ "open": 60600,
+ "close": 61900,
+ "high": 62000,
+ "low": 60500,
+ "volume": 20832517,
+ "date": "20220718"
+ },
+ {
+ "open": 58400,
+ "close": 60000,
+ "high": 60000,
+ "low": 58100,
+ "volume": 18685583,
+ "date": "20220715"
+ },
+ {
+ "open": 57500,
+ "close": 57500,
+ "high": 58200,
+ "low": 57400,
+ "volume": 15067012,
+ "date": "20220714"
+ },
+ {
+ "open": 58300,
+ "close": 58000,
+ "high": 58600,
+ "low": 58000,
+ "volume": 10841315,
+ "date": "20220713"
+ },
+ {
+ "open": 58600,
+ "close": 58100,
+ "high": 58700,
+ "low": 58100,
+ "volume": 9336061,
+ "date": "20220712"
+ },
+ {
+ "open": 59300,
+ "close": 58800,
+ "high": 59600,
+ "low": 58700,
+ "volume": 13042624,
+ "date": "20220711"
+ },
+ {
+ "open": 58600,
+ "close": 58700,
+ "high": 59300,
+ "low": 58200,
+ "volume": 15339271,
+ "date": "20220708"
+ },
+ {
+ "open": 56400,
+ "close": 58200,
+ "high": 58700,
+ "low": 56300,
+ "volume": 21322833,
+ "date": "20220707"
+ },
+ {
+ "open": 57300,
+ "close": 56400,
+ "high": 57300,
+ "low": 56400,
+ "volume": 16820461,
+ "date": "20220706"
+ },
+ {
+ "open": 57600,
+ "close": 57200,
+ "high": 58200,
+ "low": 57200,
+ "volume": 14216539,
+ "date": "20220705"
+ },
+ {
+ "open": 56100,
+ "close": 57100,
+ "high": 57400,
+ "low": 55700,
+ "volume": 17807126,
+ "date": "20220704"
+ },
+ {
+ "open": 56900,
+ "close": 56200,
+ "high": 57500,
+ "low": 55900,
+ "volume": 24982097,
+ "date": "20220701"
+ },
+ {
+ "open": 57200,
+ "close": 57000,
+ "high": 57600,
+ "low": 57000,
+ "volume": 18915142,
+ "date": "20220630"
+ },
+ {
+ "open": 58500,
+ "close": 58000,
+ "high": 58800,
+ "low": 58000,
+ "volume": 14677138,
+ "date": "20220629"
+ },
+ {
+ "open": 59200,
+ "close": 59400,
+ "high": 59500,
+ "low": 58700,
+ "volume": 13540538,
+ "date": "20220628"
+ },
+ {
+ "open": 59000,
+ "close": 58800,
+ "high": 59900,
+ "low": 58300,
+ "volume": 18122236,
+ "date": "20220627"
+ },
+ {
+ "open": 57900,
+ "close": 58400,
+ "high": 59100,
+ "low": 57700,
+ "volume": 23256103,
+ "date": "20220624"
+ },
+ {
+ "open": 57700,
+ "close": 57400,
+ "high": 58000,
+ "low": 56800,
+ "volume": 28338608,
+ "date": "20220623"
+ },
+ {
+ "open": 59000,
+ "close": 57600,
+ "high": 59100,
+ "low": 57600,
+ "volume": 23334687,
+ "date": "20220622"
+ },
+ {
+ "open": 58700,
+ "close": 58500,
+ "high": 59200,
+ "low": 58200,
+ "volume": 25148109,
+ "date": "20220621"
+ },
+ {
+ "open": 59800,
+ "close": 58700,
+ "high": 59900,
+ "low": 58100,
+ "volume": 34111306,
+ "date": "20220620"
+ },
+ {
+ "open": 59400,
+ "close": 59800,
+ "high": 59900,
+ "low": 59400,
+ "volume": 29053450,
+ "date": "20220617"
+ },
+ {
+ "open": 61300,
+ "close": 60900,
+ "high": 61800,
+ "low": 60500,
+ "volume": 23394895,
+ "date": "20220616"
+ },
+ {
+ "open": 61300,
+ "close": 60700,
+ "high": 61500,
+ "low": 60200,
+ "volume": 26811224,
+ "date": "20220615"
+ },
+ {
+ "open": 61200,
+ "close": 61900,
+ "high": 62200,
+ "low": 61100,
+ "volume": 24606419,
+ "date": "20220614"
+ },
+ {
+ "open": 62400,
+ "close": 62100,
+ "high": 62800,
+ "low": 62100,
+ "volume": 22157816,
+ "date": "20220613"
+ },
+ {
+ "open": 64000,
+ "close": 63800,
+ "high": 64400,
+ "low": 63800,
+ "volume": 22193552,
+ "date": "20220610"
+ },
+ {
+ "open": 65100,
+ "close": 65200,
+ "high": 65200,
+ "low": 64500,
+ "volume": 25790725,
+ "date": "20220609"
+ },
+ {
+ "open": 65400,
+ "close": 65300,
+ "high": 65700,
+ "low": 65300,
+ "volume": 12483180,
+ "date": "20220608"
+ },
+ {
+ "open": 66200,
+ "close": 65500,
+ "high": 66400,
+ "low": 65400,
+ "volume": 19355755,
+ "date": "20220607"
+ },
+ {
+ "open": 67200,
+ "close": 66800,
+ "high": 67300,
+ "low": 66800,
+ "volume": 8222883,
+ "date": "20220603"
+ },
+ {
+ "open": 66600,
+ "close": 66700,
+ "high": 67000,
+ "low": 66400,
+ "volume": 14959443,
+ "date": "20220602"
+ },
+ {
+ "open": 67500,
+ "close": 67400,
+ "high": 67500,
+ "low": 66700,
+ "volume": 24365002,
+ "date": "20220531"
+ },
+ {
+ "open": 67500,
+ "close": 67700,
+ "high": 67800,
+ "low": 66900,
+ "volume": 14255484,
+ "date": "20220530"
+ },
+ {
+ "open": 66700,
+ "close": 66500,
+ "high": 66900,
+ "low": 66200,
+ "volume": 11405555,
+ "date": "20220527"
+ },
+ {
+ "open": 66300,
+ "close": 65900,
+ "high": 67200,
+ "low": 65500,
+ "volume": 15970890,
+ "date": "20220526"
+ },
+ {
+ "open": 66700,
+ "close": 66400,
+ "high": 67100,
+ "low": 65900,
+ "volume": 15150490,
+ "date": "20220525"
+ },
+ {
+ "open": 67500,
+ "close": 66500,
+ "high": 67700,
+ "low": 66500,
+ "volume": 15482576,
+ "date": "20220524"
+ },
+ {
+ "open": 68800,
+ "close": 67900,
+ "high": 68800,
+ "low": 67600,
+ "volume": 13684088,
+ "date": "20220523"
+ },
+ {
+ "open": 67800,
+ "close": 68000,
+ "high": 68400,
+ "low": 67700,
+ "volume": 12109671,
+ "date": "20220520"
+ },
+ {
+ "open": 66500,
+ "close": 67500,
+ "high": 67600,
+ "low": 66500,
+ "volume": 17073727,
+ "date": "20220519"
+ },
+ {
+ "open": 68300,
+ "close": 68100,
+ "high": 68700,
+ "low": 67600,
+ "volume": 16486319,
+ "date": "20220518"
+ },
+ {
+ "open": 66600,
+ "close": 67600,
+ "high": 67900,
+ "low": 66600,
+ "volume": 15680447,
+ "date": "20220517"
+ },
+ {
+ "open": 67100,
+ "close": 66300,
+ "high": 67400,
+ "low": 66100,
+ "volume": 11937555,
+ "date": "20220516"
+ },
+ {
+ "open": 65300,
+ "close": 66500,
+ "high": 66700,
+ "low": 65200,
+ "volume": 14551536,
+ "date": "20220513"
+ },
+ {
+ "open": 65200,
+ "close": 64900,
+ "high": 65500,
+ "low": 64900,
+ "volume": 16414188,
+ "date": "20220512"
+ },
+ {
+ "open": 65500,
+ "close": 65700,
+ "high": 66300,
+ "low": 65200,
+ "volume": 12330920,
+ "date": "20220511"
+ },
+ {
+ "open": 65900,
+ "close": 65700,
+ "high": 66300,
+ "low": 65300,
+ "volume": 17235605,
+ "date": "20220510"
+ },
+ {
+ "open": 66300,
+ "close": 66100,
+ "high": 66900,
+ "low": 66100,
+ "volume": 11858736,
+ "date": "20220509"
+ },
+ {
+ "open": 67000,
+ "close": 66500,
+ "high": 67100,
+ "low": 66500,
+ "volume": 14356156,
+ "date": "20220506"
+ },
+ {
+ "open": 68000,
+ "close": 67900,
+ "high": 68400,
+ "low": 67500,
+ "volume": 11505248,
+ "date": "20220504"
+ },
+ {
+ "open": 67400,
+ "close": 67500,
+ "high": 68400,
+ "low": 67300,
+ "volume": 14168875,
+ "date": "20220503"
+ },
+ {
+ "open": 66600,
+ "close": 67300,
+ "high": 67600,
+ "low": 66500,
+ "volume": 14106184,
+ "date": "20220502"
+ },
+ {
+ "open": 65100,
+ "close": 67400,
+ "high": 67600,
+ "low": 65000,
+ "volume": 26190390,
+ "date": "20220429"
+ },
+ {
+ "open": 65400,
+ "close": 64800,
+ "high": 65500,
+ "low": 64500,
+ "volume": 16895527,
+ "date": "20220428"
+ },
+ {
+ "open": 65400,
+ "close": 65000,
+ "high": 65500,
+ "low": 64900,
+ "volume": 18122084,
+ "date": "20220427"
+ },
+ {
+ "open": 66400,
+ "close": 66100,
+ "high": 66700,
+ "low": 66100,
+ "volume": 12946923,
+ "date": "20220426"
+ },
+ {
+ "open": 66500,
+ "close": 66300,
+ "high": 66700,
+ "low": 66300,
+ "volume": 11016474,
+ "date": "20220425"
+ },
+ {
+ "open": 67200,
+ "close": 67000,
+ "high": 67300,
+ "low": 66700,
+ "volume": 11791478,
+ "date": "20220422"
+ },
+ {
+ "open": 67600,
+ "close": 67700,
+ "high": 68300,
+ "low": 67500,
+ "volume": 12847448,
+ "date": "20220421"
+ },
+ {
+ "open": 67000,
+ "close": 67400,
+ "high": 67400,
+ "low": 66500,
+ "volume": 16693293,
+ "date": "20220420"
+ },
+ {
+ "open": 67100,
+ "close": 67300,
+ "high": 68000,
+ "low": 67000,
+ "volume": 12959434,
+ "date": "20220419"
+ },
+ {
+ "open": 66500,
+ "close": 66700,
+ "high": 67100,
+ "low": 66100,
+ "volume": 10119203,
+ "date": "20220418"
+ },
+ {
+ "open": 67200,
+ "close": 66600,
+ "high": 67300,
+ "low": 66500,
+ "volume": 13176415,
+ "date": "20220415"
+ },
+ {
+ "open": 68700,
+ "close": 67500,
+ "high": 68700,
+ "low": 67500,
+ "volume": 16409494,
+ "date": "20220414"
+ },
+ {
+ "open": 67300,
+ "close": 68700,
+ "high": 69000,
+ "low": 67200,
+ "volume": 17378619,
+ "date": "20220413"
+ },
+ {
+ "open": 67600,
+ "close": 67000,
+ "high": 67700,
+ "low": 67000,
+ "volume": 13924389,
+ "date": "20220412"
+ },
+ {
+ "open": 67800,
+ "close": 67900,
+ "high": 68100,
+ "low": 67400,
+ "volume": 12263735,
+ "date": "20220411"
+ },
+ {
+ "open": 68100,
+ "close": 67800,
+ "high": 68300,
+ "low": 67700,
+ "volume": 15453191,
+ "date": "20220408"
+ },
+ {
+ "open": 68500,
+ "close": 68000,
+ "high": 68500,
+ "low": 68000,
+ "volume": 20683327,
+ "date": "20220407"
+ },
+ {
+ "open": 68600,
+ "close": 68500,
+ "high": 68800,
+ "low": 68500,
+ "volume": 15517308,
+ "date": "20220406"
+ },
+ {
+ "open": 69400,
+ "close": 69200,
+ "high": 69600,
+ "low": 69100,
+ "volume": 8467248,
+ "date": "20220405"
+ },
+ {
+ "open": 68900,
+ "close": 69300,
+ "high": 69300,
+ "low": 68600,
+ "volume": 11107905,
+ "date": "20220404"
+ },
+ {
+ "open": 69500,
+ "close": 69100,
+ "high": 69500,
+ "low": 69000,
+ "volume": 15916846,
+ "date": "20220401"
+ },
+ {
+ "open": 69900,
+ "close": 69600,
+ "high": 70200,
+ "low": 69600,
+ "volume": 12510366,
+ "date": "20220331"
+ },
+ {
+ "open": 70300,
+ "close": 69900,
+ "high": 70500,
+ "low": 69800,
+ "volume": 12670187,
+ "date": "20220330"
+ },
+ {
+ "open": 70000,
+ "close": 70200,
+ "high": 70300,
+ "low": 69800,
+ "volume": 13686208,
+ "date": "20220329"
+ },
+ {
+ "open": 69500,
+ "close": 69700,
+ "high": 69900,
+ "low": 69200,
+ "volume": 12619289,
+ "date": "20220328"
+ },
+ {
+ "open": 70100,
+ "close": 69800,
+ "high": 70200,
+ "low": 69600,
+ "volume": 12986010,
+ "date": "20220325"
+ },
+ {
+ "open": 69600,
+ "close": 69800,
+ "high": 70300,
+ "low": 69600,
+ "volume": 37943357,
+ "date": "20220324"
+ },
+ {
+ "open": 70600,
+ "close": 70500,
+ "high": 71200,
+ "low": 70300,
+ "volume": 12398025,
+ "date": "20220323"
+ },
+ {
+ "open": 69900,
+ "close": 70300,
+ "high": 70500,
+ "low": 69900,
+ "volume": 9402666,
+ "date": "20220322"
+ },
+ {
+ "open": 70900,
+ "close": 69900,
+ "high": 71000,
+ "low": 69900,
+ "volume": 11169002,
+ "date": "20220321"
+ },
+ {
+ "open": 70600,
+ "close": 70700,
+ "high": 70900,
+ "low": 70200,
+ "volume": 14410038,
+ "date": "20220318"
+ }
+ ],5
+);
\ No newline at end of file
diff --git a/src/store/reducers/.gitkeep b/src/store/reducers/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/src/store/reducers/Chart/Indicators/chart.jsx b/src/store/reducers/Chart/Indicators/chart.jsx
new file mode 100644
index 0000000..b165947
--- /dev/null
+++ b/src/store/reducers/Chart/Indicators/chart.jsx
@@ -0,0 +1,84 @@
+import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
+import { getBBANDS, getEMA, getSAR, getSMA, getWMA } from "../../../../lib/apis/chart";
+
+const initialState = {
+ SMADatas: [],
+ WMADatas: [],
+ EMADatas: [],
+ BBANDSDatas: [],
+ SARDatas: [],
+};
+
+export const getSMAChart = createAsyncThunk(
+ "chart/getSMA",
+ async (data, tunkAPI) => {
+ // const { code, start_date, end_date, time_format } = data;
+ const response = await getSMA(data);
+ return response.data;
+ }
+)
+
+export const getWMAChart = createAsyncThunk(
+ "chart/getWMA",
+ async (data, tunkAPI) => {
+ // const { code, start_date, end_date, time_format } = data;
+ const response = await getWMA(data);
+ return response.data;
+ }
+)
+
+export const getEMAChart = createAsyncThunk(
+ "chart/getEMA",
+ async (data, tunkAPI) => {
+ // const { code, start_date, end_date, time_format } = data;
+ const response = await getEMA(data);
+ return response.data;
+ }
+)
+
+export const getBBANDSChart = createAsyncThunk(
+ "chart/getBBANDS",
+ async (data, tunkAPI) => {
+ // const { code, start_date, end_date, time_format } = data;
+ const response = await getBBANDS(data);
+ return response.data;
+ }
+)
+
+export const getSARChart = createAsyncThunk(
+ "chart/getSAR",
+ async (data, tunkAPI) => {
+ // const { code, start_date, end_date, time_format } = data;
+ const response = await getSAR(data);
+ return response.data;
+ }
+)
+
+const chartIndicatorSlice = createSlice({
+ name: "chartIndicator",
+ initialState: initialState,
+ reducers: {
+ setSMADatas(state, action) {
+ state.SMADatas = action.payload;
+ },
+ setWMADatas(state, action) {
+ state.WMADatas = action.payload;
+ },
+ setEMADatas(state, action) {
+ state.EMADatas = action.payload;
+ },
+ setBBANDSDatas(state, action) {
+ state.BBANDSDatas = action.payload;
+ },
+ setSARDatas(state, action) {
+ state.SARDatas = action.payload;
+ },
+ },
+ extraReducers: (builder) => {
+ },
+});
+
+const { setSMADatas, setWMADatas, setEMADatas, setBBANDSDatas, setSARDatas } = chartIndicatorSlice.actions;
+export { setSMADatas, setWMADatas, setEMADatas, setBBANDSDatas, setSARDatas };
+
+export default chartIndicatorSlice.reducer;
diff --git a/src/store/reducers/Chart/Indicators/clickIndicators.jsx b/src/store/reducers/Chart/Indicators/clickIndicators.jsx
new file mode 100644
index 0000000..027d9a1
--- /dev/null
+++ b/src/store/reducers/Chart/Indicators/clickIndicators.jsx
@@ -0,0 +1,58 @@
+import { createSlice } from "@reduxjs/toolkit";
+
+const initialState = {
+ SMA: false,
+ WMA: false,
+ EMA: false,
+ BBANDS: false,
+ SAR: false,
+ MACD: false,
+ STOCHF: false,
+ STOCH: false,
+ RSI: false,
+ CCI: false,
+ MOM: false,
+ ROC: false,
+ AD: false,
+ ATR: false,
+ MFI: false,
+ OBV: false,
+ ADOSC: false,
+ TRIX: false,
+ WILLR: false,
+ DX: false,
+ ADX: false,
+ ADXR: false,
+ AROON: false,
+ AROONOSC: false,
+ STOCHRSI: false,
+ ULTOSC: false,
+ PPO: false,
+ chartIndi: [],
+ subIndi: [],
+};
+
+const clickSubSlice = createSlice({
+ name: "subChart",
+ initialState: initialState,
+ reducers: {
+ setActiveSub(state, action) {
+ state[action.payload] = true;
+ },
+ setDisactiveSub(state, action) {
+ state[action.payload] = false;
+ },
+ setChartIndi(state, action) {
+ state.chartIndi = action.payload;
+ },
+ setSubIndi(state, action) {
+ state.subIndi = action.payload;
+ },
+ },
+});
+
+const { setActiveSub, setDisactiveSub, setChartIndi, setSubIndi } =
+ clickSubSlice.actions;
+export { setActiveSub, setDisactiveSub, setChartIndi, setSubIndi };
+
+export default clickSubSlice.reducer;
diff --git a/src/store/reducers/Chart/Indicators/sub.jsx b/src/store/reducers/Chart/Indicators/sub.jsx
new file mode 100644
index 0000000..210bc96
--- /dev/null
+++ b/src/store/reducers/Chart/Indicators/sub.jsx
@@ -0,0 +1,215 @@
+import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
+import { getAD, getADOSC, getADX, getADXR, getAROON, getAROONOSC, getATR, getCCI, getDX, getMACD, getMFI, getMOM, getOBV, getPPO, getROC, getRSI, getSTOCH, getSTOCHF, getSTOCHRSI, getTRIX, getULTOSC, getWILLR } from "../../../../lib/apis/chart";
+
+const initialState = {
+ MACDDatas: [],
+};
+
+export const getMACDChart = createAsyncThunk(
+ "chart/getMACD",
+ async (data, tunkAPI) => {
+ // const { code, start_date, end_date, time_format } = data;
+ const response = await getMACD(data);
+ return response.data;
+ }
+)
+
+export const getSTOCHFChart = createAsyncThunk(
+ "chart/getSTOCHF",
+ async (data, tunkAPI) => {
+ // const { code, start_date, end_date, time_format } = data;
+ const response = await getSTOCHF(data);
+ return response.data;
+ }
+)
+
+export const getSTOCHChart = createAsyncThunk(
+ "chart/getSTOCH",
+ async (data, tunkAPI) => {
+ // const { code, start_date, end_date, time_format } = data;
+ const response = await getSTOCH(data);
+ return response.data;
+ }
+)
+
+export const getRSIChart = createAsyncThunk(
+ "chart/getRSI",
+ async (data, tunkAPI) => {
+ // const { code, start_date, end_date, time_format } = data;
+ const response = await getRSI(data);
+ return response.data;
+ }
+)
+
+export const getCCIChart = createAsyncThunk(
+ "chart/getCCI",
+ async (data, tunkAPI) => {
+ // const { code, start_date, end_date, time_format } = data;
+ const response = await getCCI(data);
+ return response.data;
+ }
+)
+
+export const getMOMChart = createAsyncThunk(
+ "chart/getMOM",
+ async (data, tunkAPI) => {
+ // const { code, start_date, end_date, time_format } = data;
+ const response = await getMOM(data);
+ return response.data;
+ }
+)
+
+export const getROCChart = createAsyncThunk(
+ "chart/getROC",
+ async (data, tunkAPI) => {
+ // const { code, start_date, end_date, time_format } = data;
+ const response = await getROC(data);
+ return response.data;
+ }
+)
+
+export const getADChart = createAsyncThunk(
+ "chart/getAD",
+ async (data, tunkAPI) => {
+ // const { code, start_date, end_date, time_format } = data;
+ const response = await getAD(data);
+ return response.data;
+ }
+)
+
+export const getATRChart = createAsyncThunk(
+ "chart/getATR",
+ async (data, tunkAPI) => {
+ // const { code, start_date, end_date, time_format } = data;
+ const response = await getATR(data);
+ return response.data;
+ }
+)
+
+export const getMFIChart = createAsyncThunk(
+ "chart/getMFI",
+ async (data, tunkAPI) => {
+ // const { code, start_date, end_date, time_format } = data;
+ const response = await getMFI(data);
+ return response.data;
+ }
+)
+
+export const getOBVChart = createAsyncThunk(
+ "chart/getOBV",
+ async (data, tunkAPI) => {
+ // const { code, start_date, end_date, time_format } = data;
+ const response = await getOBV(data);
+ return response.data;
+ }
+)
+
+export const getADOSCChart = createAsyncThunk(
+ "chart/getADOSC",
+ async (data, tunkAPI) => {
+ // const { code, start_date, end_date, time_format } = data;
+ const response = await getADOSC(data);
+ return response.data;
+ }
+)
+
+export const getTRIXChart = createAsyncThunk(
+ "chart/getTRIX",
+ async (data, tunkAPI) => {
+ // const { code, start_date, end_date, time_format } = data;
+ const response = await getTRIX(data);
+ return response.data;
+ }
+)
+
+export const getWILLRChart = createAsyncThunk(
+ "chart/getWILLR",
+ async (data, tunkAPI) => {
+ // const { code, start_date, end_date, time_format } = data;
+ const response = await getWILLR(data);
+ return response.data;
+ }
+)
+
+export const getDXChart = createAsyncThunk(
+ "chart/getDX",
+ async (data, tunkAPI) => {
+ // const { code, start_date, end_date, time_format } = data;
+ const response = await getDX(data);
+ return response.data;
+ }
+)
+
+export const getADXChart = createAsyncThunk(
+ "chart/getADX",
+ async (data, tunkAPI) => {
+ // const { code, start_date, end_date, time_format } = data;
+ const response = await getADX(data);
+ return response.data;
+ }
+)
+
+export const getADXRChart = createAsyncThunk(
+ "chart/getADXR",
+ async (data, tunkAPI) => {
+ // const { code, start_date, end_date, time_format } = data;
+ const response = await getADXR(data);
+ return response.data;
+ }
+)
+
+export const getAROONChart = createAsyncThunk(
+ "chart/getAROON",
+ async (data, tunkAPI) => {
+ // const { code, start_date, end_date, time_format } = data;
+ const response = await getAROON(data);
+ return response.data;
+ }
+)
+
+export const getAROONOSCChart = createAsyncThunk(
+ "chart/getAROONOSC",
+ async (data, tunkAPI) => {
+ // const { code, start_date, end_date, time_format } = data;
+ const response = await getAROONOSC(data);
+ return response.data;
+ }
+)
+
+export const getSTOCHRSIChart = createAsyncThunk(
+ "chart/getSTOCHRSI",
+ async (data, tunkAPI) => {
+ // const { code, start_date, end_date, time_format } = data;
+ const response = await getSTOCHRSI(data);
+ return response.data;
+ }
+)
+
+export const getULTOSCChart = createAsyncThunk(
+ "chart/getULTOSC",
+ async (data, tunkAPI) => {
+ // const { code, start_date, end_date, time_format } = data;
+ const response = await getULTOSC(data);
+ return response.data;
+ }
+)
+
+export const getPPOChart = createAsyncThunk(
+ "chart/getPPO",
+ async (data, tunkAPI) => {
+ // const { code, start_date, end_date, time_format } = data;
+ const response = await getPPO(data);
+ return response.data;
+ }
+)
+
+const subIndicatorSlice = createSlice({
+ name: "subIndicator",
+ initialState: initialState,
+ reducers: {
+ },
+ extraReducers: (builder) => {
+ },
+});
+
+export default subIndicatorSlice.reducer;
diff --git a/src/store/reducers/Chart/chart.jsx b/src/store/reducers/Chart/chart.jsx
new file mode 100644
index 0000000..519927b
--- /dev/null
+++ b/src/store/reducers/Chart/chart.jsx
@@ -0,0 +1,305 @@
+import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
+import { getChartData, getMinuteData } from '../../../lib/apis/chart'
+
+const initialState = {
+ // default : 삼성전자
+ datas: [],
+ date: "D",
+ loading: false,
+};
+
+export const getChartDatas = createAsyncThunk(
+ "chart/getData",
+ async (data, tunkAPI) => {
+ const response = await getChartData(data);
+ return response.data;
+ }
+)
+
+export const getMinuteDatas = createAsyncThunk(
+ "chart/getMinuteData",
+ async (data, tunkAPI) => {
+ const response = await getMinuteData(data);
+ return response.data;
+ }
+)
+
+const chartSlice = createSlice({
+ name: "chart",
+ initialState: initialState,
+ reducers: {
+ setChartDatas(state, action) {
+ const f_idx = action.payload.data.begIndex;
+ const l_idx = action.payload.data.nbElement;
+ const getIndi = action.payload.data.result;
+ if (action.payload.name === 'MACD') {
+ state.datas.map(item => {
+ delete item.macd;
+ delete item.macdSignal;
+ delete item.macdHist;
+ });
+
+ for (let i = 0; i < l_idx; i++) {
+ state.datas[f_idx + i]["macd"] = getIndi.outMACD[i]
+ state.datas[f_idx + i]["macdSignal"] = getIndi.outMACDSignal[i] // 새로운 속성 추가
+ state.datas[f_idx + i]["macdHist"] = getIndi.outMACDHist[i] // 새로운 속성 추가
+ }
+ } else if (action.payload.name === 'STOCHF') {
+ state.datas.map(item => {
+ delete item.outFastK;
+ delete item.outFastD;
+ });
+ for (let i = 0; i < l_idx; i++) {
+ state.datas[f_idx + i]["outFastK"] = getIndi.outFastK[i]
+ state.datas[f_idx + i]["outFastD"] = getIndi.outFastD[i] // 새로운 속성 추가
+ }
+ } else if (action.payload.name === 'STOCH') {
+ state.datas.map(item => {
+ delete item.outSlowK;
+ delete item.outSlowD;
+ });
+ for (let i = 0; i < l_idx; i++) {
+ state.datas[f_idx + i]["outSlowK"] = getIndi.outSlowK[i]
+ state.datas[f_idx + i]["outSlowD"] = getIndi.outSlowD[i] // 새로운 속성 추가
+ }
+ } else if (action.payload.name === 'RSI') {
+ state.datas.map(item => {
+ delete item.rsi;
+ });
+ for (let i = 0; i < l_idx; i++) {
+ state.datas[f_idx + i]["rsi"] = getIndi.outReal[i]
+ }
+ } else if (action.payload.name === 'CCI') {
+ state.datas.map(item => {
+ delete item.cci;
+ });
+ for (let i = 0; i < l_idx; i++) {
+ state.datas[f_idx + i]["cci"] = getIndi.outReal[i]
+ }
+ } else if (action.payload.name === 'MOM') {
+ state.datas.map(item => {
+ delete item.mom;
+ });
+ for (let i = 0; i < l_idx; i++) {
+ state.datas[f_idx + i]["mom"] = getIndi.outReal[i]
+ }
+ } else if (action.payload.name === 'ROC') {
+ state.datas.map(item => {
+ delete item.roc;
+ });
+ for (let i = 0; i < l_idx; i++) {
+ state.datas[f_idx + i]["roc"] = getIndi.outReal[i]
+ }
+ } else if (action.payload.name === 'AD') {
+ state.datas.map(item => {
+ delete item.ad;
+ });
+ for (let i = 0; i < l_idx; i++) {
+ state.datas[f_idx + i]["ad"] = getIndi.outReal[i]
+ }
+ } else if (action.payload.name === 'ATR') {
+ state.datas.map(item => {
+ delete item.atr;
+ });
+ for (let i = 0; i < l_idx; i++) {
+ state.datas[f_idx + i]["atr"] = getIndi.outReal[i]
+ }
+ } else if (action.payload.name === 'MFI') {
+ state.datas.map(item => {
+ delete item.mfi;
+ });
+ for (let i = 0; i < l_idx; i++) {
+ state.datas[f_idx + i]["mfi"] = getIndi.outReal[i]
+ }
+ } else if (action.payload.name === 'OBV') {
+ state.datas.map(item => {
+ delete item.obv;
+ });
+ for (let i = 0; i < l_idx; i++) {
+ state.datas[f_idx + i]["obv"] = getIndi.outReal[i]
+ }
+ } else if (action.payload.name === 'ADOSC') {
+ state.datas.map(item => {
+ delete item.adosc;
+ });
+ for (let i = 0; i < l_idx; i++) {
+ state.datas[f_idx + i]["adosc"] = getIndi.outReal[i]
+ }
+ } else if (action.payload.name === 'TRIX') {
+ state.datas.map(item => {
+ delete item.trix;
+ });
+ for (let i = 0; i < l_idx; i++) {
+ state.datas[f_idx + i]["trix"] = getIndi.outReal[i]
+ }
+ } else if (action.payload.name === 'WILLR') {
+ state.datas.map(item => {
+ delete item.willr;
+ });
+ for (let i = 0; i < l_idx; i++) {
+ state.datas[f_idx + i]["willr"] = getIndi.outReal[i]
+ }
+ } else if (action.payload.name === 'DMI') {
+ state.datas.map(item => {
+ delete item.dx;
+ });
+ for (let i = 0; i < l_idx; i++) {
+ state.datas[f_idx + i]["dx"] = getIndi.outReal[i]
+ }
+ } else if (action.payload.name === 'ADX') {
+ state.datas.map(item => {
+ delete item.adx;
+ });
+ for (let i = 0; i < l_idx; i++) {
+ state.datas[f_idx + i]["adx"] = getIndi.outReal[i]
+ }
+ } else if (action.payload.name === 'ADXR') {
+ state.datas.map(item => {
+ delete item.adxr;
+ });
+ for (let i = 0; i < l_idx; i++) {
+ state.datas[f_idx + i]["adxr"] = getIndi.outReal[i]
+ }
+ } else if (action.payload.name === 'AROON') {
+ state.datas.map(item => {
+ delete item.aroonDown;
+ delete item.aroonUp;
+ });
+ for (let i = 0; i < l_idx; i++) {
+ state.datas[f_idx + i]["aroonDown"] = getIndi.outAroonDown[i]
+ state.datas[f_idx + i]["aroonUp"] = getIndi.outAroonUp[i]
+ }
+ } else if (action.payload.name === 'AROONOSC') {
+ state.datas.map(item => {
+ delete item.aroonosc;
+ });
+ for (let i = 0; i < l_idx; i++) {
+ state.datas[f_idx + i]["aroonosc"] = getIndi.outReal[i]
+ }
+ } else if (action.payload.name === 'STOCHRSI') {
+ state.datas.map(item => {
+ delete item.stochRsiK;
+ delete item.stochRsiD;
+ });
+ for (let i = 0; i < l_idx; i++) {
+ state.datas[f_idx + i]["stochRsiK"] = getIndi.outFastK[i];
+ state.datas[f_idx + i]["stochRsiD"] = getIndi.outFastD[i];
+ }
+ } else if (action.payload.name === 'ULTOSC') {
+ state.datas.map(item => {
+ delete item.ultosc;
+ });
+ for (let i = 0; i < l_idx; i++) {
+ state.datas[f_idx + i]["ultosc"] = getIndi.outReal[i];
+ }
+ } else if (action.payload.name === 'PPO') {
+ state.datas.map(item => {
+ delete item.ppo;
+ });
+ for (let i = 0; i < l_idx; i++) {
+ state.datas[f_idx + i]["ppo"] = getIndi.outReal[i];
+ }
+ } else if (action.payload.name === 'BBANDS') {
+ state.datas.map(item => {
+ delete item.upper;
+ delete item.middle;
+ delete item.lower;
+ });
+ for (let i = 0; i < l_idx; i++) {
+ state.datas[f_idx + i]["upper"] = getIndi.outRealUpperBand[i];
+ state.datas[f_idx + i]["middle"] = getIndi.outRealMiddleBand[i];
+ state.datas[f_idx + i]["lower"] = getIndi.outRealLowerBand[i];
+ }
+ } else if (action.payload.name === 'SAR') {
+ state.datas.map(item => {
+ delete item.sar;
+
+ });
+ for (let i = 0; i < l_idx; i++) {
+ state.datas[f_idx + i]["sar"] = getIndi.outReal[i];
+ }
+ } else if (action.payload.name === 'EMA') {
+ state.datas.map(item => {
+ Object.keys(item).forEach(key => {
+ if (key.includes('ema')) {
+ delete item[key];
+ }
+ });
+ });
+
+ const responses = [action.payload.data.response1, action.payload.data.response2, action.payload.data.response3, action.payload.data.response4, action.payload.data.response5];
+ const EMAValue = action.payload.value;
+ responses.forEach((response, index) => {
+ const f_idx = response.begIndex;
+ const l_idx = response.nbElement;
+ const subData = response.result.outReal;
+ for (let i = 0; i < l_idx; i++) {
+ const emaKey = `ema${[EMAValue[0], EMAValue[1], EMAValue[2], EMAValue[3], EMAValue[4]][index]}`;
+ state.datas[f_idx + i][emaKey] = subData[i];
+ }
+ });
+ } else if (action.payload.name === 'SMA') {
+ state.datas.map(item => {
+ Object.keys(item).forEach(key => {
+ if (key.includes('sma')) {
+ delete item[key];
+ }
+ });
+ });
+
+ const responses = [action.payload.data.response1, action.payload.data.response2, action.payload.data.response3, action.payload.data.response4, action.payload.data.response5];
+ const SMAValue = action.payload.value;
+ responses.forEach((response, index) => {
+ const f_idx = response.begIndex;
+ const l_idx = response.nbElement;
+ const subData = response.result.outReal;
+ for (let i = 0; i < l_idx; i++) {
+ const smaKey = `sma${[SMAValue[0], SMAValue[1], SMAValue[2], SMAValue[3], SMAValue[4]][index]}`;
+ state.datas[f_idx + i][smaKey] = subData[i];
+ }
+ });
+ } else if (action.payload.name === 'WMA') {
+ state.datas.map(item => {
+ Object.keys(item).forEach(key => {
+ if (key.includes('wma')) {
+ delete item[key];
+ }
+ });
+ });
+
+ const responses = [action.payload.data.response1, action.payload.data.response2, action.payload.data.response3, action.payload.data.response4, action.payload.data.response5];
+ const WMAValue = action.payload.value;
+ responses.forEach((response, index) => {
+ const f_idx = response.begIndex;
+ const l_idx = response.nbElement;
+ const subData = response.result.outReal;
+ for (let i = 0; i < l_idx; i++) {
+ const wmaKey = `wma${[WMAValue[0], WMAValue[1], WMAValue[2], WMAValue[3], WMAValue[4]][index]}`;
+ state.datas[f_idx + i][wmaKey] = subData[i];
+ }
+ });
+ }
+ },
+ setLiveData(state, action) {
+ state.datas.pop();
+ state.datas.push(action.payload)
+ },
+ setClickDate(state, action) {
+ state.date = action.payload;
+ }
+ },
+ extraReducers: (builder) => {
+ builder.addCase(getChartDatas.fulfilled, (state, action) => {
+ state.datas = action.payload.reverse();
+ state.loading = false;
+ }),
+ builder.addCase(getChartDatas.pending, (state, action) => {
+ state.loading = true;
+ })
+ },
+});
+
+const { setChartDatas, setClickDate, setLiveData } = chartSlice.actions;
+export { setChartDatas, setClickDate, setLiveData };
+
+export default chartSlice.reducer;
diff --git a/src/store/reducers/Chart/clickCompany.jsx b/src/store/reducers/Chart/clickCompany.jsx
new file mode 100644
index 0000000..f566df2
--- /dev/null
+++ b/src/store/reducers/Chart/clickCompany.jsx
@@ -0,0 +1,38 @@
+import { createSlice } from "@reduxjs/toolkit";
+
+const initialState = {
+ data: [
+ {
+ code: "005930",
+ market: "kospi",
+ market_code: "KR7005930003",
+ name: "삼성전자",
+ __v: 0,
+ _id: "65f24176b5fd9b9fdc483b63",
+ },
+ {
+ code: "005930",
+ market: "kospi",
+ market_code: "KR7005930003",
+ name: "삼성전자",
+ __v: 0,
+ _id: "65f24176b5fd9b9fdc483b63",
+ }
+ ]
+};
+
+const companySlice = createSlice({
+ name: "company",
+ initialState: initialState,
+ reducers: {
+ setClickCompany(state, action) {
+ state.data.shift();
+ state.data.push(action.payload);
+ },
+ },
+});
+
+const { setClickCompany } = companySlice.actions;
+export { setClickCompany };
+
+export default companySlice.reducer;
diff --git a/src/store/reducers/Chart/kospiKosdaq.jsx b/src/store/reducers/Chart/kospiKosdaq.jsx
new file mode 100644
index 0000000..3adb60c
--- /dev/null
+++ b/src/store/reducers/Chart/kospiKosdaq.jsx
@@ -0,0 +1,30 @@
+import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
+import { getkospiKosdaq } from "../../../lib/apis/chart";
+
+const initialState = {
+ data: [],
+};
+
+export const getkospiKosdaqDatas = createAsyncThunk(
+ "chart/getkospiKosdaq",
+ async (data, tunkAPI) => {
+ const response = await getkospiKosdaq();
+ return response.data;
+ }
+)
+
+const kospiSlice = createSlice({
+ name: "kospi",
+ initialState: initialState,
+ reducers: {},
+ extraReducers: (builder) => {
+ builder.addCase(getkospiKosdaqDatas.fulfilled, (state, action) => {
+ state.data = action.payload;
+ }),
+ builder.addCase(getkospiKosdaqDatas.rejected, (state, action) => {
+ state.data = [];
+ })
+ },
+});
+
+export default kospiSlice.reducer;
diff --git a/src/store/reducers/Feed/comment.jsx b/src/store/reducers/Feed/comment.jsx
new file mode 100644
index 0000000..9d0815b
--- /dev/null
+++ b/src/store/reducers/Feed/comment.jsx
@@ -0,0 +1,127 @@
+import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
+import {
+ fetchComments as reqFetchComments,
+ postComment as reqPostComment,
+ putComment as reqPutComment,
+ deleteComment as reqDeleteComment,
+} from "~/lib/apis/comment";
+
+const initialState = {
+ comments: {},
+ selectedFriend: null,
+ loading: "idle",
+};
+
+const fetchComments = createAsyncThunk(
+ "comment/fetchComments",
+ async (feedId, thunkAPI) => {
+ const response = await reqFetchComments(feedId);
+ return response;
+ }
+);
+
+const postComment = createAsyncThunk(
+ "comment/postComment",
+ async ({ feedId, content }, thunkAPI) => {
+ const response = await reqPostComment(feedId, content);
+ return response;
+ }
+);
+
+const putComment = createAsyncThunk(
+ "comment/putComment",
+ async ({ commentId, content }, thunkAPI) => {
+ const response = await reqPutComment(commentId, content);
+ return response;
+ }
+);
+
+const deleteComment = createAsyncThunk(
+ "comment/deleteComment",
+ async (commentId, thunkAPI) => {
+ const response = await reqDeleteComment(commentId);
+ return response;
+ }
+);
+
+const commentSlice = createSlice({
+ name: "comment",
+ initialState: initialState,
+ reducers: {
+ setSelectedFriend: (state, action) => {
+ state.selectedFriend = action.payload;
+ },
+ },
+ extraReducers: (builder) => {
+ builder
+ .addCase(fetchComments.fulfilled, (state, action) => {
+ state.loading = "fulfilled";
+
+ action.payload.forEach((comment) => {
+ const { feed, _id, ...rest } = comment;
+
+ if (!state.comments[feed]) {
+ state.comments[feed] = [];
+ }
+
+ if (
+ !state.comments[feed].some(
+ (existingComment) => existingComment._id === _id
+ )
+ ) {
+ state.comments[feed].push(comment);
+ }
+ });
+ })
+ .addCase(fetchComments.pending, (state) => {
+ state.loading = "pending";
+ })
+ .addCase(fetchComments.rejected, (state) => {
+ state.loading = "rejected";
+ })
+ .addCase(postComment.fulfilled, (state, action) => {
+ state.loading = "fulfilled";
+ const comment = action.payload;
+
+ if (!state.comments[comment.feed]) {
+ state.comments[comment.feed] = [];
+ }
+ state.comments[comment.feed].push(comment);
+ })
+ .addCase(postComment.pending, (state) => {
+ state.loading = "pending";
+ })
+ .addCase(postComment.rejected, (state) => {
+ state.loading = "rejected";
+ })
+ .addCase(putComment.fulfilled, (state, action) => {
+ state.loading = "fulfilled";
+ })
+ .addCase(putComment.pending, (state) => {
+ state.loading = "pending";
+ })
+ .addCase(putComment.rejected, (state) => {
+ state.loading = "rejected";
+ })
+ .addCase(deleteComment.fulfilled, (state, action) => {
+ state.loading = "fulfilled";
+ const deletedCommentId = action.meta.arg;
+
+ Object.keys(state.comments).forEach((feedId) => {
+ state.comments[feedId] = state.comments[feedId].filter(
+ (comment) => comment._id !== deletedCommentId
+ );
+ });
+ })
+ .addCase(deleteComment.pending, (state) => {
+ state.loading = "pending";
+ })
+ .addCase(deleteComment.rejected, (state) => {
+ state.loading = "rejected";
+ });
+ },
+});
+
+export const { setSelectedFriend } = commentSlice.actions;
+export { fetchComments, postComment, putComment, deleteComment };
+export default commentSlice.reducer;
diff --git a/src/store/reducers/Feed/feed.jsx b/src/store/reducers/Feed/feed.jsx
new file mode 100644
index 0000000..da1cd86
--- /dev/null
+++ b/src/store/reducers/Feed/feed.jsx
@@ -0,0 +1,282 @@
+import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
+import {
+ fetchAFeed as reqFetchAFeed,
+ fetchMyFeed as reqFetchMyFeed,
+ fetchOtherFeed as reqFetchOtherFeed,
+ fetchAllFeed as reqFetchcAllFeed,
+ postBoardFeed as reqPostBoardFeed,
+ deleteFeed as reqDeleteFeed,
+ postVoteFeed as reqPostVoteFeed,
+ postVote as reqPostVote,
+ postLike as reqPostLike,
+ postUnlike as reqPostUnlike,
+ fetchMyFeedCount as reqFetchMyFeedCount,
+ postMyProfit as reqPostMyProfit,
+} from "~/lib/apis/feed";
+
+const initialState = {
+ myFeed: [],
+ allFeed: [],
+ otherFeed: [],
+ aFeed: null,
+ myFeedCount: null,
+ mypageFeedCount: null,
+ loading: "idle",
+};
+
+const fetchAFeed = createAsyncThunk(
+ "feed/fetchAFeed",
+ async (feedId, thunkAPI) => {
+ const response = await reqFetchAFeed(feedId);
+ return response;
+ }
+);
+
+const fetchMyFeed = createAsyncThunk(
+ "feed/fetchMyFeed",
+ async ({ userId, page }, thunkAPI) => {
+ const response = await reqFetchMyFeed(userId, page);
+ return response;
+ }
+);
+const fetchOtherFeed = createAsyncThunk(
+ "feed/fetchOtherFeed",
+ async ({ userId, page }, thunkAPI) => {
+ const response = await reqFetchOtherFeed(userId, page);
+ return response;
+ }
+);
+
+const fetchAllFeed = createAsyncThunk(
+ "feed/fetchAllFeed",
+ async (page, thunkAPI) => {
+ const response = await reqFetchcAllFeed(page);
+ return response;
+ }
+);
+
+const postBoardFeed = createAsyncThunk(
+ "feed/postBoardFeed",
+ async (formdata, thunkAPI) => {
+ const response = await reqPostBoardFeed(formdata);
+ return response;
+ }
+);
+
+const deleteFeed = createAsyncThunk(
+ "feed/deleteFeed",
+ async (feedId, thunkAPI) => {
+ const response = await reqDeleteFeed(feedId);
+ return response;
+ }
+);
+
+const postVoteFeed = createAsyncThunk(
+ "feed/postVoteFeed",
+ async (body, thunkAPI) => {
+ const response = await reqPostVoteFeed(body);
+ return response;
+ }
+);
+
+const postVote = createAsyncThunk(
+ "feed/postVote",
+ async ({ feedId, voteResult }, thunkAPI) => {
+ const response = await reqPostVote(feedId, voteResult);
+ return response;
+ }
+);
+
+const postLike = createAsyncThunk("feed/postLike", async (feedId, thunkAPI) => {
+ const response = await reqPostLike(feedId);
+ return response;
+});
+
+const postUnlike = createAsyncThunk(
+ "feed/postUnlike",
+ async (feedId, thunkAPI) => {
+ const response = await reqPostUnlike(feedId);
+ return response;
+ }
+);
+
+const fetchMyFeedCount = createAsyncThunk(
+ "feed/fetchMyFeedCount",
+ async (userId, thunkAPI) => {
+ const response = await reqFetchMyFeedCount(userId);
+ return response;
+ }
+);
+
+const fetchMyPageFeedCount = createAsyncThunk(
+ "feed/fetchMyPageFeedCount",
+ async (userId, thunkAPI) => {
+ const response = await reqFetchMyFeedCount(userId);
+ return response;
+ }
+);
+
+const postMyProfit = createAsyncThunk(
+ "feed/postMyProfit",
+ async (profit, thunkAPI) => {
+ const response = await reqPostMyProfit(profit);
+ return response;
+ }
+);
+
+const feedSlice = createSlice({
+ name: "feed",
+ initialState: initialState,
+ reducers: {},
+ extraReducers: (builder) => {
+ builder
+ .addCase(fetchAFeed.fulfilled, (state, action) => {
+ state.loading = "fulfilled";
+ state.aFeed = action.payload;
+ })
+ .addCase(fetchAFeed.pending, (state) => {
+ state.loading = "pending";
+ })
+ .addCase(fetchAFeed.rejected, (state) => {
+ state.loading = "rejected";
+ })
+ .addCase(fetchMyFeed.fulfilled, (state, action) => {
+ state.loading = "fulfilled";
+ action.payload.forEach((item) => {
+ if (!state.myFeed.some((feedItem) => feedItem._id === item._id)) {
+ state.myFeed = [...state.myFeed, ...action.payload];
+ }
+ });
+ })
+ .addCase(fetchMyFeed.pending, (state) => {
+ state.loading = "pending";
+ })
+ .addCase(fetchMyFeed.rejected, (state) => {
+ state.loading = "rejected";
+ })
+ .addCase(fetchOtherFeed.fulfilled, (state, action) => {
+ state.loading = "fulfilled";
+ if (action.payload === "") {
+ state.otherFeed = [];
+ }
+ state.otherFeed = [...state.otherFeed, ...action.payload];
+ })
+ .addCase(fetchOtherFeed.pending, (state) => {
+ state.loading = "pending";
+ })
+ .addCase(fetchOtherFeed.rejected, (state) => {
+ state.loading = "rejected";
+ })
+ .addCase(fetchAllFeed.fulfilled, (state, action) => {
+ state.loading = "fulfilled";
+ state.allFeed = [...state.allFeed, ...action.payload];
+ })
+ .addCase(fetchAllFeed.pending, (state) => {
+ state.loading = "pending";
+ })
+ .addCase(fetchAllFeed.rejected, (state) => {
+ state.loading = "rejected";
+ })
+ .addCase(postBoardFeed.fulfilled, (state, action) => {
+ state.loading = "go";
+ })
+ .addCase(postBoardFeed.pending, (state) => {
+ state.loading = "wait";
+ })
+ .addCase(postBoardFeed.rejected, (state) => {
+ state.loading = "wait";
+ })
+ .addCase(deleteFeed.fulfilled, (state, action) => {
+ state.loading = "fulfilled";
+
+ const deletedFeedId = action.meta.arg;
+
+ state.allFeed = state.allFeed.filter(
+ (feed) => feed._id !== deletedFeedId
+ );
+
+ state.myFeed = state.myFeed.filter(
+ (feed) => feed._id !== deletedFeedId
+ );
+ })
+ .addCase(deleteFeed.pending, (state) => {
+ state.loading = "pending";
+ })
+ .addCase(deleteFeed.rejected, (state) => {
+ state.loading = "rejected";
+ })
+ .addCase(postVoteFeed.fulfilled, (state, action) => {
+ state.loading = "fulfilled";
+ })
+ .addCase(postVoteFeed.pending, (state) => {
+ state.loading = "pending";
+ })
+ .addCase(postVoteFeed.rejected, (state) => {
+ state.loading = "rejected";
+ })
+ .addCase(postVote.fulfilled, (state) => {
+ state.loading = "fulfilled";
+ })
+ .addCase(postVote.pending, (state) => {
+ state.loading = "pending";
+ })
+ .addCase(postVote.rejected, (state) => {
+ state.loading = "rejected";
+ })
+ .addCase(postLike.fulfilled, (state) => {
+ state.loading = "fulfilled";
+ })
+ .addCase(postLike.pending, (state) => {
+ state.loading = "pending";
+ })
+ .addCase(postLike.rejected, (state) => {
+ state.loading = "rejected";
+ })
+ .addCase(postUnlike.fulfilled, (state) => {
+ state.loading = "fulfilled";
+ })
+ .addCase(postUnlike.pending, (state) => {
+ state.loading = "pending";
+ })
+ .addCase(postUnlike.rejected, (state) => {
+ state.loading = "rejected";
+ })
+ .addCase(fetchMyFeedCount.fulfilled, (state, action) => {
+ state.loading = "fulfilled";
+ state.myFeedCount = action.payload.postCount;
+ })
+ .addCase(fetchMyFeedCount.pending, (state) => {
+ state.loading = "pending";
+ })
+ .addCase(fetchMyFeedCount.rejected, (state) => {
+ state.loading = "rejected";
+ })
+ .addCase(fetchMyPageFeedCount.fulfilled, (state, action) => {
+ state.loading = "fulfilled";
+ state.mypageFeedCount = action.payload.postCount;
+ })
+ .addCase(fetchMyPageFeedCount.pending, (state) => {
+ state.loading = "pending";
+ })
+ .addCase(fetchMyPageFeedCount.rejected, (state) => {
+ state.loading = "rejected";
+ });
+ },
+});
+
+export {
+ fetchAFeed,
+ fetchMyFeed,
+ fetchOtherFeed,
+ fetchAllFeed,
+ postBoardFeed,
+ postVoteFeed,
+ postVote,
+ postLike,
+ postUnlike,
+ deleteFeed,
+ fetchMyFeedCount,
+ fetchMyPageFeedCount,
+ postMyProfit,
+};
+export default feedSlice.reducer;
diff --git a/src/store/reducers/Hot/getStockInfo.jsx b/src/store/reducers/Hot/getStockInfo.jsx
new file mode 100644
index 0000000..97020a1
--- /dev/null
+++ b/src/store/reducers/Hot/getStockInfo.jsx
@@ -0,0 +1,51 @@
+import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
+import { getHotStock, getPopularStock } from "../../../lib/apis/hot";
+
+const initialState = {
+ popularData: [],
+ hotData: [],
+ loading: false,
+};
+
+export const getPopularDatas = createAsyncThunk(
+ "chart/getPopularData",
+ async (data, tunkAPI) => {
+ const response = await getPopularStock();
+ return response.data;
+ }
+)
+
+export const getHotDatas = createAsyncThunk(
+ "chart/getHotData",
+ async (data, tunkAPI) => {
+ const response = await getHotStock(data);
+ return response.data;
+ }
+)
+
+const hotStockSlice = createSlice({
+ name: "hotStock",
+ initialState: initialState,
+ reducers: {},
+ extraReducers: (builder) => {
+ builder.addCase(getPopularDatas.fulfilled, (state, action) => {
+ state.popularData = action.payload;
+ }),
+ builder.addCase(getPopularDatas.rejected, (state, action) => {
+ state.popularData = [];
+ }),
+ builder.addCase(getHotDatas.fulfilled, (state, action) => {
+ state.hotData = action.payload;
+ state.loading = false;
+ }),
+ builder.addCase(getHotDatas.pending, (state, action) => {
+ state.loading = true;
+ }),
+ builder.addCase(getHotDatas.rejected, (state, action) => {
+ state.hotData = [];
+ state.loading = false;
+ })
+ },
+});
+
+export default hotStockSlice.reducer;
diff --git a/src/store/reducers/Strategy/getStrategy.jsx b/src/store/reducers/Strategy/getStrategy.jsx
new file mode 100644
index 0000000..397aa85
--- /dev/null
+++ b/src/store/reducers/Strategy/getStrategy.jsx
@@ -0,0 +1,32 @@
+import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
+import { getStrategy } from "../../../lib/apis/strategy";
+
+const initialState = {
+ strategy: {},
+ loading: false,
+};
+
+export const getStrategyDatas = createAsyncThunk(
+ "chart/getStrategyDatas",
+ async (data, tunkAPI) => {
+ const response = await getStrategy();
+ return response.data;
+ }
+)
+
+const strategySlice = createSlice({
+ name: "strategy",
+ initialState: initialState,
+ reducers: {},
+ extraReducers: (builder) => {
+ builder.addCase(getStrategyDatas.pending, (state, action) => {
+ state.loading = true;
+ }),
+ builder.addCase(getStrategyDatas.fulfilled, (state, action) => {
+ state.strategy = action.payload;
+ state.loading = false;
+ })
+ },
+});
+
+export default strategySlice.reducer;
diff --git a/src/store/reducers/Trading/chartValues.jsx b/src/store/reducers/Trading/chartValues.jsx
new file mode 100644
index 0000000..f7c752d
--- /dev/null
+++ b/src/store/reducers/Trading/chartValues.jsx
@@ -0,0 +1,25 @@
+import { createSlice } from "@reduxjs/toolkit";
+
+const initialState = {
+ values: {
+ SMA: [5, 10, 20, 60, 120],
+ WMA: [5, 10, 20, 60, 120],
+ EMA: [5, 10, 20, 60, 120],
+ BBANDS: [5, 2],
+ SAR: [0.02, 0.2],
+ },
+};
+
+const chartValuesSlice = createSlice({
+ name: "chartValues",
+ initialState: initialState,
+ reducers: {
+ setValues: (state, action) => {
+ state.values = { ...state.values, ...action.payload };
+ },
+ },
+ extraReducers: (builder) => {},
+});
+
+export const { setValues } = chartValuesSlice.actions;
+export default chartValuesSlice.reducer;
diff --git a/src/store/reducers/Trading/indicatorValues.jsx b/src/store/reducers/Trading/indicatorValues.jsx
new file mode 100644
index 0000000..8740f77
--- /dev/null
+++ b/src/store/reducers/Trading/indicatorValues.jsx
@@ -0,0 +1,42 @@
+import { createSlice } from "@reduxjs/toolkit";
+
+const initialState = {
+ values: {
+ MACD: [26, 12, 9],
+ STOCHF: [14, 3],
+ STOCH: [14, 3, 3],
+ RSI: [10],
+ CCI: [20],
+ MOM: [10],
+ ROC: [10],
+ AD: [],
+ ATR: [20],
+ MFI: [14],
+ OBV: [],
+ ADOSC: [10, 3],
+ TRIX: [12],
+ WILLR: [14],
+ DX: [14],
+ ADX: [14],
+ ADXR: [14],
+ AROON: [25],
+ AROONOSC: [25],
+ STOCHRSI: [14, 14, 3],
+ ULTOSC: [28, 14, 7],
+ PPO: [26, 12],
+ },
+};
+
+const indicatorValuesSlice = createSlice({
+ name: "indicatorValues",
+ initialState: initialState,
+ reducers: {
+ setValues: (state, action) => {
+ state.values = { ...state.values, ...action.payload };
+ },
+ },
+ extraReducers: (builder) => {},
+});
+
+export const { setValues } = indicatorValuesSlice.actions;
+export default indicatorValuesSlice.reducer;
diff --git a/src/store/reducers/Trading/search.jsx b/src/store/reducers/Trading/search.jsx
new file mode 100644
index 0000000..bf0b3db
--- /dev/null
+++ b/src/store/reducers/Trading/search.jsx
@@ -0,0 +1,128 @@
+import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
+import {
+ postSearch as reqPostSearch,
+ postSearchUser as reqPostSearchUser,
+ postLikeStock as reqPostLikeStock,
+ fetchLikeStock as reqFetchLikeStock,
+ fetchLikeStockArr as reqFetchLikeStockArr,
+} from "~/lib/apis/search";
+
+const initialState = {
+ searchResults: [],
+ favoriteArr: [],
+ myFavoriteArr: [],
+ loading: "idle",
+};
+
+const postSearch = createAsyncThunk(
+ "search/postSearch",
+ async ({ searchQuery }, thunkAPI) => {
+ const response = await reqPostSearch(searchQuery);
+ return response;
+ }
+);
+
+const postSearchUser = createAsyncThunk(
+ "search/postSearchUser",
+ async ({ searchQuery, userId }, thunkAPI) => {
+ const response = await reqPostSearchUser(searchQuery, userId);
+ return response;
+ }
+);
+
+const postLikeStock = createAsyncThunk(
+ "search/postLikeStock",
+ async ({ likeStock, userId }, thunkAPI) => {
+ const response = await reqPostLikeStock(likeStock, userId);
+ return response;
+ }
+);
+
+const fetchLikeStock = createAsyncThunk(
+ "search/fetchLikeStock",
+ async (userId, thunkAPI) => {
+ const response = await reqFetchLikeStock(userId);
+ return response;
+ }
+);
+
+const fetchLikeStockArr = createAsyncThunk(
+ "search/fetchLikeStockArr",
+ async (userId, thunkAPI) => {
+ const response = await reqFetchLikeStockArr(userId);
+ return response;
+ }
+);
+
+const searchSlice = createSlice({
+ name: "search",
+ initialState: initialState,
+ reducers: {
+ setFavoriteArr: (state, action) => {
+ state.favoriteArr = action.payload;
+ },
+ },
+ extraReducers: (builder) => {
+ builder
+ .addCase(postSearch.fulfilled, (state, action) => {
+ state.searchResults = action.payload;
+ state.loading = "idle";
+ })
+ .addCase(postSearch.pending, (state) => {
+ state.loading = "pending";
+ })
+ .addCase(postSearch.rejected, (state) => {
+ state.loading = "rejected";
+ })
+ .addCase(postSearchUser.fulfilled, (state, action) => {
+ state.searchResults = action.payload;
+ state.loading = "idle";
+ })
+ .addCase(postSearchUser.pending, (state) => {
+ state.loading = "pending";
+ })
+ .addCase(postSearchUser.rejected, (state) => {
+ state.loading = "rejected";
+ })
+ .addCase(postLikeStock.fulfilled, (state, action) => {
+ state.favoriteArr = action.payload;
+ state.loading = "idle";
+ })
+ .addCase(postLikeStock.pending, (state) => {
+ state.loading = "pending";
+ })
+ .addCase(postLikeStock.rejected, (state) => {
+ state.loading = "rejected";
+ })
+ .addCase(fetchLikeStock.fulfilled, (state, action) => {
+ state.myFavoriteArr = action.payload;
+ state.loading = "idle";
+ })
+ .addCase(fetchLikeStock.pending, (state) => {
+ state.loading = "pending";
+ })
+ .addCase(fetchLikeStock.rejected, (state) => {
+ state.loading = "rejected";
+ })
+ .addCase(fetchLikeStockArr.fulfilled, (state, action) => {
+ state.favoriteArr = action.payload;
+ state.loading = "idle";
+ })
+ .addCase(fetchLikeStockArr.pending, (state) => {
+ state.loading = "pending";
+ })
+ .addCase(fetchLikeStockArr.rejected, (state) => {
+ state.loading = "rejected";
+ });
+ },
+});
+
+export const { setFavoriteArr } = searchSlice.actions;
+export {
+ postSearch,
+ postSearchUser,
+ postLikeStock,
+ fetchLikeStock,
+ fetchLikeStockArr,
+};
+export default searchSlice.reducer;
diff --git a/src/store/reducers/Trading/trading.jsx b/src/store/reducers/Trading/trading.jsx
new file mode 100644
index 0000000..6134b23
--- /dev/null
+++ b/src/store/reducers/Trading/trading.jsx
@@ -0,0 +1,56 @@
+import { createSlice } from "@reduxjs/toolkit";
+
+const initialState = {
+ selectedTab: "매수",
+ selectedPrice: null,
+ selectedQuantity: 0,
+ scrollPosition: 100,
+ orderType: "지정가", // 시장가, 지정가 가격 input
+ isNew: false,
+};
+
+const tradingSlice = createSlice({
+ name: "trading",
+ initialState: initialState,
+ reducers: {
+ setSelectedTab(state, action) {
+ state.selectedTab = action.payload;
+ },
+ setSelectedPrice(state, action) {
+ state.selectedPrice = action.payload;
+ },
+ setSelectedQuantity(state, action) {
+ state.selectedQuantity = action.payload;
+ },
+ setScrollPosition(state, action) {
+ state.scrollPosition = action.payload;
+ },
+ setOrderType(state, action) {
+ state.orderType = action.payload;
+ },
+ increaseSelectedQuantity(state) {
+ state.selectedQuantity += 1;
+ },
+ setIsNew(state, action) {
+ state.isNew = action.payload;
+ },
+ decreaseSelectedQuantity(state) {
+ if (state.selectedQuantity > 0) {
+ state.selectedQuantity -= 1;
+ }
+ },
+ },
+ extraReducers: (builder) => {},
+});
+
+export const {
+ setSelectedPrice,
+ setScrollPosition,
+ setSelectedTab,
+ setOrderType,
+ setSelectedQuantity,
+ increaseSelectedQuantity,
+ decreaseSelectedQuantity,
+ setIsNew,
+} = tradingSlice.actions;
+export default tradingSlice.reducer;
diff --git a/src/store/reducers/User/friend.jsx b/src/store/reducers/User/friend.jsx
new file mode 100644
index 0000000..2125792
--- /dev/null
+++ b/src/store/reducers/User/friend.jsx
@@ -0,0 +1,247 @@
+import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
+import {
+ fetchFriends as reqFetchFriends,
+ fetchPendingFriends as reqFetchPendingFriends,
+ fetchFriendsState as reqFetchFriendsState,
+ fetchFriendCount as reqFetchFriendCount,
+ postFriendRequest as reqPostFriendRequest,
+ postFriendAccept as reqPostFriendAccept,
+ deleteFriend as reqDeleteFriend,
+ deleteFriendRequest as reqDeleteFriendRequest,
+ deleteMyRequest as reqDeleteMyRequest,
+ postSearchFriend as reqPostSearchFriend,
+} from "~/lib/apis/friend";
+
+const initialState = {
+ friends: [],
+ pendingFriends: [],
+ friendsState: null,
+ friendCount: null,
+ friendNickname: null,
+ friendId: null,
+ friendSearch: [],
+ loading: "idle",
+};
+
+const fetchFriends = createAsyncThunk(
+ "friend/fetchFriends",
+ async (data, thunkAPI) => {
+ const response = await reqFetchFriends();
+ return response;
+ }
+);
+
+const fetchPendingFriends = createAsyncThunk(
+ "friend/fetchPendingFriends",
+ async (data, thunkAPI) => {
+ const response = await reqFetchPendingFriends();
+ return response;
+ }
+);
+
+const fetchFriendsState = createAsyncThunk(
+ "friend/fetchFriendsState",
+ async (friendId, thunkAPI) => {
+ const response = await reqFetchFriendsState(friendId);
+ return response;
+ }
+);
+
+const fetchFriendCount = createAsyncThunk(
+ "friend/fetchFriendCount",
+ async (userId, thunkAPI) => {
+ const response = await reqFetchFriendCount(userId);
+ return response;
+ }
+);
+
+const postFriendRequest = createAsyncThunk(
+ "friend/postFriendRequest",
+ async (friendId, thunkAPI) => {
+ const response = await reqPostFriendRequest(friendId);
+ return response;
+ }
+);
+
+const postFriendAccept = createAsyncThunk(
+ "friend/postFriendAccept",
+ async (friendId, thunkAPI) => {
+ const response = await reqPostFriendAccept(friendId);
+ return response;
+ }
+);
+
+const deleteFriend = createAsyncThunk(
+ "friend/deleteFriend",
+ async (friendId, thunkAPI) => {
+ const response = await reqDeleteFriend(friendId);
+ return response;
+ }
+);
+
+const deleteFriendRequest = createAsyncThunk(
+ "friend/deleteFriendRequest",
+ async (friendId, thunkAPI) => {
+ const response = await reqDeleteFriendRequest(friendId);
+ return response;
+ }
+);
+
+const deleteMyRequest = createAsyncThunk(
+ "friend/deleteMyRequest",
+ async (friendId, thunkAPI) => {
+ const response = await reqDeleteMyRequest(friendId);
+ return response;
+ }
+);
+
+const postSearchFriend = createAsyncThunk(
+ "friend/postSearchFriend",
+ async ({ nickname }, thunkAPI) => {
+ const response = await reqPostSearchFriend(nickname);
+ return response;
+ }
+);
+
+const friendSlice = createSlice({
+ name: "friend",
+ initialState: initialState,
+ reducers: {
+ setFriendNickname: (state, action) => {
+ state.friendNickname = action.payload;
+ },
+ setFriendId: (state, action) => {
+ state.friendId = action.payload;
+ },
+ },
+ extraReducers: (builder) => {
+ builder
+ .addCase(fetchFriends.fulfilled, (state, action) => {
+ state.loading = "fulfilled";
+ state.friends = action.payload;
+ })
+ .addCase(fetchFriends.pending, (state) => {
+ state.loading = "pending";
+ })
+ .addCase(fetchFriends.rejected, (state) => {
+ state.loading = "rejected";
+ })
+ .addCase(fetchPendingFriends.fulfilled, (state, action) => {
+ state.loading = "fulfilled";
+ state.pendingFriends = action.payload;
+ })
+ .addCase(fetchPendingFriends.pending, (state) => {
+ state.loading = "pending";
+ })
+ .addCase(fetchPendingFriends.rejected, (state) => {
+ state.loading = "rejected";
+ })
+ .addCase(fetchFriendsState.fulfilled, (state, action) => {
+ state.loading = "fulfilled";
+ state.friendsState = action.payload;
+ })
+ .addCase(fetchFriendsState.pending, (state) => {
+ state.loading = "pending";
+ })
+ .addCase(fetchFriendsState.rejected, (state) => {
+ state.loading = "rejected";
+ })
+ .addCase(fetchFriendCount.fulfilled, (state, action) => {
+ state.loading = "fulfilled";
+ state.friendCount = action.payload.friendCount;
+ })
+ .addCase(fetchFriendCount.pending, (state) => {
+ state.loading = "pending";
+ })
+ .addCase(fetchFriendCount.rejected, (state) => {
+ state.loading = "rejected";
+ })
+ .addCase(postFriendRequest.fulfilled, (state, action) => {
+ state.loading = "fulfilled";
+ state.friendsState = { friendId: action.meta.arg, state: "requested" };
+ })
+ .addCase(postFriendRequest.pending, (state) => {
+ state.loading = "pending";
+ })
+ .addCase(postFriendRequest.rejected, (state) => {
+ state.loading = "rejected";
+ })
+ .addCase(postFriendAccept.fulfilled, (state, action) => {
+ state.loading = "fulfilled";
+ const acceptedFriend = state.pendingFriends.find(
+ (friend) => friend._id === action.meta.arg
+ );
+ if (acceptedFriend) {
+ state.friends = [...state.friends, acceptedFriend];
+ }
+ state.pendingFriends = state.pendingFriends.filter(
+ (friend) => friend._id !== action.meta.arg
+ );
+ })
+ .addCase(postFriendAccept.pending, (state) => {
+ state.loading = "pending";
+ })
+ .addCase(postFriendAccept.rejected, (state) => {
+ state.loading = "rejected";
+ })
+ .addCase(deleteFriend.fulfilled, (state, action) => {
+ state.loading = "fulfilled";
+ state.friends = state.friends.filter(
+ (friend) => friend._id !== action.meta.arg
+ );
+ })
+ .addCase(deleteFriend.pending, (state) => {
+ state.loading = "pending";
+ })
+ .addCase(deleteFriend.rejected, (state) => {
+ state.loading = "rejected";
+ })
+ .addCase(deleteFriendRequest.fulfilled, (state, action) => {
+ state.loading = "fulfilled";
+ state.pendingFriends = state.pendingFriends.filter(
+ (friend) => friend._id !== action.meta.arg
+ );
+ })
+ .addCase(deleteFriendRequest.pending, (state) => {
+ state.loading = "pending";
+ })
+ .addCase(deleteFriendRequest.rejected, (state) => {
+ state.loading = "rejected";
+ })
+ .addCase(deleteMyRequest.fulfilled, (state, action) => {
+ state.loading = "fulfilled";
+ state.friendsState = { friendId: action.meta.arg, state: "" };
+ })
+ .addCase(deleteMyRequest.pending, (state) => {
+ state.loading = "pending";
+ })
+ .addCase(deleteMyRequest.rejected, (state) => {
+ state.loading = "rejected";
+ })
+ .addCase(postSearchFriend.fulfilled, (state, action) => {
+ state.loading = "fulfilled";
+ state.friendSearch = action.payload;
+ })
+ .addCase(postSearchFriend.pending, (state) => {
+ state.loading = "pending";
+ })
+ .addCase(postSearchFriend.rejected, (state) => {
+ state.loading = "rejected";
+ });
+ },
+});
+
+export const { setFriendNickname, setFriendId } = friendSlice.actions;
+export {
+ fetchFriends,
+ fetchPendingFriends,
+ fetchFriendsState,
+ fetchFriendCount,
+ postFriendRequest,
+ postFriendAccept,
+ deleteFriend,
+ deleteFriendRequest,
+ deleteMyRequest,
+ postSearchFriend,
+};
+export default friendSlice.reducer;
diff --git a/src/store/reducers/User/order.jsx b/src/store/reducers/User/order.jsx
new file mode 100644
index 0000000..f098861
--- /dev/null
+++ b/src/store/reducers/User/order.jsx
@@ -0,0 +1,69 @@
+import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
+import {
+ fetchMyOrder as reqFetchMyOrder,
+ getOrderHistory as reqGetOrderHistory,
+} from "../../../lib/apis/order";
+
+const initialState = {
+ myMoney: {},
+ mystocks: [],
+ reservedHistory: [],
+ completedHistory: [],
+ uniqueCompletedHistory: [],
+ loading: "idle",
+};
+
+export const fetchMyOrder = createAsyncThunk(
+ "order/fetchMyOrder",
+ async (data, thunkAPI) => {
+ const response = await reqFetchMyOrder();
+ return response;
+ }
+);
+
+export const getOrderHistory = createAsyncThunk(
+ "order/getOrderHistory",
+ async ({ code, userId }, thunkAPI) => {
+ const response = await reqGetOrderHistory(code, userId);
+ return response;
+ }
+);
+
+const orderSlice = createSlice({
+ name: "order",
+ initialState: initialState,
+ reducers: {
+ setUniqueCompletedHistory: (state, action) => {
+ state.uniqueCompletedHistory = action.payload;
+ },
+ },
+ extraReducers: (builder) => {
+ builder
+ .addCase(fetchMyOrder.fulfilled, (state, action) => {
+ state.loading = "fulfilled";
+ state.myMoney = action.payload.myMoney[0];
+ state.mystocks = action.payload.mystocks;
+ })
+ .addCase(fetchMyOrder.pending, (state) => {
+ state.loading = "pending";
+ })
+ .addCase(fetchMyOrder.rejected, (state) => {
+ state.loading = "rejected";
+ })
+ .addCase(getOrderHistory.fulfilled, (state, action) => {
+ state.loading = "fulfilled";
+ state.reservedHistory = action.payload.reservedHistory;
+ state.completedHistory = action.payload.completedHistory;
+ })
+ .addCase(getOrderHistory.pending, (state) => {
+ state.loading = "pending";
+ })
+ .addCase(getOrderHistory.rejected, (state) => {
+ state.loading = "rejected";
+ });
+ },
+});
+
+export const { setUniqueCompletedHistory } = orderSlice.actions;
+
+export default orderSlice.reducer;
diff --git a/src/store/reducers/User/user.jsx b/src/store/reducers/User/user.jsx
new file mode 100644
index 0000000..66f26c7
--- /dev/null
+++ b/src/store/reducers/User/user.jsx
@@ -0,0 +1,55 @@
+import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
+import { login, logout, signup } from "../../../lib/apis/user";
+import { removeCookie, setCookie } from "../../../lib/apis/cookie";
+
+const initialState = {
+ user: {},
+ loading: "idle",
+};
+
+export const postSignup = createAsyncThunk(
+ "user/signup",
+ async (data, thunkAPI) => {
+ const response = await signup(data.email, data.password, data.nickname);
+ return response;
+ }
+);
+
+export const postLogin = createAsyncThunk(
+ "user/login",
+ async (data, thunkAPI) => {
+ const response = await login(data.email, data.password);
+ setCookie("token", response.data.token, {
+ path: "/",
+ // secure: true,
+ });
+ return response;
+ }
+);
+
+export const postLogout = createAsyncThunk(
+ "user/logout",
+ async (data, thunkAPI) => {
+ const response = await logout(data);
+ removeCookie("token");
+
+ return response;
+ }
+);
+
+const userSlice = createSlice({
+ name: "user",
+ initialState: initialState,
+ reducers: {
+ setUser(state, action) {
+ state.user = action.payload;
+ state.loading = "fulfilled";
+ },
+ },
+ extraReducers: (builder) => {},
+});
+
+const { setUser } = userSlice.actions;
+export { setUser };
+
+export default userSlice.reducer;
diff --git a/src/store/store.js b/src/store/store.js
new file mode 100644
index 0000000..efe4a1e
--- /dev/null
+++ b/src/store/store.js
@@ -0,0 +1,84 @@
+import { combineReducers, configureStore } from "@reduxjs/toolkit";
+import storage from "redux-persist/lib/storage";
+import {
+ FLUSH,
+ PAUSE,
+ PERSIST,
+ PURGE,
+ REGISTER,
+ REHYDRATE,
+ persistReducer,
+ persistStore,
+} from "redux-persist";
+import logger from "redux-logger";
+import chartValuesReducer from "./reducers/Trading/chartValues";
+import indicatorValuesReducer from "./reducers/Trading/indicatorValues";
+import chartReducer from "./reducers/Chart/chart.jsx";
+import companyReducer from "./reducers/Chart/clickCompany.jsx";
+import clickIndicatorsReducer from "./reducers/Chart/Indicators/clickIndicators.jsx";
+import getChartIndicatorReducer from "./reducers/Chart/Indicators/chart.jsx";
+import getSubIndicatorReducer from "./reducers/Chart/Indicators/sub.jsx";
+import searchReducer from "./reducers/Trading/search";
+import userReducer from "./reducers/User/user";
+import feedReducer from "./reducers/Feed/feed";
+import commentReducer from "./reducers/Feed/comment";
+import hotStockReducer from "./reducers/Hot/getStockInfo.jsx";
+import strategyReducer from "./reducers/Strategy/getStrategy.jsx";
+import tradingReducer from "./reducers/Trading/trading.jsx";
+import kospiKosdaqReducer from "./reducers/Chart/kospiKosdaq.jsx";
+import friendReducer from "./reducers/User/friend";
+import orderReducer from "./reducers/User/order";
+
+const rootPersistConfig = {
+ key: "root",
+ storage: storage,
+ whitelist: [
+ "user",
+ "chartValues",
+ "indicatorValues",
+ "search",
+ "chart",
+ "clickIndicator",
+ "getChartIndicator",
+ "getSubIndicator",
+ "company",
+ "user",
+ "order",
+ ],
+};
+
+const rootReducer = persistReducer(
+ rootPersistConfig,
+ combineReducers({
+ trading: tradingReducer,
+ chart: chartReducer,
+ company: companyReducer,
+ chartValues: chartValuesReducer,
+ indicatorValues: indicatorValuesReducer,
+ clickIndicator: clickIndicatorsReducer,
+ getChartIndicator: getChartIndicatorReducer,
+ getSubIndicator: getSubIndicatorReducer,
+ search: searchReducer,
+ user: userReducer,
+ feed: feedReducer,
+ comment: commentReducer,
+ hot: hotStockReducer,
+ strategy: strategyReducer,
+ kospiKosdaq: kospiKosdaqReducer,
+ friend: friendReducer,
+ order: orderReducer,
+ })
+);
+// const myMiddlewares = [logger];
+export const store = configureStore({
+ reducer: rootReducer,
+ middleware: (getDefaultMiddleware) =>
+ getDefaultMiddleware({
+ serializableCheck: {
+ ignoreActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
+ },
+ }),
+ // .concat(myMiddlewares),
+});
+
+export const persistor = persistStore(store);
diff --git a/src/store/webSocket/nowPrice.js b/src/store/webSocket/nowPrice.js
new file mode 100644
index 0000000..61d90b3
--- /dev/null
+++ b/src/store/webSocket/nowPrice.js
@@ -0,0 +1,32 @@
+import io from "socket.io-client";
+const SERVER_URL = "";
+
+const socket = io(SERVER_URL);
+
+export const joinRoom = (stockCode) => {
+ if (stockCode) {
+ socket.emit("joinRoom", stockCode);
+ }
+};
+
+export const leaveRoom = (stockCode) => {
+ if (stockCode) {
+ socket.emit("leaveRoom", stockCode);
+ }
+};
+
+export const subscribeNowPrice = (callback) => {
+ socket.on("nowPrice", (message) => {
+ callback(message);
+ });
+
+ return () => socket.off("nowPrice");
+};
+
+export const subscribeAskPrice = (callback) => {
+ socket.on("askPrice", (message) => {
+ callback(message);
+ });
+
+ return () => socket.off("askPrice");
+};
diff --git a/src/style/GlobalStyle.js b/src/style/GlobalStyle.js
new file mode 100644
index 0000000..feb6fcb
--- /dev/null
+++ b/src/style/GlobalStyle.js
@@ -0,0 +1,119 @@
+import styled from "styled-components";
+
+// Sidebar
+export const Container = styled.div`
+ display: flex;
+ flex-direction: column;
+ width: 400px;
+ height: 100%;
+ position: relative;
+ overflow: hidden;
+ background-color: #f3f3f3;
+`;
+
+// FeedShow
+export const FeedWrapper = styled.div`
+ display: flex;
+ flex-direction: column;
+ background-color: white;
+ margin-bottom: 5px;
+`;
+
+export const UserDiv = styled.div`
+ margin: 20px 25px 0px 25px;
+ width: 80%;
+`;
+
+export const DeleteDiv = styled.div`
+ color: gray;
+ width: 20%;
+ cursor: pointer;
+ &:hover {
+ color: black;
+ }
+`;
+
+export const UserNickname = styled.div`
+ font-size: 16px;
+ font-weight: 600;
+ line-height: normal;
+ margin-bottom: 5px;
+ cursor: pointer;
+`;
+
+export const DateDiv = styled.div`
+ color: #c1c1c1;
+ font-size: 13px;
+ font-style: normal;
+ font-weight: 600;
+ line-height: normal;
+`;
+
+export const BodyWrapper = styled.div`
+ display: flex;
+ flex-direction: column;
+ margin: 15px 25px 0px 25px;
+`;
+
+export const BodyCenter = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+`;
+
+export const BodyDiv = styled.div`
+ margin-bottom: 15px;
+ font-weight: ${(props) => props.$weight || "400"};
+`;
+
+export const BottomWrapper = styled.div`
+ display: flex;
+ margin: 20px 25px 0px 25px;
+ padding: 15px 0px;
+ border-top: 1px solid #dadada;
+ gap: 20px;
+`;
+
+export const IconDiv = styled.div`
+ display: flex;
+ align-items: center;
+ width: 50px;
+`;
+
+export const StockWrapper = styled.div`
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ width: 95%;
+ padding: 11px 22px;
+ border-radius: 10px;
+ background-color: ${(props) =>
+ props.$buy === "sell"
+ ? "#FFE3D7"
+ : props.$buy === "returns"
+ ? "#EFEFEF"
+ : "#BEE4FF"};
+`;
+
+export const StockDiv = styled.div`
+ margin-left: ${(props) => props.$margin || "10px"};
+ color: #000;
+ font-size: 17px;
+ font-style: normal;
+ font-weight: 550;
+`;
+
+export const CommentDiv = styled.div`
+ display: ${(props) => (props.$check == true ? "none" : "flex")};
+`;
+
+//common
+export const Wrapper = styled.div`
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ background-color: white;
+ height: 100vh;
+ gap: 10px;
+`;
diff --git a/src/util/getLogoFileName.jsx b/src/util/getLogoFileName.jsx
new file mode 100644
index 0000000..edb1e6c
--- /dev/null
+++ b/src/util/getLogoFileName.jsx
@@ -0,0 +1,27 @@
+import default_Img from "../../public/icon/logo.svg";
+
+export const getLogoFileName = (name, code) => {
+ if (name.includes("스팩")) {
+ return "SPAC_230706";
+ } else if (name.includes("ETN")) {
+ return "ETN_230706";
+ } else if (
+ name.includes("KODEX") ||
+ name.includes("KOSEF") ||
+ name.includes("KoAct") ||
+ name.includes("TIGER") ||
+ name.includes("ACE") ||
+ name.includes("ARIRANG") ||
+ name.includes("합성 H") ||
+ name.includes("HANARO") ||
+ name.includes("SOL")
+ ) {
+ return "ETF_230706";
+ } else {
+ return `kr/${code}`;
+ }
+};
+
+export const onErrorImg = (e) => {
+ e.target.src = default_Img;
+};
diff --git a/vite.config.js b/vite.config.js
new file mode 100644
index 0000000..bb873ea
--- /dev/null
+++ b/vite.config.js
@@ -0,0 +1,23 @@
+import { defineConfig } from "vite";
+import react from "@vitejs/plugin-react";
+
+export default defineConfig({
+ plugins: [react()],
+ server: {
+ proxy: {
+ "/api": "", // 서버 주소 기입
+ },
+ },
+ resolve: {
+ alias: [
+ // 절대경로로 접근
+ { find: "~/components", replacement: "/src/components" },
+ { find: "~/lib", replacement: "/src/lib" },
+ { find: "~/router", replacement: "/src/router" },
+ { find: "~/routes", replacement: "/src/routes" },
+ { find: "~/store", replacement: "/src/store" },
+ { find: "~/assets", replacement: "/src/assets" },
+ { find: "~/util", replacement: "/src/util" },
+ ],
+ },
+});