diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..1dc7164 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,6 @@ +{ + "trailingComma": "es5", + "tabWidth": 2, + "semi": true, + "singleQuote": true +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 73afbd8..4b71e47 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,9 +17,15 @@ "@types/react-dom": "^18.0.6", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-router-dom": "^6.4.2", "react-scripts": "5.0.1", + "recoil": "^0.7.5", + "styled-components": "^5.3.6", "typescript": "^4.8.3", "web-vitals": "^2.1.4" + }, + "devDependencies": { + "@types/styled-components": "^5.1.26" } }, "node_modules/@adobe/css-tools": { @@ -2140,6 +2146,29 @@ "postcss-selector-parser": "^6.0.10" } }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz", + "integrity": "sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==", + "dependencies": { + "@emotion/memoize": "^0.8.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz", + "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==" + }, + "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.3.2", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.2.tgz", @@ -3083,6 +3112,14 @@ } } }, + "node_modules/@remix-run/router": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.0.2.tgz", + "integrity": "sha512-GRSOFhJzjGN+d4sKHTMSvNeUPoZiDHWmRnXfzaxrqe7dE/Nzlc8BiMSJdLDESZlndM7jIUrZ/F4yWqVYlI0rwQ==", + "engines": { + "node": ">=14" + } + }, "node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -3757,6 +3794,16 @@ "@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==", + "dev": true, + "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", @@ -3916,6 +3963,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.26", + "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.26.tgz", + "integrity": "sha512-KuKJ9Z6xb93uJiIyxo/+ksS7yLjS1KzG6iv5i78dhVg/X3u5t1H7juRWqVmodIdz6wGVaIApo1u01kmFRdJHVw==", + "dev": true, + "dependencies": { + "@types/hoist-non-react-statics": "*", + "@types/react": "*", + "csstype": "^3.0.2" + } + }, "node_modules/@types/testing-library__jest-dom": { "version": "5.14.5", "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.5.tgz", @@ -4948,6 +5006,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": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==" + }, "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", @@ -5261,6 +5339,11 @@ "node": ">= 6" } }, + "node_modules/camelize": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", + "integrity": "sha512-W2lPwkBkMZwFlPCXhIlYgxu+7gC/NUlCtdK652DAJ1JdgV0sTrvuPFshNPrFa1TY2JOkLhgdeEBplB4ezEa+xg==" + }, "node_modules/caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", @@ -5700,6 +5783,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": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "engines": { + "node": ">=4" + } + }, "node_modules/css-declaration-sorter": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.3.1.tgz", @@ -5881,6 +5972,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", @@ -8188,6 +8289,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/hamt_plus": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/hamt_plus/-/hamt_plus-1.0.2.tgz", + "integrity": "sha512-t2JXKaehnMb9paaYA7J0BX8QQAY8lwfQ9Gjf4pg/mk4krt+cmwmU652HOoWonf+7+EQV97ARPMhhVgU1ra2GhA==" + }, "node_modules/handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -8269,6 +8375,19 @@ "he": "bin/he" } }, + "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", @@ -13774,6 +13893,36 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.4.2.tgz", + "integrity": "sha512-Rb0BAX9KHhVzT1OKhMvCDMw776aTYM0DtkxqUBP8dNBom3mPXlfNs76JNGK8wKJ1IZEY1+WGj+cvZxHVk/GiKw==", + "dependencies": { + "@remix-run/router": "1.0.2" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.4.2.tgz", + "integrity": "sha512-yM1kjoTkpfjgczPrcyWrp+OuQMyB1WleICiiGfstnQYo/S8hPEEnVjr/RdmlH6yKK4Tnj1UGXFSa7uwAtmDoLQ==", + "dependencies": { + "@remix-run/router": "1.0.2", + "react-router": "6.4.2" + }, + "engines": { + "node": ">=14" + }, + "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", @@ -13878,6 +14027,25 @@ "node": ">=8.10.0" } }, + "node_modules/recoil": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/recoil/-/recoil-0.7.5.tgz", + "integrity": "sha512-GVShsj5+M/2GULWBs5WBJGcsNis/d3YvDiaKjYh3mLKXftjtmk9kfaQ8jwjoIXySCwn8/RhgJ4Sshwgzj2UpFA==", + "dependencies": { + "hamt_plus": "1.0.2" + }, + "peerDependencies": { + "react": ">=16.13.1" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/recursive-readdir": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz", @@ -14548,6 +14716,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", @@ -14938,6 +15111,36 @@ "webpack": "^5.0.0" } }, + "node_modules/styled-components": { + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.6.tgz", + "integrity": "sha512-hGTZquGAaTqhGWldX7hhfzjnIYBZ0IXQXkCYdvF1Sq3DsUaLx6+NTHC5Jj1ooM2F68sBiVz3lvhfwQs/S3l6qg==", + "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", @@ -17953,6 +18156,29 @@ "integrity": "sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==", "requires": {} }, + "@emotion/is-prop-valid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz", + "integrity": "sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==", + "requires": { + "@emotion/memoize": "^0.8.0" + } + }, + "@emotion/memoize": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz", + "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==" + }, + "@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.3.2", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.2.tgz", @@ -18631,6 +18857,11 @@ "source-map": "^0.7.3" } }, + "@remix-run/router": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.0.2.tgz", + "integrity": "sha512-GRSOFhJzjGN+d4sKHTMSvNeUPoZiDHWmRnXfzaxrqe7dE/Nzlc8BiMSJdLDESZlndM7jIUrZ/F4yWqVYlI0rwQ==" + }, "@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -19112,6 +19343,16 @@ "@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==", + "dev": true, + "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", @@ -19271,6 +19512,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.26", + "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.26.tgz", + "integrity": "sha512-KuKJ9Z6xb93uJiIyxo/+ksS7yLjS1KzG6iv5i78dhVg/X3u5t1H7juRWqVmodIdz6wGVaIApo1u01kmFRdJHVw==", + "dev": true, + "requires": { + "@types/hoist-non-react-statics": "*", + "@types/react": "*", + "csstype": "^3.0.2" + } + }, "@types/testing-library__jest-dom": { "version": "5.14.5", "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.5.tgz", @@ -20016,6 +20268,23 @@ "@babel/helper-define-polyfill-provider": "^0.3.3" } }, + "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": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==" + }, "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", @@ -20260,6 +20529,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": "sha512-W2lPwkBkMZwFlPCXhIlYgxu+7gC/NUlCtdK652DAJ1JdgV0sTrvuPFshNPrFa1TY2JOkLhgdeEBplB4ezEa+xg==" + }, "caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", @@ -20585,6 +20859,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": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==" + }, "css-declaration-sorter": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.3.1.tgz", @@ -20692,6 +20971,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", @@ -22366,6 +22655,11 @@ "duplexer": "^0.1.2" } }, + "hamt_plus": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/hamt_plus/-/hamt_plus-1.0.2.tgz", + "integrity": "sha512-t2JXKaehnMb9paaYA7J0BX8QQAY8lwfQ9Gjf4pg/mk4krt+cmwmU652HOoWonf+7+EQV97ARPMhhVgU1ra2GhA==" + }, "handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -22420,6 +22714,21 @@ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" }, + "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", @@ -26219,6 +26528,23 @@ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==" }, + "react-router": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.4.2.tgz", + "integrity": "sha512-Rb0BAX9KHhVzT1OKhMvCDMw776aTYM0DtkxqUBP8dNBom3mPXlfNs76JNGK8wKJ1IZEY1+WGj+cvZxHVk/GiKw==", + "requires": { + "@remix-run/router": "1.0.2" + } + }, + "react-router-dom": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.4.2.tgz", + "integrity": "sha512-yM1kjoTkpfjgczPrcyWrp+OuQMyB1WleICiiGfstnQYo/S8hPEEnVjr/RdmlH6yKK4Tnj1UGXFSa7uwAtmDoLQ==", + "requires": { + "@remix-run/router": "1.0.2", + "react-router": "6.4.2" + } + }, "react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", @@ -26300,6 +26626,14 @@ "picomatch": "^2.2.1" } }, + "recoil": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/recoil/-/recoil-0.7.5.tgz", + "integrity": "sha512-GVShsj5+M/2GULWBs5WBJGcsNis/d3YvDiaKjYh3mLKXftjtmk9kfaQ8jwjoIXySCwn8/RhgJ4Sshwgzj2UpFA==", + "requires": { + "hamt_plus": "1.0.2" + } + }, "recursive-readdir": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz", @@ -26789,6 +27123,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", @@ -27081,6 +27420,23 @@ "integrity": "sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ==", "requires": {} }, + "styled-components": { + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.6.tgz", + "integrity": "sha512-hGTZquGAaTqhGWldX7hhfzjnIYBZ0IXQXkCYdvF1Sq3DsUaLx6+NTHC5Jj1ooM2F68sBiVz3lvhfwQs/S3l6qg==", + "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", diff --git a/package.json b/package.json index 665ea56..d27d5ba 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,10 @@ "@types/react-dom": "^18.0.6", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-router-dom": "^6.4.2", "react-scripts": "5.0.1", + "recoil": "^0.7.5", + "styled-components": "^5.3.6", "typescript": "^4.8.3", "web-vitals": "^2.1.4" }, @@ -39,5 +42,8 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "devDependencies": { + "@types/styled-components": "^5.1.26" } } diff --git a/public/img/0.png b/public/img/0.png new file mode 100644 index 0000000..db5cfb9 Binary files /dev/null and b/public/img/0.png differ diff --git a/public/img/1.png b/public/img/1.png new file mode 100644 index 0000000..c417a5c Binary files /dev/null and b/public/img/1.png differ diff --git a/public/img/2.png b/public/img/2.png new file mode 100644 index 0000000..6088d7b Binary files /dev/null and b/public/img/2.png differ diff --git a/public/img/3.png b/public/img/3.png new file mode 100644 index 0000000..485787a Binary files /dev/null and b/public/img/3.png differ diff --git a/public/img/4.png b/public/img/4.png new file mode 100644 index 0000000..2fb9dad Binary files /dev/null and b/public/img/4.png differ diff --git a/public/img/5.png b/public/img/5.png new file mode 100644 index 0000000..55524a4 Binary files /dev/null and b/public/img/5.png differ diff --git a/public/img/chat.png b/public/img/chat.png new file mode 100644 index 0000000..3aaff2a Binary files /dev/null and b/public/img/chat.png differ diff --git a/public/img/chat2.png b/public/img/chat2.png new file mode 100644 index 0000000..de459e0 Binary files /dev/null and b/public/img/chat2.png differ diff --git a/public/img/find.png b/public/img/find.png new file mode 100644 index 0000000..9ac1852 Binary files /dev/null and b/public/img/find.png differ diff --git a/public/img/heart.png b/public/img/heart.png new file mode 100644 index 0000000..b36b97e Binary files /dev/null and b/public/img/heart.png differ diff --git a/public/img/out.png b/public/img/out.png new file mode 100644 index 0000000..3a0e09e Binary files /dev/null and b/public/img/out.png differ diff --git a/public/img/search.png b/public/img/search.png new file mode 100644 index 0000000..4b154a9 Binary files /dev/null and b/public/img/search.png differ diff --git a/public/img/setting.png b/public/img/setting.png new file mode 100644 index 0000000..3701fef Binary files /dev/null and b/public/img/setting.png differ diff --git a/public/img/setting2.png b/public/img/setting2.png new file mode 100644 index 0000000..63cd4a9 Binary files /dev/null and b/public/img/setting2.png differ diff --git a/public/img/user.png b/public/img/user.png new file mode 100644 index 0000000..e43f22f Binary files /dev/null and b/public/img/user.png differ diff --git a/public/img/user2.png b/public/img/user2.png new file mode 100644 index 0000000..5aca4a5 Binary files /dev/null and b/public/img/user2.png differ diff --git a/public/logo192.png b/public/logo192.png deleted file mode 100644 index fc44b0a..0000000 Binary files a/public/logo192.png and /dev/null differ diff --git a/public/logo512.png b/public/logo512.png deleted file mode 100644 index a4e47a6..0000000 Binary files a/public/logo512.png and /dev/null differ diff --git a/src/App.tsx b/src/App.tsx index 5d90092..11854b4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,93 @@ +import React, { useState } from 'react'; +import InputForm from './components/InputMessage'; +import styled from 'styled-components'; + +import MessageChatForm from './components/MessageChatForm'; + +import { useRecoilState, useRecoilValue, RecoilRoot } from 'recoil'; + +import useChatRoom from './hooks/useChatRoom'; +import { useEffect } from 'react'; +import { chatRoomState } from './atom'; +import { Link, useNavigate } from 'react-router-dom'; + function App() { - return
화이팅!!
; + const navigate = useNavigate(); + const { addMsg, toggleAccount } = useChatRoom(); + const chatRoom = useRecoilValue(chatRoomState); + /* + useEffect(() => { + console.log(chatRoom); + }, [chatRoom]); + */ + + const onCLickMe = (): void => { + toggleAccount(0); + }; + const onCLickYou = (): void => { + toggleAccount(1); + }; + const goToMain = () => { + navigate('/list'); + }; + + return ( + + +
+ +

이한비

+
+
+ +

문상훈

+
+
+ +
+
+ + +
+ ); } +const AllTemplate = styled.div` + width: 350px; + height: 660px; + background-color: white; + border-radius: 20px; + box-shadow: 1px 1px 30px grey; + margin-top: 10%; + margin-bottom: 10%; + margin-left: 38%; + justify-content: center; + align-items: center; +`; + +const ImgProfAll = styled.section` + display: flex; + flex-direction: row; +`; +const ImgProf = styled.img` + width: 100px; + height: 100px; + border-radius: 25px; + transition: 0.5s; + display: block; + :hover { + cursor: grab; + width: 105px; + height: 105px; + } +`; + export default App; diff --git a/src/atom.tsx b/src/atom.tsx new file mode 100644 index 0000000..7a8812b --- /dev/null +++ b/src/atom.tsx @@ -0,0 +1,82 @@ +import { atom, selector } from 'recoil'; +import messages from '../src/db/messages.json'; +import users from '../src/db/user.json'; +import { + IChatRoomItem, + IChatRoomState, + IUserState, + IChatRoomId, +} from './interface'; + +export const messageState = atom({ + key: 'messages', + default: messages, +}); + +export const idFilterState = selector({ + key: 'rooms', + get: ({ get }) => { + const filter = get(chatRoomState); + const ids = get(messageState); + const valueOb = ids.filter((va) => filter.id === va.id)[0]; + /*if (valueOb.message.length !== 0) { + return valueOb; + } else { + return console.log('error'); + } + */ + return valueOb; + }, + set: ({ set, get }, newValue: any) => { + const realData = get(messageState); + const filter = get(chatRoomState); + const realMess = realData.filter((v) => v.id === newValue.id); + set(messageState, newValue); + }, +}); + +export const userState = atom({ + key: 'user', + default: { + users: users, + mainUser: users[0], + }, +}); + +export const chatRoomState = atom({ + key: 'chatRoom', + default: { + id: 0, + messages: [], + message: [], + currentUser: { + id: 0, + name: '이한비', + currentText: '배고파...', + }, + }, +}); + +export const chatRoomId = atom({ + key: 'chatId', + default: { + id: 1, + }, +}); + +/* +export const chatState = atom({ + key:"chats", + default:[ + {id: number, + text: string, + user: IUserInfo, + time: string} + ] +}) +export const selectCurrentUser = selector({ + key:"selectUser", + get:({get})=>{ + const original + } +})*/ diff --git a/src/components/InputMessage.tsx b/src/components/InputMessage.tsx new file mode 100644 index 0000000..1cd62ab --- /dev/null +++ b/src/components/InputMessage.tsx @@ -0,0 +1,55 @@ +import useInput from '../hooks/useInput'; +import React from 'react'; +import styled from 'styled-components'; +import { IInputForm } from '../interface'; + +const InputForm = ({ sendMsg }: IInputForm) => { + const { chat, handleChange, resetChat } = useInput(''); + const addInput = (e: React.FormEvent): void => { + e.preventDefault(); + if (!chat.trim()) { + resetChat(); + } else { + sendMsg(chat); + resetChat(); + } + }; + + return ( + + + 전송 + + ); +}; + +const StyledAddList = styled.form` + display: flex; + text-align: center; + font-weight: bolder; + font-family: 'Jua', sans-serif; + padding-bottom: 10px; +`; + +const StyledInput = styled.input` + border: 1px solid lightgrey; + border-radius: 10px; + font-family: 'Jua', sans-serif; + padding: 15px; + width: 235px; + height: 65px; + margin-left: 9px; + margin-top: 3px; +`; +const StyledButton = styled.button` + border: 1px solid lightgrey; + border-radius: 10px; + font-family: 'Jua', sans-serif; + font-size: 25px; + color: white; + background-color: #337de4; + margin-left: 5px; + margin-top: 3px; +`; + +export default InputForm; diff --git a/src/components/MessageChat.tsx b/src/components/MessageChat.tsx new file mode 100644 index 0000000..5a5f425 --- /dev/null +++ b/src/components/MessageChat.tsx @@ -0,0 +1,120 @@ +import { IMessageType } from '../interface'; +import { useRecoilValue } from 'recoil'; +import { chatRoomState } from '../atom'; +import styled, { css } from 'styled-components'; +import { useParams } from 'react-router-dom'; + +const MessageChat = ({ messages }: { messages: IMessageType }) => { + const { currentUser } = useRecoilValue(chatRoomState); + + const isUser = messages.user.id === currentUser.id; + + let params = useParams(); + let num = params.id; + console.log(num); + + return ( + +
+ + + + {messages.text} + +
+ + {messages.time} +
+ ); +}; + +const MessageProfAll = styled.section` + display: flex; + flex-direction: column; +`; + +const Messageimg = styled.img<{ + isUser: boolean; + _id: number; + _currentUserId: number; +}>` + width: 38px; + height: 38px; + border-radius: 50%; + background-size: cover; + + ${({ _id, _currentUserId }) => + _id === _currentUserId && + css` + align-self: flex-end; + `} +`; + +const MessageTime = styled.p<{ isUser: boolean }>` + font-size: 0.5rem; + color: lightgrey; + display: flex; + align-items: flex-end; + ${({ isUser }) => + isUser + ? css` + margin-left: 0.5rem; + ` + : css` + margin-right: 0.5rem; + `} +`; + +const MessageBox = styled.section<{ isUser: boolean }>` + display: flex; + flex-direction: ${({ isUser }) => isUser && 'row-reverse'}; + padding-bottom: 1rem; +`; + +const MessageText = styled.p<{ isUser: boolean }>` + padding: 0.65rem; + + font-size: 0.75rem; + margin: 0.4rem; + margin-top: 0.1px; + ${({ isUser }) => + isUser + ? css` + border-radius: 0.5rem 0 0.5rem 0.5rem; + background-color: #337de4; + color: #ffffff; + font-weight: bold; + margin-right: 40px; + ` + : css` + border-radius: 0 0.5rem 0.5rem 0.5rem; + background-color: #f1f1f3; + font-weight: bold; + margin-left: 40px; + `}; +`; + +const MessageUser = styled.p<{ isUser: boolean }>` + font-size: 0.5rem; + color: white; + margin: 0px; + display: flex; + ${({ isUser }) => + isUser + ? css` + justify-content: flex-end; + margin-right: 0.2rem; + font-weight: bold; + ` + : css` + margin-left: 0.2rem; + font-weight: bold; + `} +`; + +export default MessageChat; diff --git a/src/components/MessageChatForm.tsx b/src/components/MessageChatForm.tsx new file mode 100644 index 0000000..342ae5c --- /dev/null +++ b/src/components/MessageChatForm.tsx @@ -0,0 +1,81 @@ +import MessageChat from './MessageChat'; +import { useEffect, useRef } from 'react'; +import { useRecoilValue } from 'recoil'; +import { chatRoomState, idFilterState, messageState, userState } from '../atom'; +import styled from 'styled-components'; + +import { useParams } from 'react-router-dom'; +import { IMessageType } from '../interface'; + +const MessageChatForm = () => { + const messageWrapperRef = useRef(null); + const { currentUser } = useRecoilValue(chatRoomState); + + //currentRoomId를 사용해서 room번호를 구분시켜줘야함. 이 id값은 버튼 누를때마다 바뀌어야 함!그래서 그 바뀐번호로 렌더링해줘야함 + const messageData = useRecoilValue(messageState); + let params = useParams(); + + const num = params.id; + const realNum = Number(num); //나와의 채팅방하려면, -1을 없애야함 + console.log(realNum); + + //room 번호대로 라우팅 페이지 만들어서 그거대로 messageData에 접근! + const scrollToBottom = () => { + if (messageWrapperRef.current) { + messageWrapperRef.current.scrollTop = + messageWrapperRef.current.scrollHeight; + } + }; + const realMessage = useRecoilValue(idFilterState); + console.log(realMessage); + + useEffect(() => { + scrollToBottom(); + }, [messageData]); + + return ( + + {realMessage.messages.map((msg: IMessageType) => ( + + + + ))} + {realMessage.message.map((msg: IMessageType) => ( + + + + ))} + + ); +}; + +const Wrapper = styled.section` + display: flex; + flex-direction: column; + height: 464px; + overflow: auto; + padding: 1rem 0.8rem 0 0.8rem; + ::-webkit-scrollbar { + width: 0.9rem; + } + ::-webkit-scrollbar-thumb { + background-color: white; + border-radius: 1rem; + background-clip: padding-box; + border: 0.3rem solid transparent; + } + background: black; +`; + +const MessageChatContainer = styled.section<{ isUser: boolean }>` + display: flex; + justify-content: ${({ isUser }) => isUser && 'flex-end'}; +`; + +export default MessageChatForm; diff --git a/src/db/messages.json b/src/db/messages.json new file mode 100644 index 0000000..7a4fc42 --- /dev/null +++ b/src/db/messages.json @@ -0,0 +1,281 @@ +[ + { + "id": 0, + "user": [ + { + "id": 22, + "name": "이한비", + "currentText": "배고파..." + } + ], + "messages": [ + { + "id": 23, + "user": { + "id": 0, + "name": "이한비", + "currentText": "배고파..." + }, + "time": "04:41", + "text": "과제가" + }, + { + "id": 24, + "user": { + "id": 0, + "name": "이한비", + "currentText": "배고파..." + }, + "time": "04:42", + "text": "너무 어려워요.." + } + ], + "message": [], + "currentUser": { + "id": 0, + "name": "이한비", + "currentText": "배고파..." + } + }, + { + "id": 1, + "user": [ + { + "id": 0, + "name": "이한비", + "currentText": "배고파..." + }, + { + "id": 1, + "name": "문상훈", + "currentText": "오당기 많은 관심 부탁ㅎㅎ" + } + ], + "messages": [ + { + "id": 1, + "user": { + "id": 0, + "name": "이한비", + "currentText": "배고파..." + }, + "time": "07:41", + "text": "과제가" + }, + { + "id": 2, + "user": { + "id": 0, + "name": "이한비", + "currentText": "배고파..." + }, + "time": "07:42", + "text": "너무 어려워요.." + }, + { + "id": 3, + "user": { + "id": 1, + "name": "문상훈", + "currentText": "오당기 많은 관심 부탁ㅎㅎ" + }, + "time": "07:43", + "text": "개쳐발렸됴" + } + ], + "message": [], + "currentUser": { + "id": 0, + "name": "이한비", + "currentText": "배고파..." + } + }, + { + "id": 2, + "user": [ + { + "id": 0, + "name": "이한비", + "currentText": "배고파..." + }, + { "id": 2, "name": "문쌤", "currentText": "한국지리 일타강사!" } + ], + "messages": [ + { + "id": 4, + "user": { + "id": 0, + "name": "이한비", + "currentText": "배고파..." + }, + "time": "08:41", + "text": "선생님" + }, + { + "id": 5, + "user": { + "id": 0, + "name": "이한비", + "currentText": "배고파..." + }, + "time": "08:42", + "text": "에어드랍 확인해보세요" + }, + { + "id": 6, + "user": { + "id": 2, + "name": "문쌤", + "currentText": "한국지리 일타강사!" + }, + "time": "08:43", + "text": "내가 돼지야?" + } + ], + "message": [], + "currentUser": { + "id": 0, + "name": "이한비", + "currentText": "배고파..." + } + }, + { + "id": 3, + "user": [ + { + "id": 0, + "name": "이한비", + "currentText": "배고파..." + }, + { "id": 3, "name": "문이병", "currentText": "만년 이등병ㅋㅋ" } + ], + "messages": [ + { + "id": 7, + "user": { + "id": 0, + "name": "이한비", + "currentText": "배고파..." + }, + "time": "09:45", + "text": "문이병" + }, + { + "id": 8, + "user": { + "id": 0, + "name": "이한비", + "currentText": "배고파..." + }, + "time": "09:46", + "text": "일로와바" + }, + { + "id": 9, + "user": { "id": 3, "name": "문이병", "currentText": "만년 이등병ㅋㅋ" }, + "time": "09:47", + "text": "저한테 왜그러십니까!!!" + } + ], + "message": [], + "currentUser": { + "id": 0, + "name": "이한비", + "currentText": "배고파..." + } + }, + { + "id": 4, + "user": [ + { + "id": 0, + "name": "이한비", + "currentText": "배고파..." + }, + { "id": 4, "name": "김인영", "currentText": "터벅터벅..." } + ], + "messages": [ + { + "id": 100, + "user": { + "id": 0, + "name": "이한비", + "currentText": "배고파..." + }, + "time": "11:45", + "text": "인영아" + }, + { + "id": 101, + "user": { + "id": 0, + "name": "이한비", + "currentText": "배고파..." + }, + "time": "11:46", + "text": "뭐해" + }, + { + "id": 102, + "user": { "id": 4, "name": "김인영", "currentText": "터벅터벅..." }, + "time": "11:47", + "text": "아 나 진짜 공부 시작하려고 했엉~" + } + ], + "message": [], + "currentUser": { + "id": 0, + "name": "이한비", + "currentText": "배고파..." + } + }, + { + "id": 5, + "user": [ + { + "id": 0, + "name": "이한비", + "currentText": "배고파..." + }, + { "id": 5, "name": "김수혁상병", "currentText": "열심히 살자!" } + ], + "messages": [ + { + "id": 200, + "user": { + "id": 0, + "name": "이한비", + "currentText": "배고파..." + }, + "time": "15:45", + "text": "수혁아" + }, + { + "id": 201, + "user": { + "id": 0, + "name": "이한비", + "currentText": "배고파..." + }, + "time": "15:46", + "text": "뭐해" + }, + { + "id": 202, + "user": { + "id": 5, + "name": "김수혁상병", + "currentText": "열심히 살자!" + }, + "time": "15:47", + "text": "상훈이 요즘 뭐하니" + } + ], + "message": [], + "currentUser": { + "id": 0, + "name": "이한비", + "currentText": "배고파..." + } + } +] diff --git a/src/db/user.json b/src/db/user.json new file mode 100644 index 0000000..76157ac --- /dev/null +++ b/src/db/user.json @@ -0,0 +1,16 @@ +[ + { + "id": 0, + "name": "이한비", + "currentText": "배고파..." + }, + { + "id": 1, + "name": "문상훈", + "currentText": "오당기 많은 관심 부탁ㅎㅎ" + }, + { "id": 2, "name": "문쌤", "currentText": "한국지리 일타강사!" }, + { "id": 3, "name": "문이병", "currentText": "만년 이등병ㅋㅋ" }, + { "id": 4, "name": "김인영", "currentText": "터벅터벅..." }, + { "id": 5, "name": "김수혁상병", "currentText": "열심히 살자!" } +] diff --git a/src/hooks/useChatRoom.tsx b/src/hooks/useChatRoom.tsx new file mode 100644 index 0000000..37bf187 --- /dev/null +++ b/src/hooks/useChatRoom.tsx @@ -0,0 +1,64 @@ +import { chatRoomState, messageState, userState, idFilterState } from '../atom'; +import { useRecoilState, useRecoilValue, useResetRecoilState } from 'recoil'; +import { useParams } from 'react-router-dom'; +import { useEffect } from 'react'; + +const useChatRoom = () => { + const userList = useRecoilValue(userState); + const [chatState, setChatState] = useRecoilState(chatRoomState); + const allData = useRecoilValue(messageState); + console.log(allData); + + const [newC, setNewC] = useRecoilState(chatRoomState); + + const [chatId, setChatId] = useRecoilState(idFilterState); + console.log(chatId); + + //메세지 추가 + + const addMsg = (text: string): void => { + const curTime = + String(new Date().getHours()).padStart(2, '0') + + ':' + + String(new Date().getMinutes()).padStart(2, '0'); + const messageOb = { + id: new Date().valueOf(), + user: newC.currentUser, + time: curTime, + text: text, + }; + console.log(chatState); + + const newOb = { + ...chatId, + currentUser: newC.currentUser, + message: [...chatId.message, messageOb], + }; + setNewC(newOb); + + console.log(newC.messages); + //newC or chatId인데 newC를 하면...currentUser newCcureentUser이랑 chatId의 user값이 같으면 넣어주기 아니면 x이런식으로 해보기!값을 구분해줘야됨 비어있을때도 생각해줘야함 + + setChatId([ + ...allData.map((data) => (data.id === newOb.id ? newOb : data)), + newOb, + ]); + //객체를 그냥 선언해줘서 그걸 사용하면 오류!!해결!! + }; + + const toggleAccount = (id: number): void => { + let toggleId = 0; + if (id >= 1 && chatState.currentUser.id === userList.mainUser.id) { + toggleId = userList.users.findIndex((user) => user.id === id); + } + setChatState({ + ...chatState, + currentUser: userList.users[toggleId], + }); + console.log(id); + }; + + return { addMsg, toggleAccount }; +}; + +export default useChatRoom; diff --git a/src/hooks/useInput.tsx b/src/hooks/useInput.tsx new file mode 100644 index 0000000..4beb8ee --- /dev/null +++ b/src/hooks/useInput.tsx @@ -0,0 +1,17 @@ +import { useState } from 'react'; + +const useInput = (initialValue: string) => { + const [chat, setChat] = useState(initialValue); + + const handleChange = (e: React.ChangeEvent): void => { + setChat(e.target.value); + }; + + const resetChat = (): void => { + setChat(''); + }; + + return { chat, handleChange, resetChat }; +}; + +export default useInput; diff --git a/src/index.tsx b/src/index.tsx index 086c86b..1f9fa37 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,10 +1,28 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; +import { RecoilRoot } from 'recoil'; import App from './App'; +import { BrowserRouter, Routes, Route } from 'react-router-dom'; +import List from './pages/List'; +import MainPage from './pages/MainPage'; +import Room from './pages/Room'; +import Search from './pages/Search'; +import Setting from './pages/Setting'; +import OwnChat from './pages/OwnChat'; +const rootElement = document.getElementById('root') as HTMLElement; + +const root = ReactDOM.createRoot(rootElement); -const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); root.render( - - - + + + + } /> + } /> + } /> + } /> + } /> + + + ); diff --git a/src/interface/chatRoom.tsx b/src/interface/chatRoom.tsx new file mode 100644 index 0000000..df32d5c --- /dev/null +++ b/src/interface/chatRoom.tsx @@ -0,0 +1,27 @@ +import { IUserInfo } from './user'; +import { IMessageType } from './message'; + +export interface IChatRoomItem { + id: number; + user: IUserInfo[]; + messages: IMessageType[]; + message: IMessageType[]; + currentUser: IUserInfo; +} + +export interface IChatRoomState { + message: IMessageType[]; + currentUser: IUserInfo; + id: number; + messages: IMessageType[]; +} + +export interface IChatRoomId { + id: number; +} + +export interface IChatRooms { + message: IMessageType[]; + currentUser: IUserInfo; + id: number; +} diff --git a/src/interface/index.tsx b/src/interface/index.tsx new file mode 100644 index 0000000..1c1105a --- /dev/null +++ b/src/interface/index.tsx @@ -0,0 +1,19 @@ +import { IUserState, IUserInfo } from './user'; +import { IInputForm, IMessageType } from './message'; +import { + IChatRoomItem, + IChatRoomState, + IChatRoomId, + IChatRooms, +} from './chatRoom'; + +export type { + IUserState, + IUserInfo, + IInputForm, + IMessageType, + IChatRoomItem, + IChatRoomState, + IChatRoomId, + IChatRooms, +}; diff --git a/src/interface/message.tsx b/src/interface/message.tsx new file mode 100644 index 0000000..b0b8f35 --- /dev/null +++ b/src/interface/message.tsx @@ -0,0 +1,12 @@ +import { IUserInfo } from './user'; + +export interface IMessageType { + id: number; + text: string; + user: IUserInfo; + time: string; +} + +export interface IInputForm { + sendMsg: (text: string) => void; +} diff --git a/src/interface/user.tsx b/src/interface/user.tsx new file mode 100644 index 0000000..5b646dc --- /dev/null +++ b/src/interface/user.tsx @@ -0,0 +1,10 @@ +export interface IUserInfo { + id: number; + name: string; + currentText: string; +} + +export interface IUserState { + users: IUserInfo[]; + mainUser: IUserInfo; +} diff --git a/src/pages/List.tsx b/src/pages/List.tsx new file mode 100644 index 0000000..7be514e --- /dev/null +++ b/src/pages/List.tsx @@ -0,0 +1,200 @@ +import React, { useEffect, useRef } from 'react'; +import { Link, useNavigate } from 'react-router-dom'; +import styled from 'styled-components'; +import { chatRoomState, messageState, userState } from '../atom'; +import { useRecoilValue, useRecoilState, useResetRecoilState } from 'recoil'; +import useChatRoom from '../hooks/useChatRoom'; +import userEvent from '@testing-library/user-event'; + +function List() { + const messageWrapperRef = useRef(null); + const chatRoomList = useRecoilValue(messageState); + + const UserList = useRecoilValue(userState); + const { toggleAccount } = useChatRoom(); + + /* const goToChat = ({ userList }: any) => { + navigate(`/room/${chatRoomList.id}`); + }; + */ + const scrollToBottom = () => { + if (messageWrapperRef.current) { + messageWrapperRef.current.scrollTop = + messageWrapperRef.current.scrollHeight; + } + }; + useEffect(() => {}, [chatRoomList]); + const ResetData = useResetRecoilState(chatRoomState); + + const onClickMe = (): void => { + toggleAccount(0); + ResetData(); + }; + + //recoil로 전체 가져오기!! [chatRoom,setChatROom]useRecoilState, 이한비 값은 안받는걸로 해주기 + const TempList = ({ userList, realId }: any) => { + const [chatRoom, setChatRoom] = useRecoilState(messageState); + const testRoom = useRecoilValue(chatRoomState); + + const newData = chatRoom.filter((txt) => txt.id === Number(userList.id))[0]; + + const realNum = newData.messages.length - 1; + const realNum2 = newData.message.length - 1; + + if (newData.message.length === 0) { + return ( + + + + {userList.name} + {newData.messages[realNum].text} + {newData.messages[realNum].time} + + + ); + } else { + return ( + + + + {userList.name} + {newData.message[realNum2].text} + {newData.message[realNum2].time} + + + ); + } + }; + const newUserList = UserList.users.slice(0, 6); + + return ( + + + + + + + + + + + + + + + +

