diff --git a/package-lock.json b/package-lock.json index 43e807c..da90db4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,13 +8,32 @@ "name": "react-messenger-15th", "version": "0.1.0", "dependencies": { + "@reduxjs/toolkit": "^1.8.1", "@testing-library/jest-dom": "^5.16.4", "@testing-library/react": "^13.1.1", "@testing-library/user-event": "^13.5.0", + "@types/jest": "^27.5.0", + "@types/node": "^17.0.31", + "@types/react": "^18.0.8", + "@types/react-dom": "^18.0.3", + "@types/react-redux": "^7.1.24", + "@types/redux": "^3.6.0", + "@types/redux-thunk": "^2.1.0", "react": "^18.1.0", "react-dom": "^18.1.0", + "react-icons": "^4.3.1", + "react-redux": "^8.0.1", + "react-router-dom": "^6.3.0", "react-scripts": "5.0.1", + "redux-thunk": "^2.4.1", + "styled-components": "^5.3.5", + "typescript": "^4.6.4", + "uuid": "^8.3.2", "web-vitals": "^2.1.4" + }, + "devDependencies": { + "@types/styled-components": "^5.1.25", + "@types/uuid": "^8.3.4" } }, "node_modules/@ampproject/remapping": { @@ -1972,6 +1991,29 @@ "postcss": "^8.3" } }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.1.2.tgz", + "integrity": "sha512-3QnhqeL+WW88YjYbQL5gUIkthuMw7a0NGbZ7wfFVk2kg/CK5w8w5FFa0RzWjyY1+sujN0NWbtSHH6OJmWHtJpQ==", + "dependencies": { + "@emotion/memoize": "^0.7.4" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.5.tgz", + "integrity": "sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ==" + }, + "node_modules/@emotion/stylis": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", + "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==" + }, + "node_modules/@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + }, "node_modules/@eslint/eslintrc": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.2.tgz", @@ -2841,6 +2883,29 @@ } } }, + "node_modules/@reduxjs/toolkit": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.8.1.tgz", + "integrity": "sha512-Q6mzbTpO9nOYRnkwpDlFOAbQnd3g7zj7CtHAZWz5SzE5lcV97Tf8f3SzOO8BoPOMYBFgfZaqTUZqgGu+a0+Fng==", + "dependencies": { + "immer": "^9.0.7", + "redux": "^4.1.2", + "redux-thunk": "^2.4.1", + "reselect": "^4.1.5" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18", + "react-redux": "^7.2.1 || ^8.0.0-beta" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, "node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -3515,6 +3580,15 @@ "@types/node": "*" } }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "node_modules/@types/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", @@ -3550,9 +3624,9 @@ } }, "node_modules/@types/jest": { - "version": "27.4.1", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.4.1.tgz", - "integrity": "sha512-23iPJADSmicDVrWk+HT58LMJtzLAnB2AgIzplQuq/bSrGaxCrlvRFjGbXmamnnk/mAmCdLStiGqggu28ocUyiw==", + "version": "27.5.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.5.0.tgz", + "integrity": "sha512-9RBFx7r4k+msyj/arpfaa0WOOEcaAZNmN+j80KFbFCoSqCJGHTz7YMAMGQW9Xmqm5w6l5c25vbSjMwlikJi5+g==", "dependencies": { "jest-matcher-utils": "^27.0.0", "pretty-format": "^27.0.0" @@ -3574,9 +3648,9 @@ "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" }, "node_modules/@types/node": { - "version": "17.0.30", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.30.tgz", - "integrity": "sha512-oNBIZjIqyHYP8VCNAV9uEytXVeXG2oR0w9lgAXro20eugRQfY002qr3CUl6BAe+Yf/z3CRjPdz27Pu6WWtuSRw==" + "version": "17.0.31", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.31.tgz", + "integrity": "sha512-AR0x5HbXGqkEx9CadRH3EBYx/VkiUgZIhP4wvPn/+5KIsgpNoyFaRlVe0Zlx9gRtg8fA06a9tskE2MSN7TcG4Q==" }, "node_modules/@types/parse-json": { "version": "4.0.0", @@ -3626,6 +3700,35 @@ "@types/react": "*" } }, + "node_modules/@types/react-redux": { + "version": "7.1.24", + "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.24.tgz", + "integrity": "sha512-7FkurKcS1k0FHZEtdbbgN8Oc6b+stGSfZYjQGicofJ0j4U0qIn/jaSvnP2pLwZKiai3/17xqqxkkrxTgN8UNbQ==", + "dependencies": { + "@types/hoist-non-react-statics": "^3.3.0", + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0", + "redux": "^4.0.0" + } + }, + "node_modules/@types/redux": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@types/redux/-/redux-3.6.0.tgz", + "integrity": "sha1-8evh5UEVGAcuT9/KXHbhbnTBOZo=", + "deprecated": "This is a stub types definition for Redux (https://github.com/reactjs/redux). Redux provides its own type definitions, so you don't need @types/redux installed!", + "dependencies": { + "redux": "*" + } + }, + "node_modules/@types/redux-thunk": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/redux-thunk/-/redux-thunk-2.1.0.tgz", + "integrity": "sha1-vCtulylhgxr7gqm/TwZybjUflBY=", + "deprecated": "This is a stub types definition for Redux Thunk (https://github.com/gaearon/redux-thunk). Redux Thunk provides its own type definitions, so you don't need @types/redux-thunk installed!", + "dependencies": { + "redux-thunk": "*" + } + }, "node_modules/@types/resolve": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", @@ -3674,6 +3777,17 @@ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==" }, + "node_modules/@types/styled-components": { + "version": "5.1.25", + "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.25.tgz", + "integrity": "sha512-fgwl+0Pa8pdkwXRoVPP9JbqF0Ivo9llnmsm+7TCI330kbPIFd9qv1Lrhr37shf4tnxCOSu+/IgqM7uJXLWZZNQ==", + "dev": true, + "dependencies": { + "@types/hoist-non-react-statics": "*", + "@types/react": "*", + "csstype": "^3.0.2" + } + }, "node_modules/@types/testing-library__jest-dom": { "version": "5.14.3", "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.3.tgz", @@ -3687,6 +3801,17 @@ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz", "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==" }, + "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/uuid": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", + "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==", + "dev": true + }, "node_modules/@types/ws": { "version": "8.5.3", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", @@ -4702,6 +4827,26 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/babel-plugin-styled-components": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.7.tgz", + "integrity": "sha512-i7YhvPgVqRKfoQ66toiZ06jPNA3p6ierpfUuEWxNF+fV27Uv5gxBkf8KZLHUCc1nFA9j6+80pYoIpqCeyW3/bA==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.16.0", + "@babel/helper-module-imports": "^7.16.0", + "babel-plugin-syntax-jsx": "^6.18.0", + "lodash": "^4.17.11", + "picomatch": "^2.3.0" + }, + "peerDependencies": { + "styled-components": ">= 2" + } + }, + "node_modules/babel-plugin-syntax-jsx": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=" + }, "node_modules/babel-plugin-transform-react-remove-prop-types": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz", @@ -5016,6 +5161,11 @@ "node": ">= 6" } }, + "node_modules/camelize": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", + "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=" + }, "node_modules/caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", @@ -5482,6 +5632,14 @@ "postcss": "^8.4" } }, + "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": "sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU=", + "engines": { + "node": ">=4" + } + }, "node_modules/css-declaration-sorter": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.2.2.tgz", @@ -5663,6 +5821,16 @@ "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" }, + "node_modules/css-to-react-native": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.0.0.tgz", + "integrity": "sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ==", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "node_modules/css-tree": { "version": "1.0.0-alpha.37", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", @@ -7990,6 +8158,27 @@ "he": "bin/he" } }, + "node_modules/history": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz", + "integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==", + "dependencies": { + "@babel/runtime": "^7.7.6" + } + }, + "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/hoist-non-react-statics/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/hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -13406,11 +13595,62 @@ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==" }, + "node_modules/react-icons": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.3.1.tgz", + "integrity": "sha512-cB10MXLTs3gVuXimblAdI71jrJx8njrJZmNMEMC+sQu5B/BIOmlsAjskdqpn81y8UBVEGuHODd7/ci5DvoSzTQ==", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, + "node_modules/react-redux": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.1.tgz", + "integrity": "sha512-LMZMsPY4DYdZfLJgd7i79n5Kps5N9XVLCJJeWAaPYTV+Eah2zTuBjTxKtNEbjiyitbq80/eIkm55CYSLqAub3w==", + "dependencies": { + "@babel/runtime": "^7.12.1", + "@types/hoist-non-react-statics": "^3.3.1", + "@types/use-sync-external-store": "^0.0.3", + "hoist-non-react-statics": "^3.3.2", + "react-is": "^18.0.0", + "use-sync-external-store": "^1.0.0" + }, + "peerDependencies": { + "@types/react": "^16.8 || ^17.0 || ^18.0", + "@types/react-dom": "^16.8 || ^17.0 || ^18.0", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0", + "react-native": ">=0.59", + "redux": "^4" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/react-redux/node_modules/react-is": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz", + "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==" + }, "node_modules/react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", @@ -13419,6 +13659,30 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.3.0.tgz", + "integrity": "sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ==", + "dependencies": { + "history": "^5.2.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.3.0.tgz", + "integrity": "sha512-uaJj7LKytRxZNQV8+RbzJWnJ8K2nPsOOEuX7aQstlMZKQT0164C+X2w6bnkqU3sjtLvpd5ojrezAyfZ1+0sStw==", + "dependencies": { + "history": "^5.2.0", + "react-router": "6.3.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", @@ -13549,6 +13813,22 @@ "node": ">=8" } }, + "node_modules/redux": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.0.tgz", + "integrity": "sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==", + "dependencies": { + "@babel/runtime": "^7.9.2" + } + }, + "node_modules/redux-thunk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.1.tgz", + "integrity": "sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==", + "peerDependencies": { + "redux": "^4" + } + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -13691,6 +13971,11 @@ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" }, + "node_modules/reselect": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.5.tgz", + "integrity": "sha512-uVdlz8J7OO+ASpBYoz1Zypgx0KasCY20H+N8JD13oUMtPvSHQuscrHop4KbXrbsBcdB9Ds7lVK7eRkBIfO43vQ==" + }, "node_modules/resolve": { "version": "1.22.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", @@ -14172,6 +14457,11 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "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", @@ -14569,6 +14859,36 @@ "webpack": "^5.0.0" } }, + "node_modules/styled-components": { + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.5.tgz", + "integrity": "sha512-ndETJ9RKaaL6q41B69WudeqLzOpY1A/ET/glXkNZ2T7dPjPqpPCXXQjDFYZWwNnE5co0wX+gTCqx9mfxTmSIPg==", + "hasInstallScript": true, + "dependencies": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/traverse": "^7.4.5", + "@emotion/is-prop-valid": "^1.1.0", + "@emotion/stylis": "^0.8.4", + "@emotion/unitless": "^0.7.4", + "babel-plugin-styled-components": ">= 1.12.0", + "css-to-react-native": "^3.0.0", + "hoist-non-react-statics": "^3.0.0", + "shallowequal": "^1.1.0", + "supports-color": "^5.5.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0", + "react-is": ">= 16.8.0" + } + }, "node_modules/stylehacks": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.0.tgz", @@ -15131,7 +15451,6 @@ "version": "4.6.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.4.tgz", "integrity": "sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -15239,6 +15558,14 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.1.0.tgz", + "integrity": "sha512-SEnieB2FPKEVne66NpXPd1Np4R1lTNKfjuy3XdIoPQKYBAFdzbzSZlSn1KJZUiihQLQC5Znot4SBz1EOTBwQAQ==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -17516,6 +17843,29 @@ "postcss-value-parser": "^4.2.0" } }, + "@emotion/is-prop-valid": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.1.2.tgz", + "integrity": "sha512-3QnhqeL+WW88YjYbQL5gUIkthuMw7a0NGbZ7wfFVk2kg/CK5w8w5FFa0RzWjyY1+sujN0NWbtSHH6OJmWHtJpQ==", + "requires": { + "@emotion/memoize": "^0.7.4" + } + }, + "@emotion/memoize": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.5.tgz", + "integrity": "sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ==" + }, + "@emotion/stylis": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", + "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==" + }, + "@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + }, "@eslint/eslintrc": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.2.tgz", @@ -18139,6 +18489,17 @@ "source-map": "^0.7.3" } }, + "@reduxjs/toolkit": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.8.1.tgz", + "integrity": "sha512-Q6mzbTpO9nOYRnkwpDlFOAbQnd3g7zj7CtHAZWz5SzE5lcV97Tf8f3SzOO8BoPOMYBFgfZaqTUZqgGu+a0+Fng==", + "requires": { + "immer": "^9.0.7", + "redux": "^4.1.2", + "redux-thunk": "^2.4.1", + "reselect": "^4.1.5" + } + }, "@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -18620,6 +18981,15 @@ "@types/node": "*" } }, + "@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "requires": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "@types/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", @@ -18655,9 +19025,9 @@ } }, "@types/jest": { - "version": "27.4.1", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.4.1.tgz", - "integrity": "sha512-23iPJADSmicDVrWk+HT58LMJtzLAnB2AgIzplQuq/bSrGaxCrlvRFjGbXmamnnk/mAmCdLStiGqggu28ocUyiw==", + "version": "27.5.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.5.0.tgz", + "integrity": "sha512-9RBFx7r4k+msyj/arpfaa0WOOEcaAZNmN+j80KFbFCoSqCJGHTz7YMAMGQW9Xmqm5w6l5c25vbSjMwlikJi5+g==", "requires": { "jest-matcher-utils": "^27.0.0", "pretty-format": "^27.0.0" @@ -18679,9 +19049,9 @@ "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" }, "@types/node": { - "version": "17.0.30", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.30.tgz", - "integrity": "sha512-oNBIZjIqyHYP8VCNAV9uEytXVeXG2oR0w9lgAXro20eugRQfY002qr3CUl6BAe+Yf/z3CRjPdz27Pu6WWtuSRw==" + "version": "17.0.31", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.31.tgz", + "integrity": "sha512-AR0x5HbXGqkEx9CadRH3EBYx/VkiUgZIhP4wvPn/+5KIsgpNoyFaRlVe0Zlx9gRtg8fA06a9tskE2MSN7TcG4Q==" }, "@types/parse-json": { "version": "4.0.0", @@ -18731,6 +19101,33 @@ "@types/react": "*" } }, + "@types/react-redux": { + "version": "7.1.24", + "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.24.tgz", + "integrity": "sha512-7FkurKcS1k0FHZEtdbbgN8Oc6b+stGSfZYjQGicofJ0j4U0qIn/jaSvnP2pLwZKiai3/17xqqxkkrxTgN8UNbQ==", + "requires": { + "@types/hoist-non-react-statics": "^3.3.0", + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0", + "redux": "^4.0.0" + } + }, + "@types/redux": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@types/redux/-/redux-3.6.0.tgz", + "integrity": "sha1-8evh5UEVGAcuT9/KXHbhbnTBOZo=", + "requires": { + "redux": "*" + } + }, + "@types/redux-thunk": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/redux-thunk/-/redux-thunk-2.1.0.tgz", + "integrity": "sha1-vCtulylhgxr7gqm/TwZybjUflBY=", + "requires": { + "redux-thunk": "*" + } + }, "@types/resolve": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", @@ -18779,6 +19176,17 @@ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==" }, + "@types/styled-components": { + "version": "5.1.25", + "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.25.tgz", + "integrity": "sha512-fgwl+0Pa8pdkwXRoVPP9JbqF0Ivo9llnmsm+7TCI330kbPIFd9qv1Lrhr37shf4tnxCOSu+/IgqM7uJXLWZZNQ==", + "dev": true, + "requires": { + "@types/hoist-non-react-statics": "*", + "@types/react": "*", + "csstype": "^3.0.2" + } + }, "@types/testing-library__jest-dom": { "version": "5.14.3", "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.3.tgz", @@ -18792,6 +19200,17 @@ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz", "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==" }, + "@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==" + }, + "@types/uuid": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", + "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==", + "dev": true + }, "@types/ws": { "version": "8.5.3", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", @@ -19520,6 +19939,23 @@ "@babel/helper-define-polyfill-provider": "^0.3.1" } }, + "babel-plugin-styled-components": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.7.tgz", + "integrity": "sha512-i7YhvPgVqRKfoQ66toiZ06jPNA3p6ierpfUuEWxNF+fV27Uv5gxBkf8KZLHUCc1nFA9j6+80pYoIpqCeyW3/bA==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.16.0", + "@babel/helper-module-imports": "^7.16.0", + "babel-plugin-syntax-jsx": "^6.18.0", + "lodash": "^4.17.11", + "picomatch": "^2.3.0" + } + }, + "babel-plugin-syntax-jsx": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=" + }, "babel-plugin-transform-react-remove-prop-types": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz", @@ -19765,6 +20201,11 @@ "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==" }, + "camelize": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", + "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=" + }, "caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", @@ -20120,6 +20561,11 @@ "postcss-selector-parser": "^6.0.9" } }, + "css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU=" + }, "css-declaration-sorter": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.2.2.tgz", @@ -20227,6 +20673,16 @@ "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" }, + "css-to-react-native": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.0.0.tgz", + "integrity": "sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ==", + "requires": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "css-tree": { "version": "1.0.0-alpha.37", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", @@ -21911,6 +22367,29 @@ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" }, + "history": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz", + "integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==", + "requires": { + "@babel/runtime": "^7.7.6" + } + }, + "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==", + "requires": { + "react-is": "^16.7.0" + }, + "dependencies": { + "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==" + } + } + }, "hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -25692,16 +26171,59 @@ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==" }, + "react-icons": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.3.1.tgz", + "integrity": "sha512-cB10MXLTs3gVuXimblAdI71jrJx8njrJZmNMEMC+sQu5B/BIOmlsAjskdqpn81y8UBVEGuHODd7/ci5DvoSzTQ==", + "requires": {} + }, "react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, + "react-redux": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.1.tgz", + "integrity": "sha512-LMZMsPY4DYdZfLJgd7i79n5Kps5N9XVLCJJeWAaPYTV+Eah2zTuBjTxKtNEbjiyitbq80/eIkm55CYSLqAub3w==", + "requires": { + "@babel/runtime": "^7.12.1", + "@types/hoist-non-react-statics": "^3.3.1", + "@types/use-sync-external-store": "^0.0.3", + "hoist-non-react-statics": "^3.3.2", + "react-is": "^18.0.0", + "use-sync-external-store": "^1.0.0" + }, + "dependencies": { + "react-is": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz", + "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==" + } + } + }, "react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==" }, + "react-router": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.3.0.tgz", + "integrity": "sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ==", + "requires": { + "history": "^5.2.0" + } + }, + "react-router-dom": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.3.0.tgz", + "integrity": "sha512-uaJj7LKytRxZNQV8+RbzJWnJ8K2nPsOOEuX7aQstlMZKQT0164C+X2w6bnkqU3sjtLvpd5ojrezAyfZ1+0sStw==", + "requires": { + "history": "^5.2.0", + "react-router": "6.3.0" + } + }, "react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", @@ -25802,6 +26324,20 @@ "strip-indent": "^3.0.0" } }, + "redux": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.0.tgz", + "integrity": "sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==", + "requires": { + "@babel/runtime": "^7.9.2" + } + }, + "redux-thunk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.1.tgz", + "integrity": "sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==", + "requires": {} + }, "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -25913,6 +26449,11 @@ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" }, + "reselect": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.5.tgz", + "integrity": "sha512-uVdlz8J7OO+ASpBYoz1Zypgx0KasCY20H+N8JD13oUMtPvSHQuscrHop4KbXrbsBcdB9Ds7lVK7eRkBIfO43vQ==" + }, "resolve": { "version": "1.22.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", @@ -26254,6 +26795,11 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -26553,6 +27099,23 @@ "integrity": "sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ==", "requires": {} }, + "styled-components": { + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.5.tgz", + "integrity": "sha512-ndETJ9RKaaL6q41B69WudeqLzOpY1A/ET/glXkNZ2T7dPjPqpPCXXQjDFYZWwNnE5co0wX+gTCqx9mfxTmSIPg==", + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/traverse": "^7.4.5", + "@emotion/is-prop-valid": "^1.1.0", + "@emotion/stylis": "^0.8.4", + "@emotion/unitless": "^0.7.4", + "babel-plugin-styled-components": ">= 1.12.0", + "css-to-react-native": "^3.0.0", + "hoist-non-react-statics": "^3.0.0", + "shallowequal": "^1.1.0", + "supports-color": "^5.5.0" + } + }, "stylehacks": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.0.tgz", @@ -26982,8 +27545,7 @@ "typescript": { "version": "4.6.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.4.tgz", - "integrity": "sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==", - "peer": true + "integrity": "sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==" }, "unbox-primitive": { "version": "1.0.2", @@ -27056,6 +27618,12 @@ "punycode": "^2.1.0" } }, + "use-sync-external-store": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.1.0.tgz", + "integrity": "sha512-SEnieB2FPKEVne66NpXPd1Np4R1lTNKfjuy3XdIoPQKYBAFdzbzSZlSn1KJZUiihQLQC5Znot4SBz1EOTBwQAQ==", + "requires": {} + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index 7f031bc..a9686e1 100644 --- a/package.json +++ b/package.json @@ -3,12 +3,27 @@ "version": "0.1.0", "private": true, "dependencies": { + "@reduxjs/toolkit": "^1.8.1", "@testing-library/jest-dom": "^5.16.4", "@testing-library/react": "^13.1.1", "@testing-library/user-event": "^13.5.0", + "@types/jest": "^27.5.0", + "@types/node": "^17.0.31", + "@types/react": "^18.0.8", + "@types/react-dom": "^18.0.3", + "@types/react-redux": "^7.1.24", + "@types/redux": "^3.6.0", + "@types/redux-thunk": "^2.1.0", "react": "^18.1.0", "react-dom": "^18.1.0", + "react-icons": "^4.3.1", + "react-redux": "^8.0.1", + "react-router-dom": "^6.3.0", "react-scripts": "5.0.1", + "redux-thunk": "^2.4.1", + "styled-components": "^5.3.5", + "typescript": "^4.6.4", + "uuid": "^8.3.2", "web-vitals": "^2.1.4" }, "scripts": { @@ -34,5 +49,9 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "devDependencies": { + "@types/styled-components": "^5.1.25", + "@types/uuid": "^8.3.4" } } diff --git a/src/App.js b/src/App.js deleted file mode 100644 index 772afaf..0000000 --- a/src/App.js +++ /dev/null @@ -1,5 +0,0 @@ -function App() { - return
15κΈ° νŒŒμ΄νŒ… πŸ’–
; -} - -export default App; diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..bf97f70 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,34 @@ +import React from "react"; +import { Outlet } from "react-router-dom"; +import styled from "styled-components"; + +import SideButtonContainer from "./containers/SideButtonContainer"; + +const AppContainer = styled.div` + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + width: 100%; + height: 100vh; + font-family: var(--ff-main); +` + +const EmptyDiv = styled.div` + width: 5rem; + height: 3rem; + margin-left: 1rem; + flex: 1; +` + +function App() { + + return ( + + + + + ); +} + +export default App; diff --git a/src/GlobalStyle.tsx b/src/GlobalStyle.tsx new file mode 100644 index 0000000..c9a0525 --- /dev/null +++ b/src/GlobalStyle.tsx @@ -0,0 +1,27 @@ +import {createGlobalStyle} from "styled-components"; + +export default createGlobalStyle` + @import url("https://fonts.googleapis.com/css2?family=Noto+Sans+KR&display=swap"); + + :root{ + --ff-main: "Noto Sans KR", sans-serif; + } + + *, + ::after, + ::before { + margin: 0; + padding: 0; + box-sizing: border-box; + } + + body{ + display: flex; + height: 100vh; + flex-direction: column; + align-items: center; + justify-content: center; + background-color: #f0f0f0; + background-image: linear-gradient(45deg, #ff9a9e 0%, #fad0c4 99%, #fad0c4 100%); + } +`; diff --git a/src/components/ChatButtonComponent.tsx b/src/components/ChatButtonComponent.tsx new file mode 100644 index 0000000..4ad7a4a --- /dev/null +++ b/src/components/ChatButtonComponent.tsx @@ -0,0 +1,94 @@ +import React from 'react' +import styled from 'styled-components' + +import { useAppDispatch, useAppSelector } from '../hooks' +import { useNavigate } from 'react-router-dom' +import { setActiveChat } from '../state/chatSlice' +import { Chat } from '../types' + +const Component = styled.button` + width: 80%; + display: flex; + flex-direction: column; + align-content: flex-start; + justify-content: flex-start; + border-radius: 3rem; + background-color: rgba(255, 255, 255, 0.5); + margin: 1rem 0 1rem 0; + border: transparent; + padding: 0 2rem 0.5rem 2rem; + cursor: pointer; + &:hover { + box-shadow: 0px 0px 20px rgba(230, 230, 230, 1); + } +` +const ProfileDiv = styled.div` + width: 100%; + display: flex; + justify-content: flex-start; + height: 3rem; + align-items: center; +` + +const NameDiv = styled.div` + background-color: rgba(255, 255, 255, 1); + border-radius: 3rem; + padding: 0.3rem 1rem 0.3rem 1rem; + margin-left: 0.5rem; + text-transform: lowercase; + color: rgb(130, 130, 130); + font-size: 1rem; + box-shadow: 0px 5px 10px rgba(230, 230, 230, 0.3); +` +const MessageDiv = styled.div` + width: 100%; + display: flex; + justify-content: space-between; + padding: 0.3rem 1rem 0.3rem 1rem; + text-transform: lowercase; + color: rgb(130, 130, 130); + font-size: 1.3rem; + box-shadow: 0px 5px 10px rgba(230, 230, 230, 0.3); +` + +const TimeP = styled.p` + font-size: 1rem; +` + +const ProfileImage = styled.img` + position: relative; + bottom: 1rem; + width: 4rem; + height: 4rem; + object-fit: cover; +` + +function ChatButtonComponent({chat}:{chat:Chat}) { + + const dispatch = useAppDispatch(); + const navigate = useNavigate(); + const clients = useAppSelector((state)=>state.client.clients); + + return ( + { + dispatch(setActiveChat(chat.chatId)); + navigate(`/chats/${chat.chatId}`); + }}> + {chat.clients.map((client)=>{ + if(client.id !== clients[0].id) + return( + + + {client.name} + + ) + })} + + <>{chat.messages[chat.messages.length-1]? chat.messages[chat.messages.length-1].content : ""} + {chat.messages[chat.messages.length-1]? chat.messages[chat.messages.length-1].timeString : ""} + + + ) +} + +export default ChatButtonComponent \ No newline at end of file diff --git a/src/components/MessageBubbleComponent.tsx b/src/components/MessageBubbleComponent.tsx new file mode 100644 index 0000000..e647a2d --- /dev/null +++ b/src/components/MessageBubbleComponent.tsx @@ -0,0 +1,93 @@ +import styled from 'styled-components' + +import { Message, MessageType } from '../types' + +const componentPadding = { + [MessageType.BASE]: "0.5rem 1rem 0.5rem 1rem", + [MessageType.HEAD]: "0.5rem 1rem 0rem 1rem", + [MessageType.BODY]: "0rem 1rem 0rem 1rem", + [MessageType.TAIL]: "0rem 1rem 0.5rem 1rem", +} + +const bubbleRadiusSelected = { + [MessageType.BASE]: "1rem", + [MessageType.HEAD]: "1rem 1rem 0.3rem 1rem", + [MessageType.BODY]: "1rem 0.3rem 0.3rem 1rem", + [MessageType.TAIL]: "1rem 0.3rem 1rem 1rem", +} +const bubbleRadiusDeselected = { + [MessageType.BASE]: "1rem", + [MessageType.HEAD]: "1rem 1rem 1rem 0.3rem", + [MessageType.BODY]: "0.3rem 1rem 1rem 0.3rem", + [MessageType.TAIL]: "0.3rem 1rem 1rem 1rem", +} + + +const Component = styled.div<{selected: boolean, manner: MessageType}>` + width: 100%; + display: flex; + flex-direction: ${({selected})=>(selected? "row-reverse":"row")}; + padding: ${({manner})=>componentPadding[manner]}; +` +const MessageProfileImg = styled.img` + width: 3rem; + height: 3rem; + object-fit: contain; +` +const MessageProfileDiv = styled.div` + width: 3rem; + height: 3rem; +` +const MessageContentDiv = styled.div` + width: 30vw; + max-width: 20rem; + margin-left: 1rem; + margin-right: 1rem +` +const Name = styled.p<{selected: boolean}>` + font-size: 0.8rem; + text-align: ${({selected})=>(selected? "right":"left")}; +` + +const MessageHolderDiv = styled.div<{selected: boolean}>` + width: 100%; + display: flex; + flex-direction: ${({selected})=>(selected? "row-reverse":"row")}; + align-items: flex-end; +` + +const MessageBubbleDiv = styled.div<{selected: boolean, manner: MessageType}>` + max-width: calc(100% - 4rem); + height: 100%; + background-color: ${({selected})=>(selected? "#ff9a9e":"#ffecd2")}; + border-radius: ${({selected, manner})=>(selected? bubbleRadiusSelected[manner]:bubbleRadiusDeselected[manner])}; + padding: 0.8rem; + margin-top: 0.2rem; + color: ${({selected})=>(selected? "white":"black")}; + word-wrap: break-word; +` +const Time = styled.p` + font-size: 0.6rem; + margin-left: 0.3rem; + margin-right: 0.3rem; +` + +const MessageBubbleComponent = ({msg, timeString, selected, manner}: {msg:Message, timeString:string, selected:boolean, manner:MessageType}) => { + + return( + + + {manner === MessageType.BASE || manner === MessageType.HEAD? : "" } + + + {manner === MessageType.BASE || manner === MessageType.HEAD? {msg.client.name} : "" } + +

{msg.content}

+ {manner === MessageType.BASE || manner === MessageType.TAIL? : "" } +
+
+
+ ) +} + +export default MessageBubbleComponent; \ No newline at end of file diff --git a/src/containers/FriendsContainer.tsx b/src/containers/FriendsContainer.tsx new file mode 100644 index 0000000..eedbf0e --- /dev/null +++ b/src/containers/FriendsContainer.tsx @@ -0,0 +1,105 @@ +import React from 'react' +import styled from 'styled-components' +import { useNavigate } from 'react-router-dom' +import {v4 as uuidv4} from "uuid"; + +import { useAppDispatch, useAppSelector } from '../hooks' +import { addChat, setActiveChat, setActiveClient } from '../state/chatSlice' + +const Container = styled.div` + display: flex; + flex-direction: row; + justify-content: center; + align-content: flex-start; + flex-wrap: wrap; + padding: 1rem; + overflow: scroll; + height: 60vh; + &::-webkit-scrollbar { + display:none; + } +` + +const ProfileDiv = styled.div` + width: 8rem; + height: 10rem; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + margin: 1rem; + background-color: rgba(255, 255, 255, 0.5); + border-radius: 2rem; + & p{ + font-size: 0.8rem; + } + &:hover{ + box-shadow: 0px 0px 20px rgba(0, 0, 0, 0.1); + } +` + +const ProfileName = styled.p` + border-radius: 3rem; + background-color: rgba(255, 255, 255, 1); + color: rgba(150, 150, 150, 1); + margin: 1rem 0 1rem 0; + padding: 0.2rem 1rem 0.2rem 1rem; + text-align: center; + box-shadow: 0px 5px 10px rgba(230, 230, 230, 0.3) +` + +const ProfileButton = styled.button` + width: 6rem; + height: 6rem; + border-radius: 0; + border: transparent; + cursor: pointer; + background-color: rgba(255, 255, 255, 0); + z-index: 1; + font-size: 4rem; + overflow: visible; +` +const ProfileImage = styled.img` + width: 6rem; + height: 6rem; + display: block; + object-fit: cover; + margin-left: auto; + margin-right: auto; +` + +function FriendsContainer({query}:{query: string}) { + const {clients} = useAppSelector((state)=> state.client); + const dispatch = useAppDispatch(); + const navigate = useNavigate(); + + return ( + + {clients.map(client=>{ + if(client.name.includes(query) && client.id !== clients[0].id){ + return( + + { + const id = uuidv4(); + dispatch(addChat({ + chatId: id, + activeClient: clients[0], + clients: [clients[0], client], + messages: [], + })); + dispatch(setActiveChat(id)); + dispatch(setActiveClient(clients[0])); + navigate(`/chats/${id}`); + }}> + + + {client.name} + + ) + } + })} + + ) +} + +export default FriendsContainer \ No newline at end of file diff --git a/src/containers/InputContainer.tsx b/src/containers/InputContainer.tsx new file mode 100644 index 0000000..4622e0f --- /dev/null +++ b/src/containers/InputContainer.tsx @@ -0,0 +1,114 @@ +import React, {useState} from 'react' +import {v4 as uuidv4} from "uuid"; +import styled from 'styled-components'; + +import { useAppSelector, useAppDispatch } from '../hooks'; +import { addMessage } from '../state/chatSlice'; +import { MessageType, Client } from '../types'; + +const Container = styled.div` + width: 90vw; + max-width: 35rem; + height: 4rem; + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 5vh; + z-index: 1; +` + +const Form = styled.form` + width: 90vw; + max-width: 35rem; + display: flex; + flex-direction: row; + justify-content: space-around; +` + +const Button = styled.button` + width: 4rem; + height: 4rem; + background-color: rgba(255, 255, 255, 0.5); + border-radius: 4rem; + border: none; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + &:hover { + box-shadow: 0 0 10px rgba(255, 255, 255, 0.6); + background-color: rgba(255, 255, 255, 0.8); + } + & p { + font-size: 2.5rem; + width: 3rem; + text-align: center; + } +` + +const Input = styled.input` + height: 4rem; + width: 70%; + border-radius: 4rem; + border-color: transparent; + padding-left: 1.2rem; + outline: none; + box-shadow: 0px 0px 20px rgba(255, 255, 255, 1); + &:hover { + box-shadow: 0 0 10px rgba(255, 255, 255, 0.6); + } + font-size: 1rem; +` + +const handleTimeFormat = (time: Date):string[] => { + const hourString: string = time.getHours().toString(); + const minString: string = time.getMinutes().toString(); + const timeString: string = (hourString.length === 2? hourString : "0" + hourString) + ": " + (minString.length === 2? minString : "0" + minString) + + return [timeString] +}; + +function InputContainer() { + const [message, setMessage] = useState(""); + + const activeClient:Client = useAppSelector((state)=>state.chat.chats[state.chat.acidx!].activeClient); + const dispatch = useAppDispatch(); + + const handleFormSubmit = (message: string, e?:React.FormEvent) => { + + if(e) e.preventDefault(); + if(message !== "" && activeClient){ + + const [timeString] = handleTimeFormat(new Date()); + + dispatch(addMessage({ + msgId: uuidv4(), + client: activeClient, + content: message, + timeString: timeString, + messageType: MessageType.BASE, + })); + + setMessage(""); + }else if(message === ""){ + alert("enter the message") + }else{ + alert("select the profile") + } + } + + return ( + +
handleFormSubmit(message, e)}> + + setMessage(e.target.value)} value={message}/> + +
+
+ + ) +} + +export default InputContainer \ No newline at end of file diff --git a/src/containers/MessageContainer.tsx b/src/containers/MessageContainer.tsx new file mode 100644 index 0000000..44782d3 --- /dev/null +++ b/src/containers/MessageContainer.tsx @@ -0,0 +1,44 @@ +import React, { useEffect } from 'react' +import styled from 'styled-components' + +import { useAppSelector } from '../hooks' +import { Message } from "../types" +import MessageBubbleComponent from '../components/MessageBubbleComponent' + +const Container = styled.div` + width: 90vw; + margin-top: 20vh; + height: 65vh; + z-index: 0; + max-width: 35rem; + position: absolute; + overflow: scroll; + &::-webkit-scrollbar { + display:none; + } +` + +function MessageContainer() { + + const messages = useAppSelector((state)=> state.chat.chats[state.chat.acidx!].messages); + const activeClient = useAppSelector((state)=>state.chat.chats[state.chat.acidx!].activeClient); + const containerRef = React.useRef(null); + + useEffect( + ()=>{ + containerRef.current!.scrollTop = containerRef.current!.scrollHeight + } + ); + + return ( + + {messages.map((message: Message)=>{ + const timeString: string = message.timeString; + const selected: boolean = message.client.id === activeClient?.id; + return + })} + + ) +} + +export default MessageContainer \ No newline at end of file diff --git a/src/containers/ProfileContainer.tsx b/src/containers/ProfileContainer.tsx new file mode 100644 index 0000000..d4d6b22 --- /dev/null +++ b/src/containers/ProfileContainer.tsx @@ -0,0 +1,95 @@ +import React from 'react' +import { useNavigate } from 'react-router-dom' +import styled from 'styled-components' +import { useAppDispatch, useAppSelector } from '../hooks' +import { setActiveClient } from '../state/chatSlice' + +const Container = styled.div` + width: 50vw; + height: 8rem; + max-width: 35rem; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + margin-top: 3vh; + z-index: 1; +` + +const ProfileDiv = styled.div` + width: 5em; + height: 8rem; + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; + margin-left: 1rem; + margin-right: 1rem; + & p{ + font-size: 0.8rem; + } +` + +const ProfileName = styled.p<{selected: boolean}>` + border-radius: 0.4rem; + background-color: rgba(255, 255, 255, ${({selected})=>(selected? "1":"0")}); + color: ${({selected})=>(selected? "rgba(150, 150, 150, 1)":"rgba(255, 255, 255, 1)")}; + margin-top: 0.6rem; + padding: 0 1rem 0 1rem; + text-align: center; + box-shadow: 0px 0px 20px rgba(255, 255, 255, ${({selected})=>(selected? "1":"0")}); + text-shadow: 0px 0px 10px rgba(255, 255, 255, ${({selected})=>(selected? "0":"1")}); +` + +const ProfileButton = styled.button<{selected: boolean}>` + width: 6rem; + height: 6rem; + border-radius: 3rem; + position: relative; + border: transparent; + cursor: pointer; + background-color: rgba(255, 255, 255, ${({selected})=>(selected? "1":"0")}); + z-index: 1; + font-size: 4rem; + box-shadow: 0px 0px 20px rgba(255, 255, 255, ${({selected})=>(selected? "1":"0")}); + &:hover{ + text-shadow: 0px 0px 20px rgba(0, 0, 0, 0.1); + } +` +const ProfileImage = styled.img` + width: 80%; + height: 80%; + display: block; + margin-left: auto; + margin-right: auto; +` + +function ProfileContainer() { + const {clients, activeClient} = useAppSelector((state)=> state.chat.chats[state.chat.acidx!]) + const dispatch = useAppDispatch(); + const navigate = useNavigate(); + + return ( + + + { + navigate(-1); + }}>🫲🏻 + {"λ’€λ‘œκ°€κΈ°"} + + {clients.map(client=>{ + const selected = client.id === activeClient?.id? true : false + return( + + { + dispatch(setActiveClient(client)) + }}> + {client.name} + + ) + })} + + ) +} + +export default ProfileContainer \ No newline at end of file diff --git a/src/containers/SideButtonContainer.tsx b/src/containers/SideButtonContainer.tsx new file mode 100644 index 0000000..b0692b9 --- /dev/null +++ b/src/containers/SideButtonContainer.tsx @@ -0,0 +1,61 @@ +import React from 'react' +import styled from 'styled-components' +import { useNavigate } from 'react-router-dom' +import { FiMessageCircle, FiInfo, FiSmile } from "react-icons/fi"; + +const Container = styled.div` + display: flex; + width: 5rem; + margin-right: 1rem; + flex: 1; + align-items: center; + justify-content: center; +` + +const ButtonDiv = styled.div` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 1rem 0.5rem 1rem 0.5rem; + background-color: rgba(255, 255, 255, 0.5); + border-radius: 3rem; +` + +const Button = styled.button` + border-radius: 3rem; + border: none; + padding: 0.5rem; + margin: 0.5rem; + background-color: rgba(255, 255, 255, 0.8); + box-shadow: 0 0.3rem 1rem 0.1rem rgba(255, 255, 255, 0.1); + cursor: pointer; + &:hover { + box-shadow: 0 0 10px rgba(255, 255, 255, 1); + background-color: rgba(255, 255, 255, 1); + } +` + +const ButtonIcon = styled.div` + display: flex; + justify-content: center; + align-items: center; + font-size: 1.5rem; + color: rgba(200, 200, 200, 1); +` + +function SideButtonContainer() { + const navigate = useNavigate(); + + return ( + + + + + + + + ) +} + +export default SideButtonContainer \ No newline at end of file diff --git a/src/hooks.ts b/src/hooks.ts new file mode 100644 index 0000000..32827e5 --- /dev/null +++ b/src/hooks.ts @@ -0,0 +1,5 @@ +import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux"; +import type {RootState, AppDispatch} from "./state/store"; + +export const useAppDispatch = () => useDispatch() +export const useAppSelector: TypedUseSelectorHook = useSelector \ No newline at end of file diff --git a/src/images/cat-face.png b/src/images/cat-face.png new file mode 100644 index 0000000..b9e5176 Binary files /dev/null and b/src/images/cat-face.png differ diff --git a/src/images/dog-face.png b/src/images/dog-face.png new file mode 100644 index 0000000..284e875 Binary files /dev/null and b/src/images/dog-face.png differ diff --git a/src/images/fox-face.png b/src/images/fox-face.png new file mode 100644 index 0000000..046f010 Binary files /dev/null and b/src/images/fox-face.png differ diff --git a/src/images/monkey-face.png b/src/images/monkey-face.png new file mode 100644 index 0000000..d697fef Binary files /dev/null and b/src/images/monkey-face.png differ diff --git a/src/images/pig-face.png b/src/images/pig-face.png new file mode 100644 index 0000000..e9029ae Binary files /dev/null and b/src/images/pig-face.png differ diff --git a/src/images/robot-face.png b/src/images/robot-face.png new file mode 100644 index 0000000..44b6820 Binary files /dev/null and b/src/images/robot-face.png differ diff --git a/src/index.js b/src/index.js deleted file mode 100644 index 593edf1..0000000 --- a/src/index.js +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import App from './App'; - -const root = ReactDOM.createRoot(document.getElementById('root')); -root.render( - - - -); diff --git a/src/index.tsx b/src/index.tsx new file mode 100644 index 0000000..fc04003 --- /dev/null +++ b/src/index.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; +import { Provider } from 'react-redux'; +import { BrowserRouter, Routes, Route } from "react-router-dom"; + +import GlobalStyle from "./GlobalStyle" +import store from './state/store'; +import Chatroom from './pages/Chatroom'; +import AppInfo from './pages/AppInfo'; +import ChatList from './pages/ChatList'; +import FriendList from "./pages/FriendList" + +const root = ReactDOM.createRoot(document.getElementById('root')!); +root.render( + + + + + + }> + }/> + }/> + }/> + }/> + +

