diff --git a/package-lock.json b/package-lock.json index 43e807c..e50181a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,9 +11,19 @@ "@testing-library/jest-dom": "^5.16.4", "@testing-library/react": "^13.1.1", "@testing-library/user-event": "^13.5.0", + "@types/jest": "^27.4.1", + "@types/node": "^17.0.31", + "@types/react": "^18.0.8", + "@types/react-dom": "^18.0.3", + "@types/styled-components": "^5.1.25", "react": "^18.1.0", "react-dom": "^18.1.0", + "react-icons": "^4.3.1", + "react-router-dom": "^6.3.0", "react-scripts": "5.0.1", + "recoil": "^0.7.2", + "styled-components": "^5.3.5", + "typescript": "^4.6.4", "web-vitals": "^2.1.4" } }, @@ -1972,6 +1982,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", @@ -3515,6 +3548,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", @@ -3574,9 +3616,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", @@ -3674,6 +3716,16 @@ "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==", + "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", @@ -4702,6 +4754,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 +5088,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 +5559,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 +5748,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", @@ -7909,6 +8004,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": "sha1-4hwlKWjH4zsg9qGwlM2FeHomVgE=" + }, "node_modules/handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -7990,6 +8090,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,6 +13527,14 @@ "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", @@ -13419,6 +13548,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", @@ -13515,6 +13668,25 @@ "node": ">=8.10.0" } }, + "node_modules/recoil": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/recoil/-/recoil-0.7.2.tgz", + "integrity": "sha512-OT4pI7FOUHcIoRtjsL5Lqq+lFFzQfir4MIbUkqyJ3nqv3WfBP1pHepyurqTsK5gw+T+I2R8+uOD28yH+Lg5o4g==", + "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", @@ -14172,6 +14344,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 +14746,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 +15338,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" @@ -17516,6 +17722,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", @@ -18620,6 +18849,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", @@ -18679,9 +18917,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", @@ -18779,6 +19017,16 @@ "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==", + "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", @@ -19520,6 +19768,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 +20030,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 +20390,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 +20502,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", @@ -21857,6 +22142,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": "sha1-4hwlKWjH4zsg9qGwlM2FeHomVgE=" + }, "handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -21911,6 +22201,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,6 +26005,12 @@ "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", @@ -25702,6 +26021,23 @@ "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", @@ -25775,6 +26111,14 @@ "picomatch": "^2.2.1" } }, + "recoil": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/recoil/-/recoil-0.7.2.tgz", + "integrity": "sha512-OT4pI7FOUHcIoRtjsL5Lqq+lFFzQfir4MIbUkqyJ3nqv3WfBP1pHepyurqTsK5gw+T+I2R8+uOD28yH+Lg5o4g==", + "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", @@ -26254,6 +26598,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 +26902,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 +27348,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", diff --git a/package.json b/package.json index 7f031bc..e7b1fd1 100644 --- a/package.json +++ b/package.json @@ -6,9 +6,19 @@ "@testing-library/jest-dom": "^5.16.4", "@testing-library/react": "^13.1.1", "@testing-library/user-event": "^13.5.0", + "@types/jest": "^27.4.1", + "@types/node": "^17.0.31", + "@types/react": "^18.0.8", + "@types/react-dom": "^18.0.3", + "@types/styled-components": "^5.1.25", "react": "^18.1.0", "react-dom": "^18.1.0", + "react-icons": "^4.3.1", + "react-router-dom": "^6.3.0", "react-scripts": "5.0.1", + "recoil": "^0.7.2", + "styled-components": "^5.3.5", + "typescript": "^4.6.4", "web-vitals": "^2.1.4" }, "scripts": { diff --git a/public/images/DAY6.jpg b/public/images/DAY6.jpg new file mode 100644 index 0000000..b696410 Binary files /dev/null and b/public/images/DAY6.jpg differ diff --git a/public/images/backbutton.png b/public/images/backbutton.png new file mode 100644 index 0000000..381518c Binary files /dev/null and b/public/images/backbutton.png differ diff --git a/public/images/colde.jpg b/public/images/colde.jpg new file mode 100644 index 0000000..9a780c3 Binary files /dev/null and b/public/images/colde.jpg differ diff --git a/public/images/damons year.jpg b/public/images/damons year.jpg new file mode 100644 index 0000000..78ee21c Binary files /dev/null and b/public/images/damons year.jpg differ diff --git a/public/images/ducky.jpg b/public/images/ducky.jpg new file mode 100644 index 0000000..78f445f Binary files /dev/null and b/public/images/ducky.jpg differ diff --git a/public/images/ghost.jpg b/public/images/ghost.jpg new file mode 100644 index 0000000..493304b Binary files /dev/null and b/public/images/ghost.jpg differ diff --git a/public/images/ghost.png b/public/images/ghost.png new file mode 100644 index 0000000..493304b Binary files /dev/null and b/public/images/ghost.png differ diff --git a/public/images/joan.jpg b/public/images/joan.jpg new file mode 100644 index 0000000..d060f41 Binary files /dev/null and b/public/images/joan.jpg differ diff --git a/public/images/keshi.jpg b/public/images/keshi.jpg new file mode 100644 index 0000000..a5756fe Binary files /dev/null and b/public/images/keshi.jpg differ diff --git a/public/images/kitty.jpg b/public/images/kitty.jpg new file mode 100644 index 0000000..0bf33c0 Binary files /dev/null and b/public/images/kitty.jpg differ diff --git a/public/images/krkorklo.jpg b/public/images/krkorklo.jpg new file mode 100644 index 0000000..34ecf02 Binary files /dev/null and b/public/images/krkorklo.jpg differ diff --git a/public/images/nell.jpg b/public/images/nell.jpg new file mode 100644 index 0000000..13e8d72 Binary files /dev/null and b/public/images/nell.jpg differ diff --git a/public/images/send.png b/public/images/send.png new file mode 100644 index 0000000..9dfae13 Binary files /dev/null and b/public/images/send.png differ diff --git a/public/images/taeng_ss.jpg b/public/images/taeng_ss.jpg new file mode 100644 index 0000000..2015aa7 Binary files /dev/null and b/public/images/taeng_ss.jpg differ diff --git a/public/index.html b/public/index.html index a0b8d37..6e0027d 100644 --- a/public/index.html +++ b/public/index.html @@ -9,9 +9,11 @@ name="description" content="Web site created using create-react-app" /> - - - React App + + Messenger
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.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..5a378da --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,65 @@ +import styled, { css } from "styled-components"; +import { useRecoilState } from "recoil"; +import { resizeState } from "recoil/recoil"; +import { useEffect } from "react"; +import { Route, Routes } from "react-router-dom"; +import { ChatList, ChatFriends, ChatRoom, Setting } from "pages"; + +const App = () => { + const [isMobile, setIsMobile] = useRecoilState(resizeState); + + const _handleResize = () => { + if (window.innerWidth <= 640) { + setIsMobile(true); + } else { + setIsMobile(false); + } + }; + + useEffect(() => { + if (window.innerWidth <= 640) { + setIsMobile(true); + } + + window.addEventListener("resize", _handleResize); + return () => { + window.removeEventListener("resize", _handleResize); + }; + }, []); + + return ( + + + + } /> + } /> + } /> + } /> + + + + ); +}; + +const Wrapper = styled.div` + height: 100vh; + display: flex; + justify-content: center; + align-items: center; +`; +const Container = styled.div<{ isMobile: boolean }>` + border: 0.05rem solid lightgrey; + ${({ isMobile }) => + isMobile + ? css` + height: 100%; + width: 100%; + ` + : css` + height: 640px; + width: 360px; + border-radius: 0.5rem; + `} +`; + +export default App; diff --git a/src/components/chatFriends/ChatFriendsList.tsx b/src/components/chatFriends/ChatFriendsList.tsx new file mode 100644 index 0000000..4d8a390 --- /dev/null +++ b/src/components/chatFriends/ChatFriendsList.tsx @@ -0,0 +1,55 @@ +import styled from "styled-components"; +import user from "data/user.json"; +import List from "components/layout/List"; +import Search from "components/common/Search"; +import { useState } from "react"; + +const ChatList = () => { + const totalUser = user.slice(1); + const [showUser, setShowUser] = useState(totalUser); + + const filterUser = (text: string): void => { + if (!text.trim()) { + setShowUser(totalUser); + } else { + const filtered = totalUser.filter((user) => + user.name.toLowerCase().includes(text.trim().toLowerCase()) + ); + setShowUser(filtered); + } + }; + + return ( + + + {showUser.map((user) => ( + + ))} + + ); +}; + +const Container = styled.section` + display: flex; + flex-direction: column; + height: 67%; + overflow: auto; + padding-top: 1rem; + ::-webkit-scrollbar { + width: 0.9rem; + } + ::-webkit-scrollbar-thumb { + background-color: rgba(0, 0, 0, 0.2); + border-radius: 1rem; + background-clip: padding-box; + border: 0.3rem solid transparent; + } +`; + +export default ChatList; diff --git a/src/components/chatList/MessageChatList.tsx b/src/components/chatList/MessageChatList.tsx new file mode 100644 index 0000000..713e70f --- /dev/null +++ b/src/components/chatList/MessageChatList.tsx @@ -0,0 +1,66 @@ +import List from "components/layout/List"; +import message from "data/message.json"; +import styled from "styled-components"; +import { useState } from "react"; +import Search from "components/common/Search"; +import { IUserType } from "interface"; + +const MessageChatList = () => { + const [showMessage, setShowMessage] = useState(message); + + const filterMessage = (text: string): void => { + if (!text.trim()) { + setShowMessage(message); + } else { + const filtered = message.filter( + (msg) => + msg.user.filter((user) => + user.name.toLowerCase().includes(text.trim().toLowerCase()) + ).length !== 0 + ); + setShowMessage(filtered); + } + }; + const _handleFriendsName = (friends: IUserType[]): string => { + return friends.length === 1 + ? friends[0].name + : friends.map((user) => user.name).join(", "); + }; + + return ( + + + {showMessage.map( + (msg) => + msg.messages.length !== 0 && ( + + ) + )} + + ); +}; + +const Container = styled.section` + display: flex; + flex-direction: column; + height: 67%; + overflow: auto; + padding-top: 1rem; + ::-webkit-scrollbar { + width: 0.9rem; + } + ::-webkit-scrollbar-thumb { + background-color: rgba(0, 0, 0, 0.2); + border-radius: 1rem; + background-clip: padding-box; + border: 0.3rem solid transparent; + } +`; + +export default MessageChatList; diff --git a/src/components/common/Alert.tsx b/src/components/common/Alert.tsx new file mode 100644 index 0000000..f31b494 --- /dev/null +++ b/src/components/common/Alert.tsx @@ -0,0 +1,76 @@ +import styled from "styled-components"; +import { IAlert } from "interface"; + +const Alert = ({ setVisibleAlert, value }: IAlert) => { + const _handleClick = (): void => { + setVisibleAlert(false); + }; + + return ( + + + + + + X + + + {value} + + + ); +}; + +const Wrapper = styled.section` + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 100; + display: flex; + justify-content: center; + align-items: center; + animation: alert-show 0.2s; + @keyframes alert-show { + from { + opacity: 0; + } + to { + opacity: 1; + } + } +`; +const Background = styled.div` + background: #00000020; + width: 100%; + height: 100%; +`; +const Container = styled.section` + position: absolute; + width: 25rem; + height: 9rem; + background: #ffffff; + border-radius: 0.4rem; +`; +const AlertContent = styled.section` + margin: 1.8rem; + font-size: 1.1rem; +`; +const AlertButtonBox = styled.section` + display: flex; + justify-content: flex-end; + margin: 1.5rem; +`; +const AlertButton = styled.button` + border: none; + background: none; + font-size: 1.2rem; + cursor: pointer; + :hover { + opacity: 0.5; + transition: 0.15s; + } +`; + +export default Alert; diff --git a/src/components/common/Search.tsx b/src/components/common/Search.tsx new file mode 100644 index 0000000..152f0a6 --- /dev/null +++ b/src/components/common/Search.tsx @@ -0,0 +1,50 @@ +import styled from "styled-components"; +import { ISearch } from "interface"; +import { AiOutlineSearch } from "react-icons/ai"; +import { useRecoilValue } from "recoil"; +import { resizeState } from "recoil/recoil"; +import useInput from "hooks/useInput"; +import { useEffect } from "react"; + +const Search = ({ filter }: ISearch) => { + const { text, handleTextChange } = useInput(""); + const isMobile = useRecoilValue(resizeState); + + useEffect(() => { + filter(text); + }, [filter, text]); + + return ( + + + + + + + ); +}; + +const Container = styled.section` + display: flex; +`; +const IconBox = styled.section` + margin: 0.3rem 0.5rem 0 1rem; +`; +const SearchInput = styled.input<{ isMobile: boolean }>` + border: 0.05rem solid lightgrey; + border-radius: 0.5rem; + padding: 0.5rem; + width: ${({ isMobile }) => (isMobile ? "85%" : "17rem")}; + height: 1rem; + font-size: 0.7rem; + :focus { + outline: none; + } +`; + +export default Search; diff --git a/src/components/layout/ChatFooter.tsx b/src/components/layout/ChatFooter.tsx new file mode 100644 index 0000000..f39a614 --- /dev/null +++ b/src/components/layout/ChatFooter.tsx @@ -0,0 +1,39 @@ +import styled from "styled-components"; +import { Link } from "react-router-dom"; +import { AiOutlineUser, AiOutlineSetting } from "react-icons/ai"; +import { BiMessage } from "react-icons/bi"; + +const ChatFooter = ({ path }: { path: string }) => { + return ( + + + + + + + + + + + + ); +}; + +const Container = styled.section` + border-top: 0.05rem solid lightgrey; + display: grid; + grid-template-columns: 1fr 1fr 1fr; +`; +const IconBox = styled(Link)<{ selected: boolean }>` + display: flex; + justify-content: center; + margin: 1.3rem; + color: ${({ selected }) => (selected ? "#1986fc" : "#000000")}; + cursor: pointer; + :hover { + opacity: 0.7; + transition: 0.15s; + } +`; + +export default ChatFooter; diff --git a/src/components/layout/ChatHeader.tsx b/src/components/layout/ChatHeader.tsx new file mode 100644 index 0000000..05dace3 --- /dev/null +++ b/src/components/layout/ChatHeader.tsx @@ -0,0 +1,22 @@ +import styled from "styled-components"; + +const ChatHeader = ({ title }: { title: string }) => { + return ( + + {title} + + ); +}; + +const Container = styled.section` + height: 19%; + border-bottom: 0.05rem solid lightgrey; + display: flex; + align-items: center; +`; +const Title = styled.p` + font-size: 1.5rem; + margin: 1.5rem; +`; + +export default ChatHeader; diff --git a/src/components/layout/List.tsx b/src/components/layout/List.tsx new file mode 100644 index 0000000..b0426ed --- /dev/null +++ b/src/components/layout/List.tsx @@ -0,0 +1,58 @@ +import styled from "styled-components"; +import { Link } from "react-router-dom"; +import { IList } from "interface"; + +const List = ({ link, img, title, subTitle }: IList) => { + return ( + + + + + + {title} + {subTitle} + + + ); +}; + +const ListContent = styled.section` + margin: 1rem; +`; +const ListItem = styled(Link)` + display: flex; + height: 5rem; + text-decoration: none; + cursor: pointer; + :hover { + ${ListContent} { + opacity: 0.7; + transition: 0.15s; + } + } +`; +const ListTitle = styled.p` + margin-top: 0; + margin-bottom: 0.5rem; + color: black; +`; +const ListSubTitle = styled.p` + margin-top: 0; + font-size: 0.7rem; + display: block; + width: 15rem; + color: grey; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +`; +const ProfileImgBox = styled.section` + display: flex; + align-items: center; +`; +const ProfileImg = styled.img` + border-radius: 70%; + margin-left: 1rem; +`; + +export default List; diff --git a/src/components/messageChatRoom/MessageBallon.tsx b/src/components/messageChatRoom/MessageBallon.tsx new file mode 100644 index 0000000..885111e --- /dev/null +++ b/src/components/messageChatRoom/MessageBallon.tsx @@ -0,0 +1,79 @@ +import { IMessageType } from "interface"; +import { useRecoilValue } from "recoil"; +import { chatRoomState } from "recoil/recoil"; +import styled, { css } from "styled-components"; + +const MessageBallon = ({ message }: { message: IMessageType }) => { + const { currentUser } = useRecoilValue(chatRoomState); + const isUser = message.user.id === currentUser.id; + + return ( + + +
+ {message.user.name} + {message.text} +
+ {message.time} +
+ ); +}; + +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; + ${({ isUser }) => + isUser + ? css` + border-radius: 0.5rem 0 0.5rem 0.5rem; + background-color: #1986fc; + color: #ffffff; + ` + : css` + border-radius: 0 0.5rem 0.5rem 0.5rem; + background-color: #f1f1f3; + `}; +`; +const MessageUser = styled.p<{ isUser: boolean }>` + font-size: 0.75rem; + margin: 0; + display: flex; + ${({ isUser }) => + isUser + ? css` + justify-content: flex-end; + margin-right: 0.5rem; + ` + : css` + margin-left: 0.5rem; + `} +`; +const MessageImg = styled.img` + border-radius: 70%; +`; +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; + `} +`; + +export default MessageBallon; diff --git a/src/components/messageChatRoom/MessageChat.tsx b/src/components/messageChatRoom/MessageChat.tsx new file mode 100644 index 0000000..273e3c9 --- /dev/null +++ b/src/components/messageChatRoom/MessageChat.tsx @@ -0,0 +1,57 @@ +import styled from "styled-components"; +import MessageBallon from "./MessageBallon"; +import { useEffect, useRef } from "react"; +import { useRecoilValue } from "recoil"; +import { chatRoomState } from "recoil/recoil"; + +const MessageChat = () => { + const messageWrapperRef = useRef(null); + const { message, currentUser } = useRecoilValue(chatRoomState); + + const _scrollToBottom = (): void => { + if (messageWrapperRef.current) { + messageWrapperRef.current.scrollTop = + messageWrapperRef.current.scrollHeight; + } + }; + + useEffect(() => { + _scrollToBottom(); + }, [message]); + + return ( + + {message.map((msg) => ( + + + + ))} + + ); +}; + +const Wrapper = styled.section` + display: flex; + flex-direction: column; + height: 67%; + overflow: auto; + padding: 1rem 0.8rem 0 0.8rem; + ::-webkit-scrollbar { + width: 0.9rem; + } + ::-webkit-scrollbar-thumb { + background-color: rgba(0, 0, 0, 0.2); + border-radius: 1rem; + background-clip: padding-box; + border: 0.3rem solid transparent; + } +`; +const MessageBallonContainer = styled.section<{ isUser: boolean }>` + display: flex; + justify-content: ${({ isUser }) => isUser && "flex-end"}; +`; + +export default MessageChat; diff --git a/src/components/messageInput/EmoticonPopover.tsx b/src/components/messageInput/EmoticonPopover.tsx new file mode 100644 index 0000000..898a49d --- /dev/null +++ b/src/components/messageInput/EmoticonPopover.tsx @@ -0,0 +1,53 @@ +import styled from "styled-components"; +import React from "react"; +import { IEmoticonPopover } from "interface"; + +const emoticonList = ["โค๏ธ", "๐Ÿ˜„", "๐Ÿ‘", "โœจ", "๐Ÿ–"]; + +const EmoticonPopover = ({ + addEmoticonMessage, + handlePopover, +}: IEmoticonPopover) => { + // ์ด๋ชจํ‹ฐ์ฝ˜ ์ „์†ก + const _handleClick = (e: React.MouseEvent): void => { + const target = e.target as HTMLElement; + addEmoticonMessage(target.innerText); + }; + + return ( + + {emoticonList.map((emo, idx) => ( + + {emo} + + ))} + + ); +}; + +const PopoverContainer = styled.section` + position: absolute; + transform: translate(0, -120%); + background-color: #f1f1f3; + display: flex; + padding: 0.6rem; + border-radius: 0.5rem; + :before { + border-color: #f1f1f3 transparent; + border-style: solid; + border-width: 0.5rem 0.5rem 0 0.5rem; + content: ""; + display: block; + left: 1.15rem; + position: absolute; + bottom: -0.5rem; + } +`; +const EmoticonBox = styled.section` + cursor: pointer; + & + & { + margin-left: 0.7rem; + } +`; + +export default EmoticonPopover; diff --git a/src/components/messageInput/InputEmoticon.tsx b/src/components/messageInput/InputEmoticon.tsx new file mode 100644 index 0000000..fcb8d58 --- /dev/null +++ b/src/components/messageInput/InputEmoticon.tsx @@ -0,0 +1,32 @@ +import { IInputEmoticon } from "interface"; +import EmoticonPopover from "./EmoticonPopover"; +import styled from "styled-components"; +import React, { useState } from "react"; + +const InputEmoticon = ({ addEmoticonMessage }: IInputEmoticon) => { + const [visiblePopover, setVisiblePopover] = useState(false); + + const handlePopover = (): void => { + setVisiblePopover(!visiblePopover); + }; + + return ( +
+ {visiblePopover && ( + + )} + โค๏ธ +
+ ); +}; + +const Emoticon = styled.section` + width: 2rem; + margin-left: 1rem; + cursor: pointer; +`; + +export default React.memo(InputEmoticon); diff --git a/src/components/messageInput/InputMessageForm.tsx b/src/components/messageInput/InputMessageForm.tsx new file mode 100644 index 0000000..fb651ec --- /dev/null +++ b/src/components/messageInput/InputMessageForm.tsx @@ -0,0 +1,86 @@ +import useInput from "hooks/useInput"; +import React, { useState, useCallback } from "react"; +import styled from "styled-components"; +import { useRecoilValue } from "recoil"; +import { resizeState } from "recoil/recoil"; +import InputEmoticon from "./InputEmoticon"; +import { IInputMessageForm } from "interface"; +import Alert from "components/common/Alert"; + +const InputMessageForm = ({ sendMessage }: IInputMessageForm) => { + const { text, handleTextChange, resetText } = useInput(""); + const isMobile = useRecoilValue(resizeState); + const [visibleAlert, setVisibleAlert] = useState(false); + + // ๋ฉ”์‹œ์ง€ ์ „์†ก + const _addInputMessage = (e: React.FormEvent): void => { + e.preventDefault(); + + if (!text.trim()) setVisibleAlert(true); + else { + sendMessage(text); + } + resetText(); + }; + + // ์ด๋ชจํ‹ฐ์ฝ˜ ์ „์†ก + const addEmoticonMessage = useCallback( + (emo: string): void => { + sendMessage(emo); + resetText(); + }, + [sendMessage], + ); + + return ( + + {visibleAlert && ( + + )} + + + + send + + + ); +}; + +const InputForm = styled.form` + border-top: 0.05rem solid lightgrey; + display: flex; + align-items: center; + padding-top: 1rem; +`; +const InputText = styled.input<{ isMobile: boolean }>` + border: 0.05rem solid lightgrey; + border-radius: 0.7rem; + padding: 0.7rem; + width: ${({ isMobile }) => (isMobile ? "78%" : "230px")}; + height: 1rem; + font-size: 0.75rem; + :focus { + outline: none; + } +`; +const InputButton = styled.button` + background: none; + border: none; + margin-left: 0.7rem; + cursor: pointer; + padding-top: 0.5rem; + :hover { + opacity: 0.7; + transition: 0.15s; + } +`; + +export default InputMessageForm; diff --git a/src/components/messageProfile/MessageProfile.tsx b/src/components/messageProfile/MessageProfile.tsx new file mode 100644 index 0000000..f859b97 --- /dev/null +++ b/src/components/messageProfile/MessageProfile.tsx @@ -0,0 +1,54 @@ +import styled from "styled-components"; +import useChatRoom from "hooks/useChatRoom"; +import { useParams } from "react-router-dom"; +import { useRecoilValue } from "recoil"; +import { chatRoomState } from "recoil/recoil"; + +const MessageProfile = () => { + const { id } = useParams(); + const { toggleUser } = useChatRoom(); + const { currentUser } = useRecoilValue(chatRoomState); + + const _handleUser = (): void => { + toggleUser(Number(id)); + }; + + return ( + + + + {currentUser.name} + Typing... + + + ); +}; + +const Profile = styled.section` + display: flex; + align-items: center; + cursor: pointer; +`; +const ProfileImg = styled.img` + border-radius: 70%; +`; +const ProfileContent = styled.section` + display: flex; + flex-direction: column; + margin-left: 0.8rem; +`; +const ProfileName = styled.p` + margin: 0; + font-size: 0.9rem; +`; +const ProfileTyping = styled.p` + font-size: 0.7rem; + color: lightgrey; + margin-top: 0.3rem; +`; + +export default MessageProfile; diff --git a/src/components/messageProfile/MessageProfileContainer.tsx b/src/components/messageProfile/MessageProfileContainer.tsx new file mode 100644 index 0000000..fc3292b --- /dev/null +++ b/src/components/messageProfile/MessageProfileContainer.tsx @@ -0,0 +1,39 @@ +import styled from "styled-components"; +import { Link } from "react-router-dom"; +import MessageProfile from "./MessageProfile"; + +const MessageProfileContainer = () => { + return ( + + + + + + + ); +}; + +const Container = styled.section` + height: 19%; + border-bottom: 0.05rem solid lightgrey; + display: grid; + grid-template-columns: 1fr 8rem 1fr; +`; +const BackButton = styled(Link)` + display: flex; + align-items: center; + margin-left: 2rem; +`; +const BackButtonImg = styled.img` + cursor: pointer; + :hover { + opacity: 0.7; + transition: 0.15s; + } +`; + +export default MessageProfileContainer; diff --git a/src/components/setting/SettingList.tsx b/src/components/setting/SettingList.tsx new file mode 100644 index 0000000..ec0bedd --- /dev/null +++ b/src/components/setting/SettingList.tsx @@ -0,0 +1,43 @@ +import styled from "styled-components"; + +const settings = ["Profile", "Notifications", "General", "Privacy", "Help"]; + +const SettingList = () => { + return ( + + {settings.map((set, idx) => ( + {set} + ))} + + ); +}; + +const Container = styled.section` + display: flex; + flex-direction: column; + height: 67%; + overflow: auto; + padding-top: 1rem; + ::-webkit-scrollbar { + width: 0.9rem; + } + ::-webkit-scrollbar-thumb { + background-color: rgba(0, 0, 0, 0.2); + border-radius: 1rem; + background-clip: padding-box; + border: 0.3rem solid transparent; + } +`; +const ListItem = styled.section` + display: flex; + cursor: pointer; + text-decoration: none; + margin-left: 2.5rem; + padding-top: 2rem; + :hover { + opacity: 0.7; + transition: 0.15s; + } +`; + +export default SettingList; diff --git a/src/data/message.json b/src/data/message.json new file mode 100644 index 0000000..19ede5b --- /dev/null +++ b/src/data/message.json @@ -0,0 +1,441 @@ +[ + { + "id": 1001, + "user": [ + { + "id": 9, + "name": "DAY6", + "statusMessage": "ํ•œ ํŽ˜์ด์ง€๊ฐ€ ๋  ์ˆ˜ ์žˆ๊ฒŒ" + }, + { + "id": 10, + "name": "ghost", + "statusMessage": "์œ ๋ น์˜ ์ง‘" + }, + { + "id": 11, + "name": "kitty", + "statusMessage": "Meow Meow" + } + ], + "messages": [ + { + "id": 1, + "text": "์†”์งํžˆ ๋งํ• ๊ฒŒ ๋งŽ์ด ๊ธฐ๋‹ค๋ ค ์™”์–ด ๋„ˆ๋„ ๊ทธ๋žฌ์„ ๊ฑฐ๋ผ ๋ฏฟ์–ด", + "user": { + "id": 10, + "name": "ghost", + "statusMessage": "์œ ๋ น์˜ ์ง‘" + }, + "time": "15:08" + }, + { + "id": 2, + "text": "์˜ค๋Š˜์ด ์˜ค๊ธธ ๋งค์ผ๊ฐ™์ด ๋‹ฌ๋ ฅ์„ ๋ณด๋ฉด์„œ", + "user": { + "id": 10, + "name": "ghost", + "statusMessage": "์œ ๋ น์˜ ์ง‘" + }, + "time": "15:09" + }, + { + "id": 3, + "text": "์†”์งํžˆ ๋‚˜์—๊ฒŒ๋„ ์ง€๊ธˆ ์ด ์ˆœ๊ฐ„์€ ๊ฟˆ๋งŒ ๊ฐ™์•„ ๋„ˆ์™€ ํ•จ๊ป˜๋ผ", + "user": { + "id": 1, + "name": "krkorklo", + "statusMessage": "๐Ÿ™ƒ" + }, + "time": "17:08" + }, + { + "id": 4, + "text": "์˜ค๋Š˜์„ ์œ„ํ•ด ๊ฝค ๋งŽ์€ ๊ฑธ ์ค€๋น„ํ•ด ๋ดค์–ด", + "user": { + "id": 11, + "name": "kitty", + "statusMessage": "Meow Meow" + }, + "time": "17:08" + }, + { + "id": 5, + "text": "์•„๋ฆ„๋‹ค์šด ์ฒญ์ถ˜์˜ ํ•œ ์žฅ ํ•จ๊ป˜ ์จ๋‚ด๋ ค ๊ฐ€์ž", + "user": { + "id": 9, + "name": "DAY6", + "statusMessage": "ํ•œ ํŽ˜์ด์ง€๊ฐ€ ๋  ์ˆ˜ ์žˆ๊ฒŒ" + }, + "time": "17:09" + }, + { + "id": 6, + "text": "๋„ˆ์™€์˜ ์ถ”์–ต๋“ค๋กœ ๊ฐ€๋“ ์ฑ„์šธ๋ž˜", + "user": { + "id": 9, + "name": "DAY6", + "statusMessage": "ํ•œ ํŽ˜์ด์ง€๊ฐ€ ๋  ์ˆ˜ ์žˆ๊ฒŒ" + }, + "time": "18:10" + } + ] + }, + { + "id": 2, + "user": [ + { + "id": 2, + "name": "ducky", + "statusMessage": "" + } + ], + "messages": [ + { + "id": 1, + "text": "15๊ธฐ", + "user": { + "id": 2, + "name": "ducky", + "statusMessage": "๊ฝฅ" + }, + "time": "11:20" + }, + { + "id": 2, + "text": "ํ”„๋ก ํŠธ", + "user": { + "id": 2, + "name": "ducky", + "statusMessage": "๊ฝฅ" + }, + "time": "11:21" + }, + { + "id": 3, + "text": "์งฑ์ด์ง€โ™ก", + "user": { + "id": 1, + "name": "krkorklo", + "statusMessage": "๐Ÿ™ƒ" + }, + "time": "11:41" + } + ] + }, + { + "id": 3, + "user": [ + { + "id": 3, + "name": "damons year", + "statusMessage": "yours" + } + ], + "messages": [ + { + "id": 1, + "text": "๋‚ด๊ฐ€ ์†์„ ์žก์„๊ฒŒ ๋„ˆ๋Š” ํž˜์„ ๋นผ๋„ ๋ผ", + "user": { + "id": 3, + "name": "damons year", + "statusMessage": "yours" + }, + "time": "12:20" + }, + { + "id": 2, + "text": "๊ทธ์ € ๋ณต์‚ฌ๊ฝƒ ํ•€ ๊ฑฐ๋ฆด ๊ฑท์ž", + "user": { + "id": 1, + "name": "krkorklo", + "statusMessage": "๐Ÿ™ƒ" + }, + "time": "12:30" + }, + { + "id": 3, + "text": "๋„ˆ์˜ ๋งˆ์Œ์ด ๋…น์•„ ์šฐ๋ฆฌ ๋ฐค์„ ํ•ฉ์น˜๋ฉด", + "user": { + "id": 3, + "name": "damons year", + "statusMessage": "yours" + }, + "time": "12:31" + }, + { + "id": 4, + "text": "๋ฌด๋„ˆ์ง„ ๋‹ฌ์„ ์„ธ์›Œ๋†“์ž", + "user": { + "id": 3, + "name": "damons year", + "statusMessage": "yours" + }, + "time": "12:31" + } + ] + }, + { + "id": 4, + "user": [ + { + "id": 4, + "name": "colde", + "statusMessage": "offonoff" + } + ], + "messages": [ + { + "id": 1, + "text": "๋„ˆ๊ฐ€ ๋‚ด๊ฒŒ ํ•˜๋Š” ๋ง์€ ๋ฒ•์ด ๋ผ", + "user": { + "id": 4, + "name": "colde", + "statusMessage": "offonoff" + }, + "time": "05:40" + }, + { + "id": 2, + "text": "๋ชจ๋‘ ์ง€์ผœ๋‚ด์•ผ๋งŒ ๋‚œ ๋„ˆ์˜ ๋งˆ์Œ์— ์‚ด ์ˆ˜๊ฐ€ ์žˆ๊ธฐ์—", + "user": { + "id": 4, + "name": "colde", + "statusMessage": "offonoff" + }, + "time": "05:50" + }, + { + "id": 3, + "text": "๋„ˆ๋กœ ์ธํ•ด ํ”ผ์–ด๋‚œ ๊ฝƒ์ด ๋‚ด ๋งˆ์Œ์— ์‹œ๋“ค ์ˆ˜๊ฐ€ ์—†์ง€", + "user": { + "id": 1, + "name": "krkorklo", + "statusMessage": "๐Ÿ™ƒ" + }, + "time": "08:12" + } + ] + }, + { + "id": 5, + "user": [ + { + "id": 5, + "name": "keshi", + "statusMessage": "right here" + } + ], + "messages": [ + { + "id": 1, + "text": "If you need me, I'll be here", + "user": { + "id": 1, + "name": "krkorklo", + "statusMessage": "๐Ÿ™ƒ" + }, + "time": "22:03" + }, + { + "id": 2, + "text": "Said I'm here", + "user": { + "id": 1, + "name": "krkorklo", + "statusMessage": "๐Ÿ™ƒ" + }, + "time": "22:11" + }, + { + "id": 3, + "text": "you do not know who to be", + "user": { + "id": 5, + "name": "keshi", + "statusMessage": "right here" + }, + "time": "23:20" + }, + { + "id": 4, + "text": "Then go on and come home to me", + "user": { + "id": 5, + "name": "keshi", + "statusMessage": "right here" + }, + "time": "23:20" + } + ] + }, + { + "id": 6, + "user": [ + { + "id": 6, + "name": "joan", + "statusMessage": "good vibe๐Ÿ™Œ" + } + ], + "messages": [ + { + "id": 1, + "text": "You took my breath away", + "user": { + "id": 6, + "name": "joan", + "statusMessage": "good vibe๐Ÿ™Œ" + }, + "time": "15:45" + }, + { + "id": 2, + "text": "I just can't believe my eyes", + "user": { + "id": 1, + "name": "krkorklo", + "statusMessage": "๐Ÿ™ƒ" + }, + "time": "15:46" + }, + { + "id": 3, + "text": "Now you're here, life has never felt so good", + "user": { + "id": 1, + "name": "krkorklo", + "statusMessage": "๐Ÿ™ƒ" + }, + "time": "15:46" + } + ] + }, + { + "id": 7, + "user": [ + { + "id": 7, + "name": "nell", + "statusMessage": "" + } + ], + "messages": [ + { + "id": 1, + "text": "๊ฐ€๋”์€ ์•„์ง๋„ ๊ทธ๋Ÿด ์ˆ˜ ์žˆ์„ ๊ฑฐ๋ผ๊ณ  ์ƒ๊ฐ์ด ๋“ค ๋•Œ๊ฐ€ ์žˆ์–ด", + "user": { + "id": 1, + "name": "krkorklo", + "statusMessage": "๐Ÿ™ƒ" + }, + "time": "13:36" + }, + { + "id": 2, + "text": "ํ•˜์ง€๋งŒ ์ด๋‚ด ๋‹ค์‹œ ๋‘๋ ค์›€์ด ์•ž์„œ๊ณ  ๋งˆ์Œ์ด ๋ฌด๊ฑฐ์›Œ์ ธ", + "user": { + "id": 7, + "name": "nell", + "statusMessage": "" + }, + "time": "14:46" + }, + { + "id": 3, + "text": "์–ด๋Š์ƒˆ ๋˜ ์ž…๊ฐ€์—” ํ•œ์ˆจ์ด ๋งบํ˜€", + "user": { + "id": 7, + "name": "nell", + "statusMessage": "" + }, + "time": "14:47" + }, + { + "id": 4, + "text": "์‹œ๊ฐ„์€ ๋‚  ์–ด๋ฅธ์ด ๋˜๊ฒŒ ํ–ˆ์ง€๋งŒ ๊ฐ•ํ•ด์ง€๊ฒŒ ํ•˜์ง€๋Š” ์•Š์€ ๊ฒƒ ๊ฐ™์•„", + "user": { + "id": 1, + "name": "krkorklo", + "statusMessage": "๐Ÿ™ƒ" + }, + "time": "15:00" + } + ] + }, + { + "id": 8, + "user": [ + { + "id": 8, + "name": "taeng_ss", + "statusMessage": "์ •๋ง ๋„ˆ๋ฅผ ์‚ฌ๋ž‘ํ–ˆ์„๊นŒ" + } + ], + "messages": [ + { + "id": 1, + "text": "์„œ๋กœ๋ฅผ ๊ทธ๋ฆฌ์›Œํ–ˆ๊ณ  ์„œ๋กœ๋ฅผ ์ง€๊ฒจ์›Œํ•˜์ง€", + "user": { + "id": 8, + "name": "taeng_ss", + "statusMessage": "์ •๋ง ๋„ˆ๋ฅผ ์‚ฌ๋ž‘ํ–ˆ์„๊นŒ" + }, + "time": "15:08" + }, + { + "id": 2, + "text": "๊ทธ ๊ธด ๋‚ฎ๊ณผ ๋ฐค๋“ค์ด ๋‚ก์•„ ๋…น์Šฌ๊ธฐ ์ „์—", + "user": { + "id": 1, + "name": "krkorklo", + "statusMessage": "๐Ÿ™ƒ" + }, + "time": "15:09" + }, + { + "id": 3, + "text": "์šฐ๋ฆฌ ๋‹ค์‹œ ๋ฐ˜์ง์ด์ž", + "user": { + "id": 1, + "name": "krkorklo", + "statusMessage": "๐Ÿ™ƒ" + }, + "time": "17:08" + } + ] + }, + { + "id": 9, + "user": [ + { + "id": 9, + "name": "ghost", + "statusMessage": "" + } + ], + "messages": [] + }, + { + "id": 10, + "user": [ + { + "id": 10, + "name": "kitty", + "statusMessage": "" + } + ], + "messages": [] + }, + { + "id": 11, + "user": [ + { + "id": 11, + "name": "DAY6", + "statusMessage": "" + } + ], + "messages": [] + } +] diff --git a/src/data/user.json b/src/data/user.json new file mode 100644 index 0000000..04aa508 --- /dev/null +++ b/src/data/user.json @@ -0,0 +1,57 @@ +[ + { + "id": 1, + "name": "krkorklo", + "statusMessage": "๐Ÿ™ƒ" + }, + { + "id": 2, + "name": "ducky", + "statusMessage": "๊ฝฅ" + }, + { + "id": 3, + "name": "damons year", + "statusMessage": "yours" + }, + { + "id": 4, + "name": "colde", + "statusMessage": "offonoff" + }, + { + "id": 5, + "name": "keshi", + "statusMessage": "right here" + }, + { + "id": 6, + "name": "joan", + "statusMessage": "good vibe๐Ÿ™Œ" + }, + { + "id": 7, + "name": "nell", + "statusMessage": "" + }, + { + "id": 8, + "name": "taeng_ss", + "statusMessage": "์ •๋ง ๋„ˆ๋ฅผ ์‚ฌ๋ž‘ํ–ˆ์„๊นŒ" + }, + { + "id": 9, + "name": "DAY6", + "statusMessage": "ํ•œ ํŽ˜์ด์ง€๊ฐ€ ๋  ์ˆ˜ ์žˆ๊ฒŒ" + }, + { + "id": 10, + "name": "ghost", + "statusMessage": "์œ ๋ น์˜ ์ง‘" + }, + { + "id": 11, + "name": "kitty", + "statusMessage": "Meow Meow" + } +] diff --git a/src/hooks/useChatRoom.tsx b/src/hooks/useChatRoom.tsx new file mode 100644 index 0000000..be61f8c --- /dev/null +++ b/src/hooks/useChatRoom.tsx @@ -0,0 +1,59 @@ +import { chatRoomState, messageState, userState } from "recoil/recoil"; +import { useRecoilState, useRecoilValue } from "recoil"; + +const useChatRoom = () => { + const messageList = useRecoilValue(messageState); + const userStore = useRecoilValue(userState); + const [chatState, setChatState] = useRecoilState(chatRoomState); + + // ๋ฉ”์‹œ์ง€ ์ถ”๊ฐ€ + const addMessage = (text: string): void => { + const curTime = + String(new Date().getHours()).padStart(2, "0") + + ":" + + String(new Date().getMinutes()).padStart(2, "0"); + const messageObj = { + id: new Date().valueOf(), + user: chatState.currentUser, + time: curTime, + text: text, + }; + setChatState({ ...chatState, message: [...chatState.message, messageObj] }); + }; + + // user ํ† ๊ธ€ ํ•จ์ˆ˜ + const toggleUser = (id: number): void => { + let toggleIdx = 0; + if (id <= 1000 && chatState.currentUser.id === userStore.mainUser.id) { + toggleIdx = userStore.users.findIndex((user) => user.id === id); + } else if (id > 1000) { + // ๋‹จ์ฒด chat์˜ ๊ฒฝ์šฐ + let users = + messageList[messageList.findIndex((msg) => msg.id === id)].user; + toggleIdx = users.findIndex( + (user) => user.id === chatState.currentUser.id + ); + if (toggleIdx === users.length - 1) { + toggleIdx = 0; + } else { + toggleIdx = userStore.users.findIndex( + (user) => user.id === users[toggleIdx + 1].id + ); + } + } + setChatState({ ...chatState, currentUser: userStore.users[toggleIdx] }); + }; + + // ํ˜„์žฌ ๋ฉ”์‹œ์ง€ ์„ธํŒ… + const handleChatRoom = (id: number): void => { + const toggleIndex = messageList.findIndex((msg) => msg.id === id); + setChatState({ + currentUser: userStore.mainUser, + message: messageList[toggleIndex].messages, + }); + }; + + return { addMessage, handleChatRoom, toggleUser }; +}; + +export default useChatRoom; diff --git a/src/hooks/useInput.tsx b/src/hooks/useInput.tsx new file mode 100644 index 0000000..7d33657 --- /dev/null +++ b/src/hooks/useInput.tsx @@ -0,0 +1,19 @@ +import { useState } from "react"; + +const useInput = (initialValue: string) => { + const [text, setText] = useState(initialValue); + + // input text ๋ณ€๊ฒฝ + const handleTextChange = (e: React.ChangeEvent): void => { + setText(e.target.value); + }; + + // ์ž…๋ ฅ๊ฐ’ ์ดˆ๊ธฐํ™” + const resetText = (): void => { + setText(""); + }; + + return { text, handleTextChange, resetText }; +}; + +export default useInput; 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..e7720b8 --- /dev/null +++ b/src/index.tsx @@ -0,0 +1,18 @@ +import { RecoilRoot } from "recoil"; +import ReactDOM from "react-dom/client"; +import GlobalStyle from "style/GlobalStyle"; +import App from "./App"; +import { BrowserRouter } from "react-router-dom"; + +const root = ReactDOM.createRoot( + document.getElementById("root") as HTMLInputElement, +); + +root.render( + + + + + + , +); diff --git a/src/interface/alert.tsx b/src/interface/alert.tsx new file mode 100644 index 0000000..cc8f014 --- /dev/null +++ b/src/interface/alert.tsx @@ -0,0 +1,4 @@ +export interface IAlert { + setVisibleAlert: React.Dispatch; + value: string; +} diff --git a/src/interface/chat.tsx b/src/interface/chat.tsx new file mode 100644 index 0000000..0343de3 --- /dev/null +++ b/src/interface/chat.tsx @@ -0,0 +1,24 @@ +import { IUserType } from "./user"; +import { IMessageType } from "./message"; + +export interface IChatListItem { + id: number; + user: IUserType[]; + messages: IMessageType[]; +} + +export interface IChatRoomState { + message: IMessageType[]; + currentUser: IUserType; +} + +export interface IList { + link: number; + img: string; + title: string; + subTitle: string; +} + +export interface ISearch { + filter: (text: string) => void; +} diff --git a/src/interface/index.tsx b/src/interface/index.tsx new file mode 100644 index 0000000..06dfa5a --- /dev/null +++ b/src/interface/index.tsx @@ -0,0 +1,23 @@ +import { IUserType, IUserState } from "./user"; +import { + IInputMessageForm, + IMessageType, + IInputEmoticon, + IEmoticonPopover, +} from "./message"; +import { IAlert } from "./alert"; +import { IChatListItem, IList, IChatRoomState, ISearch } from "./chat"; + +export type { + IUserType, + IUserState, + IInputEmoticon, + IEmoticonPopover, + IAlert, + IInputMessageForm, + IMessageType, + IChatListItem, + IList, + IChatRoomState, + ISearch, +}; diff --git a/src/interface/message.tsx b/src/interface/message.tsx new file mode 100644 index 0000000..9e53a34 --- /dev/null +++ b/src/interface/message.tsx @@ -0,0 +1,20 @@ +import { IUserType } from "./user"; + +export interface IMessageType { + id: number; + text: string; + user: IUserType; + time: string; +} + +export interface IInputMessageForm { + sendMessage: (text: string) => void; +} + +export interface IInputEmoticon { + addEmoticonMessage: (emo: string) => void; +} + +export interface IEmoticonPopover extends IInputEmoticon { + handlePopover: () => void; +} diff --git a/src/interface/user.tsx b/src/interface/user.tsx new file mode 100644 index 0000000..fc1802e --- /dev/null +++ b/src/interface/user.tsx @@ -0,0 +1,10 @@ +export interface IUserType { + id: number; + name: string; + statusMessage: string; +} + +export interface IUserState { + users: IUserType[]; + mainUser: IUserType; +} diff --git a/src/logo.svg b/src/logo.svg deleted file mode 100644 index 9dfc1c0..0000000 --- a/src/logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/pages/ChatFriends.tsx b/src/pages/ChatFriends.tsx new file mode 100644 index 0000000..74a99bb --- /dev/null +++ b/src/pages/ChatFriends.tsx @@ -0,0 +1,15 @@ +import ChatFooter from "components/layout/ChatFooter"; +import ChatHeader from "components/layout/ChatHeader"; +import ChatFriendsList from "components/chatFriends/ChatFriendsList"; + +const ChatFriends = () => { + return ( + <> + + + + + ); +}; + +export default ChatFriends; diff --git a/src/pages/ChatList.tsx b/src/pages/ChatList.tsx new file mode 100644 index 0000000..d78ee84 --- /dev/null +++ b/src/pages/ChatList.tsx @@ -0,0 +1,15 @@ +import ChatFooter from "components/layout/ChatFooter"; +import ChatHeader from "components/layout/ChatHeader"; +import MessageChatList from "components/chatList/MessageChatList"; + +const ChatList = () => { + return ( + <> + + + + + ); +}; + +export default ChatList; diff --git a/src/pages/ChatRoom.tsx b/src/pages/ChatRoom.tsx new file mode 100644 index 0000000..24ee63f --- /dev/null +++ b/src/pages/ChatRoom.tsx @@ -0,0 +1,25 @@ +import useChatRoom from "hooks/useChatRoom"; +import MessageChat from "components/messageChatRoom/MessageChat"; +import InputMessageForm from "components/messageInput/InputMessageForm"; +import MessageProfileContainer from "components/messageProfile/MessageProfileContainer"; +import { useEffect } from "react"; +import { useParams } from "react-router-dom"; + +const ChatRoom = () => { + const { id } = useParams(); + const { addMessage, handleChatRoom } = useChatRoom(); + + useEffect(() => { + handleChatRoom(Number(id)); + }, []); + + return ( + <> + + + + + ); +}; + +export default ChatRoom; diff --git a/src/pages/Setting.tsx b/src/pages/Setting.tsx new file mode 100644 index 0000000..22e243c --- /dev/null +++ b/src/pages/Setting.tsx @@ -0,0 +1,15 @@ +import ChatFooter from "components/layout/ChatFooter"; +import ChatHeader from "components/layout/ChatHeader"; +import SettingList from "components/setting/SettingList"; + +const Setting = () => { + return ( + <> + + + + + ); +}; + +export default Setting; diff --git a/src/pages/index.tsx b/src/pages/index.tsx new file mode 100644 index 0000000..86b4bf6 --- /dev/null +++ b/src/pages/index.tsx @@ -0,0 +1,6 @@ +import ChatList from "./ChatList"; +import ChatRoom from "./ChatRoom"; +import ChatFriends from "./ChatFriends"; +import Setting from "./Setting"; + +export { ChatList, ChatRoom, ChatFriends, Setting }; diff --git a/src/recoil/recoil.tsx b/src/recoil/recoil.tsx new file mode 100644 index 0000000..fa43502 --- /dev/null +++ b/src/recoil/recoil.tsx @@ -0,0 +1,30 @@ +import messages from "data/message.json"; +import users from "data/user.json"; +import { IChatListItem, IChatRoomState, IUserState } from "interface"; +import { atom } from "recoil"; + +export const messageState = atom({ + key: "messages", + default: messages, +}); + +export const userState = atom({ + key: "user", + default: { + users: users, + mainUser: users[0], + }, +}); + +export const chatRoomState = atom({ + key: "chatRoom", + default: { + message: [], + currentUser: { id: 0, name: "", statusMessage: "" }, + }, +}); + +export const resizeState = atom({ + key: "resize", + default: false, +}); diff --git a/src/style/GlobalStyle.tsx b/src/style/GlobalStyle.tsx new file mode 100644 index 0000000..3010938 --- /dev/null +++ b/src/style/GlobalStyle.tsx @@ -0,0 +1,10 @@ +import { createGlobalStyle } from "styled-components"; + +const GlobalStyle = createGlobalStyle` + body { + margin: 0; + font-family: "Noto Sans KR", sans-serif; +} +`; + +export default GlobalStyle; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..5493aa7 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "baseUrl": "./src", + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "types": ["react/next", "react-dom/next"] + }, + "include": ["src"] +}