채팅

+ + {newUserList.map((userList) => ( + + ))} + +
+
+ ); +} +const Wrapper = styled.section` + display: flex; + flex-direction: column; + height: 584px; + + overflow: auto; + padding: 0.5rem 0.8rem 0 0.2rem; + ::-webkit-scrollbar { + width: 0.9rem; + } + ::-webkit-scrollbar-thumb { + background-color: black; + border-radius: 1rem; + background-clip: padding-box; + border: 0.3rem solid transparent; + } +`; + +const CurrentTime = styled.div` + font-size: 12px; + font-weight: light; + margin-left: 125px; + color: #d8d0d0; +`; +const AllTemp = styled.div` + display: flex; + flex-direction: row; + border-bottom: 1px solid lightgray; + margin-bottom: 1px; + padding-top: 0.05px; +`; +const ChatLink = styled(Link)` + display: flex; + flex-direction: column; + + padding: 28px 3px; + align-items: left; + + text-decoration: none; + font-size: 18px; + color: black; + font-weight: bold; + margin-bottom: 3px; +`; + +const MainImg = styled.img` + width: 38px; + height: 38px; + border-radius: 50%; + background-size: cover; + margin: 15px; + margin-top: 15px; +`; + +const CurrentText = styled.div` + color: #1c191976; + font-size: small; + font-weight: 650; +`; +const BodyTemplate = styled.div` + box-sizing: border-box; + padding: 0 3px; + margin-top: 1px; +`; + +const RealAll = styled.div` + width: 350px; + height: 680px; + background-color: white; + border-radius: 25px; + box-shadow: 1px 1px 30px grey; + margin-top: 10%; + margin-bottom: 10%; + margin-left: 38%; + display: grid; + grid-template-columns: 68px auto; +`; +const SideTemplate = styled.div` + background-color: rgb(230, 230, 230); + border-right: 1px solid rgb(223, 223, 223); + border-radius: 25px 0px 0px 25px; + display: block; +`; +const ShowImg = styled.img` + width: 58px; + height: 58px; + padding: 28px 15px; + border-radius: 50%; + background-size: cover; +`; + +const AllTemplate = styled.div` + width: 350px; + height: 660px; + background-color: white; + border-radius: 20px; + box-shadow: 1px 1px 30px grey; + margin-top: 10%; + margin-bottom: 10%; + margin-left: 38%; + justify-content: center; + align-items: center; +`; + +export default List; diff --git a/src/pages/MainPage.tsx b/src/pages/MainPage.tsx new file mode 100644 index 0000000..cc7769e --- /dev/null +++ b/src/pages/MainPage.tsx @@ -0,0 +1,188 @@ +import React, { useState } from 'react'; +import { Link, useNavigate } from 'react-router-dom'; +import { useRecoilValue, useResetRecoilState } from 'recoil'; +import styled from 'styled-components'; +import { chatRoomState, userState } from '../atom'; +import useChatRoom from '../hooks/useChatRoom'; + +function MainPage() { + const navigate = useNavigate(); + const UserList = useRecoilValue(userState); + + const goToList = () => { + navigate('/Search'); + }; + + //css공통된 요소 많은것 props로 넘기는걸로 바꾸기! + const TempList = ({ userList, realId }: any) => { + if (userList.id !== 0) { + return ( +
+ + + + {userList.name} + {userList.currentText} + + +
+ ); + } else { + return ( +
+ + + + {userList.name} + {userList.currentText} + + + 친구{UserList.users.length - 1} +
+ ); + } + }; + return ( +
+ + + + + + + + + + + + + + + + +

친구

+ + +
+ + {UserList.users.map((userList) => ( + + ))} +
+
+
+ ); +} +const AllTemp = styled.div` + display: flex; + flex-direction: row; +`; +const CurrentText = styled.div` + font-size: 11px; + color: grey; + font-weight: bold; +`; +const FriendNum = styled.h6` + margin-top: 10px; + color: grey; + margin-bottom: 20px; +`; +const ShowImg2 = styled.img` + width: 60px; + height: 60px; + border-radius: 50%; + background-size: cover; + margin: 0 15px; + border-radius: 10px; +`; +const HeaderTemplate = styled.div` + display: flex; + flex-direction: row; +`; +const FindButton = styled.img` + width: 18px; + height: 18px; + margin-left: 200px; + margin-top: 18px; +`; + +const MainImg = styled.img` + width: 38px; + height: 38px; + border-radius: 50%; + background-size: cover; + margin: 15px; + margin-top: 15px; +`; +const ShowImg = styled.img` + width: 48px; + height: 48px; + border-radius: 50%; + background-size: cover; + margin: 0 15px; + border-radius: 10px; +`; +const MainId = styled(Link)` + display: flex; + -webkit-box-align: center; + align-items: center; + padding: 30px 25px; + text-decoration: none; + border-bottom: 1px solid grey; + padding-bottom: 10px; + padding-left: 1px; + box-sizing: border-box; + color: black; + border-radius: 3px; + border-bottom-color: #66616190; + + /*margin: 0px 10px;*/ +`; +const OneId = styled(Link)` + display: flex; + flex-direction: column; + padding: 6px; + -webkit-box-align: center; + align-items: left; + padding-bottom: 20px; + text-decoration: none; + + color: black; +`; +const BodyTemplate = styled.div` + box-sizing: border-box; + padding: 0 3px; +`; +const RealAll = styled.div` + width: 350px; + height: 680px; + background-color: white; + border-radius: 25px; + box-shadow: 1px 1px 30px grey; + margin-top: 10%; + margin-bottom: 10%; + margin-left: 38%; + display: grid; + grid-template-columns: 68px auto; +`; +const SideTemplate = styled.div` + background-color: rgb(230, 230, 230); + border-right: 1px solid rgb(223, 223, 223); + border-radius: 25px 0px 0px 25px; + display: block; +`; + +const AllTemplate = styled.div` + width: 350px; + height: 660px; + background-color: white; + border-radius: 20px; + box-shadow: 1px 1px 30px grey; + margin-top: 10%; + margin-bottom: 10%; + margin-left: 38%; + justify-content: center; + align-items: center; +`; + +export default MainPage; diff --git a/src/pages/OwnChat.tsx b/src/pages/OwnChat.tsx new file mode 100644 index 0000000..7ca2c51 --- /dev/null +++ b/src/pages/OwnChat.tsx @@ -0,0 +1,62 @@ +import InputForm from '../components/InputMessage'; +import styled from 'styled-components'; +import MessageChatForm from '../components/MessageChatForm'; + +import useChatRoom from '../hooks/useChatRoom'; + +import { useNavigate } from 'react-router-dom'; + +function OwnChat() { + const navigate = useNavigate(); + const { addMsg, toggleAccount } = useChatRoom(); + + const goToMain = () => { + toggleAccount(0); + navigate('/'); + }; + return ( + + +
+ +
+
+ +
+
+ + +
+ ); +} +const MoveButton = styled.img` + width: 30px; + height: 30px; + margin-left: 250px; +`; +const AllTemplate = styled.div` + width: 350px; + height: 660px; + background-color: white; + border-radius: 20px; + box-shadow: 1px 1px 30px grey; + margin-top: 10%; + margin-bottom: 10%; + margin-left: 38%; + justify-content: center; + align-items: center; +`; + +const ImgProfAll = styled.section` + display: flex; + flex-direction: row; +`; +const ImgProf = styled.img` + width: 53px; + height: 53px; + border-radius: 25px; + transition: 0.5s; + display: block; +`; + +export default OwnChat; diff --git a/src/pages/Room.tsx b/src/pages/Room.tsx new file mode 100644 index 0000000..cf2d9d8 --- /dev/null +++ b/src/pages/Room.tsx @@ -0,0 +1,100 @@ +import React, { useState } from 'react'; +import InputForm from '../components/InputMessage'; +import styled from 'styled-components'; +import MessageChatForm from '../components/MessageChatForm'; +import { useRecoilState, useRecoilValue } from 'recoil'; + +import useChatRoom from '../hooks/useChatRoom'; +import { useEffect } from 'react'; +import { chatRoomState, messageState, userState } from '../atom'; +import { useNavigate, useParams } from 'react-router-dom'; +import { idText } from 'typescript'; + +function Room() { + const navigate = useNavigate(); + const { addMsg, toggleAccount } = useChatRoom(); + + const UserList = useRecoilValue(userState); + const [id, setId] = useRecoilState(chatRoomState); + + let params = useParams(); + + let num = params.id; + + const realNum = Number(num); + + useEffect(() => { + setId((prevId) => { + const variable = { ...prevId }; + variable.id = realNum; + + return { ...variable }; + }); + }, []); + + const onCLickYou = (): void => { + toggleAccount(realNum); + }; + + const goToMain = () => { + toggleAccount(0); + navigate('/list'); + }; + + return ( + + + + + + + {UserList.users[realNum].name} + + + + + ); +} +const Text = styled.div` + margin-top: 1px; + margin-left: 10px; + font-size: 13px; +`; +const MoveButton = styled.img` + width: 30px; + height: 30px; + margin-left: 250px; +`; +const AllTemplate = styled.div` + width: 350px; + height: 660px; + background-color: white; + border-radius: 20px; + box-shadow: 1px 1px 30px grey; + margin-top: 10%; + margin-bottom: 10%; + margin-left: 38%; + justify-content: center; + align-items: center; +`; + +const ImgProfAll = styled.section` + display: flex; + flex-direction: row; +`; +const ImgProf = styled.img` + width: 53px; + height: 53px; + border-radius: 25px; + transition: 0.5s; + display: block; + margin-left: 5px; + margin-top: 5px; + margin-bottom: 0.05px; +`; + +export default Room; diff --git a/src/pages/Search.tsx b/src/pages/Search.tsx new file mode 100644 index 0000000..83aee94 --- /dev/null +++ b/src/pages/Search.tsx @@ -0,0 +1,157 @@ +import React, { useState } from 'react'; +import { Link, useNavigate } from 'react-router-dom'; +import { useRecoilValue } from 'recoil'; +import styled from 'styled-components'; +import { userState } from '../atom'; + +function Search() { + const navigate = useNavigate(); + const UserList = useRecoilValue(userState); + + const [search, setSearch] = useState(''); + const onChange = (e: React.ChangeEvent) => { + setSearch(e.target.value); + }; + const filterName = UserList.users.filter((p) => { + return p.name.replace(' ', '').includes(search); + }); + const goToMain = () => { + navigate('/'); + }; + return ( +
+ + + + + + + + + + + + + + + +

친구

+ + +
+ + {filterName.map((m) => ( + + + + {m.name} + {m.currentText} + + + ))} +
+
+
+ ); +} +const AllTemp = styled.div` + display: flex; + flex-direction: row; + border-bottom: 1px solid lightgray; + padding-bottom: 10px; + padding-top: 10px; +`; +const CurrentText = styled.div` + font-size: 11px; + color: grey; + font-weight: bold; +`; +const InputName = styled.input` + background-color: rgb(230, 230, 230); + border: 1px solid rgb(240, 240, 240); + font-size: 12px; + padding: 3px 10px; + box-sizing: border-box; + width: calc(100% - 36px); + margin: 9px 18px; + border-radius: 20px 20px 20px 20px; +`; +const HeaderTemplate = styled.div` + display: flex; + flex-direction: row; + margin-left: 3px; +`; +const FindButton = styled.img` + width: 18px; + height: 18px; + margin-left: 200px; + margin-top: 18px; +`; + +const MainImg = styled.img` + width: 38px; + height: 38px; + border-radius: 50%; + background-size: cover; + margin: 15px; + margin-top: 15px; +`; +const ShowImg = styled.img` + width: 48px; + height: 48px; + border-radius: 50%; + background-size: cover; + //margin: 0 15px; + //margin-left: -5px; + //argin-right: 5px; + border-radius: 3px; + padding-left: 15px; + padding-right: 9px; +`; +const MainId = styled(Link)` + display: flex; + flex-direction: column; + -webkit-box-align: center; + align-items: left; + //padding: 30px 25px; + //padding-left: 5px; + padding-bottom: 10px; + //padding-top: 15px; + text-decoration: none; + + box-sizing: border-box; + color: black; + border-radius: 3px; + + /*margin: 0px 10px;*/ +`; + +const BodyTemplate = styled.div` + box-sizing: border-box; + padding: 3 2px; +`; +const RealAll = styled.div` + width: 350px; + height: 680px; + background-color: white; + border-radius: 25px; + box-shadow: 1px 1px 30px grey; + margin-top: 10%; + margin-bottom: 10%; + margin-left: 38%; + display: grid; + grid-template-columns: 68px auto; +`; +const SideTemplate = styled.div` + background-color: rgb(230, 230, 230); + border-right: 1px solid rgb(223, 223, 223); + display: block; + border-radius: 25px 0px 0px 25px; +`; + +export default Search; diff --git a/src/pages/Setting.tsx b/src/pages/Setting.tsx new file mode 100644 index 0000000..f3449a4 --- /dev/null +++ b/src/pages/Setting.tsx @@ -0,0 +1,93 @@ +import React, { useEffect } from 'react'; +import { Link, useNavigate } from 'react-router-dom'; +import styled from 'styled-components'; +import { chatRoomState, messageState, userState } from '../atom'; +import { useRecoilValue, useRecoilState, useResetRecoilState } from 'recoil'; +import useChatRoom from '../hooks/useChatRoom'; + +function Setting() { + const goToGit = () => {}; + return ( +
+ + + + + + + + + + + + + + +

Setting

+ + + Github + + + + + tistory + +
+
+
+ ); +} +const PageOut = styled.a` + text-decoration-line: none; + padding-right: 50px; + align-items: center; + display: flex; + flex-direction: column; + margin-bottom: 5px; + font-weight: bolder; + color: black; +`; +const ComImg = styled.img` + width: 50px; + height: 50px; + display: flex; + flex-direction: column; + align-items: center; + //margin-left: 100px; +`; + +const MainImg = styled.img` + width: 38px; + height: 38px; + border-radius: 50%; + background-size: cover; + margin: 15px; + margin-top: 15px; +`; + +const BodyTemplate = styled.div` + box-sizing: border-box; + padding: 0 3px; +`; + +const RealAll = styled.div` + width: 350px; + height: 680px; + background-color: white; + border-radius: 25px; + box-shadow: 1px 1px 30px grey; + margin-top: 10%; + margin-bottom: 10%; + margin-left: 38%; + display: grid; + grid-template-columns: 68px auto; +`; +const SideTemplate = styled.div` + background-color: rgb(230, 230, 230); + border-right: 1px solid rgb(223, 223, 223); + border-radius: 25px 0px 0px 25px; + display: block; +`; + +export default Setting;