NOT FOUND

+ + } + /> +
+
+
+
+
+); diff --git a/src/jsons/clients.json b/src/jsons/clients.json new file mode 100644 index 0000000..e69de29 diff --git a/src/jsons/messages.json b/src/jsons/messages.json new file mode 100644 index 0000000..e69de29 diff --git a/src/pages/AppInfo.tsx b/src/pages/AppInfo.tsx new file mode 100644 index 0000000..f6cac83 --- /dev/null +++ b/src/pages/AppInfo.tsx @@ -0,0 +1,47 @@ +import React from 'react' +import styled from 'styled-components' + +const Wrapper = styled.div` + width: 90vw; + max-width: 35rem; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +` + +const MenuDiv = styled.div` + width: 80%; + height: 3rem; + border-radius: 3rem; + background-color: rgba(255, 255, 255, 0.5); + display: flex; + margin: 0.8rem 0 0.8rem 0; + padding-left: 2rem; + align-items: center; + cursor: pointer; + &:hover { + box-shadow: 0px 0px 20px rgba(230, 230, 230, 1); + } +` +const TextDiv = styled.div` + background-color: rgba(255, 255, 255, 1); + border-radius: 3rem; + padding: 0.3rem 1rem 0.3rem 1rem; + text-transform: lowercase; + color: rgb(130, 130, 130); + font-size: 1rem; + box-shadow: 0px 5px 10px rgba(230, 230, 230, 0.3); +` + +const menuItems = ["Profile", "Privacy", "General", "Payments", "Notifications"] + +function AppInfo() { + return ( + + {menuItems.map((item)=>{item})} + + ) +} + +export default AppInfo \ No newline at end of file diff --git a/src/pages/ChatList.tsx b/src/pages/ChatList.tsx new file mode 100644 index 0000000..945ded1 --- /dev/null +++ b/src/pages/ChatList.tsx @@ -0,0 +1,30 @@ +import React from 'react' +import styled from 'styled-components' + +import { useAppSelector } from '../hooks' +import ChatButtonComponent from '../components/ChatButtonComponent' + +const Wrapper = styled.div` + width: 90vw; + max-width: 35rem; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +` + +function ChatList() { + const chats = useAppSelector((state)=>state.chat.chats); + + return ( + + {chats.map(chat=>{ + return( + + ) + })} + + ) +} + +export default ChatList \ No newline at end of file diff --git a/src/pages/Chatroom.tsx b/src/pages/Chatroom.tsx new file mode 100644 index 0000000..d6f5a9e --- /dev/null +++ b/src/pages/Chatroom.tsx @@ -0,0 +1,25 @@ +import React from 'react' +import styled from 'styled-components' + +import InputContainer from '../containers/InputContainer' +import MessageContainer from '../containers/MessageContainer' +import ProfileContainer from '../containers/ProfileContainer' + +const Wrapper = styled.div` + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-between; + height: 100vh; +` + +function Chatroom() { + return ( + + + + + + ) +} +export default Chatroom diff --git a/src/pages/FriendList.tsx b/src/pages/FriendList.tsx new file mode 100644 index 0000000..868134e --- /dev/null +++ b/src/pages/FriendList.tsx @@ -0,0 +1,41 @@ +import React, {useState} from 'react' +import styled from 'styled-components' + +import FriendsContainer from '../containers/FriendsContainer' + +const Wrapper = styled.div` + width: 90vw; + max-width: 35rem; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +` + +const Input = styled.input` + height: 3rem; + width: 80%; + border-radius: 4rem; + border-color: transparent; + padding-left: 1.2rem; + font-size: 1rem; + margin-bottom: 2rem; + outline: none; + box-shadow: 0px 0px 20px rgba(255, 255, 255, 1); + &:hover { + box-shadow: 0 0 10px rgba(255, 255, 255, 0.6); + } +` + +function FriendList() { + const [query, setQuery] = useState(""); + + return ( + + setQuery(e.target.value)} value={query}/> + + + ) +} + +export default FriendList \ No newline at end of file diff --git a/src/state/chatSlice.ts b/src/state/chatSlice.ts new file mode 100644 index 0000000..ccfbe35 --- /dev/null +++ b/src/state/chatSlice.ts @@ -0,0 +1,47 @@ +import {createSlice, PayloadAction} from "@reduxjs/toolkit" + +import { ChatState } from "./state.types" +import { MessageType, Message, Chat, Client } from "../types"; + +const chatState: ChatState = { + chats: [] +}; + +const messageTypeHandler = (messages: Message[], payload: Message) => { + const prevMessage = messages[messages.length - 1] + if(prevMessage && prevMessage.timeString.slice(-2) === payload.timeString.slice(-2) && prevMessage.client.id === payload.client.id){ + if(prevMessage.messageType === MessageType.TAIL){ + prevMessage.messageType = MessageType.BODY; + payload.messageType = MessageType.TAIL; + }else{ + prevMessage.messageType = MessageType.HEAD; + payload.messageType = MessageType.TAIL; + } + } +}; + +const chatSlice = createSlice({ + name: "chat", + initialState: chatState, + reducers: { + addChat(state, action: PayloadAction){ + state.chats.push(action.payload) + }, + addMessage(state, action: PayloadAction) { + const idx = state.acidx!; + messageTypeHandler(state.chats[idx].messages, action.payload) + state.chats[idx].messages.push(action.payload) + }, + setActiveChat(state, action: PayloadAction){ + state.acidx = state.chats.findIndex((chat)=>chat.chatId === action.payload) + }, + setActiveClient(state, action: PayloadAction){ + const idx = state.acidx!; + state.chats[idx].activeClient = action.payload; + } + } +}) + + +export const chatReducer = chatSlice.reducer +export const {addChat, addMessage, setActiveChat, setActiveClient} = chatSlice.actions diff --git a/src/state/clientSlice.ts b/src/state/clientSlice.ts new file mode 100644 index 0000000..b1aa054 --- /dev/null +++ b/src/state/clientSlice.ts @@ -0,0 +1,57 @@ +import {createSlice, PayloadAction} from "@reduxjs/toolkit" + +import { ClientState } from "./state.types" +import {Client} from "../types" + +const clientState: ClientState = { + clients: [ + { id: "1", + imageUrl: require("../images/dog-face.png"), + name: "κ°•μ•„μ§€",}, + { + id: "2", + imageUrl: require("../images/cat-face.png"), + name: "고양이" + }, + { + id: "3", + imageUrl: require("../images/fox-face.png"), + name: "μ—¬μš°" + }, + { + id: "4", + imageUrl: require("../images/monkey-face.png"), + name: "μ›μˆ­μ΄" + }, + { + id: "5", + imageUrl: require("../images/pig-face.png"), + name: "돼지" + }, + { + id: "6", + imageUrl: require("../images/robot-face.png"), + name: "λ‘œλ΄‡" + } + ] +} + +const clientSlice = createSlice({ + name: "client", + initialState: clientState, + reducers: { + // addClient(state, action: PayloadAction) { + // state.clients.push(action.payload) + // state.currentClient = action.payload; + // }, + // removeClient(state, action: PayloadAction){ + // state.clients = state.clients.filter((client)=>{ + // if(state.currentClient && state.currentClient.id == action.payload.id) + // delete state.currentClient; + // return client.id !== action.payload.id}) + // }, + } +}) + +export const clientReducer = clientSlice.reducer +export const {} = clientSlice.actions \ No newline at end of file diff --git a/src/state/state.types.ts b/src/state/state.types.ts new file mode 100644 index 0000000..f9f351c --- /dev/null +++ b/src/state/state.types.ts @@ -0,0 +1,14 @@ +import { Message, Client, Chat } from "../types"; + +export type ChatState = { + chats: Chat[], + acidx?: number, //active chat index +} + +export type MessageState = { + messages: Message[] +}; + +export type ClientState = { + clients: Client[] +} \ No newline at end of file diff --git a/src/state/store.ts b/src/state/store.ts new file mode 100644 index 0000000..832b908 --- /dev/null +++ b/src/state/store.ts @@ -0,0 +1,16 @@ +import { configureStore } from "@reduxjs/toolkit"; + +import { chatReducer } from "./chatSlice"; +import { clientReducer } from "./clientSlice"; + +const store = configureStore({ + reducer: { + // message: messageReducer, + client: clientReducer, + chat: chatReducer, + }} +) + +export default store; +export type RootState = ReturnType; +export type AppDispatch = typeof store.dispatch; \ No newline at end of file diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..8540252 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,24 @@ +export type Message = { + msgId: string, + client: Client, + content: string, + timeString: string, + messageType: MessageType, +} + +export type Client = { + id: string, + imageUrl: string, + name: string, +} + +export type Chat = { + chatId: string, + activeClient: Client, + clients: Client[], + messages: Message[], +} + +export enum MessageType { + HEAD, BODY, TAIL, BASE +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..66c8ee8 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,93 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + /* Projects */ + // "incremental": true, /* Enable incremental compilation */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + /* Language and Environment */ + "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */ + // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + /* Modules */ + "module": "commonjs", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "resolveJsonModule": true, /* Enable importing .json files */ + // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */ + // "outDir": "./", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ + // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ + // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +} \ No newline at end of file