diff --git a/package-lock.json b/package-lock.json
index 43e807c..da90db4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,13 +8,32 @@
"name": "react-messenger-15th",
"version": "0.1.0",
"dependencies": {
+ "@reduxjs/toolkit": "^1.8.1",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.1.1",
"@testing-library/user-event": "^13.5.0",
+ "@types/jest": "^27.5.0",
+ "@types/node": "^17.0.31",
+ "@types/react": "^18.0.8",
+ "@types/react-dom": "^18.0.3",
+ "@types/react-redux": "^7.1.24",
+ "@types/redux": "^3.6.0",
+ "@types/redux-thunk": "^2.1.0",
"react": "^18.1.0",
"react-dom": "^18.1.0",
+ "react-icons": "^4.3.1",
+ "react-redux": "^8.0.1",
+ "react-router-dom": "^6.3.0",
"react-scripts": "5.0.1",
+ "redux-thunk": "^2.4.1",
+ "styled-components": "^5.3.5",
+ "typescript": "^4.6.4",
+ "uuid": "^8.3.2",
"web-vitals": "^2.1.4"
+ },
+ "devDependencies": {
+ "@types/styled-components": "^5.1.25",
+ "@types/uuid": "^8.3.4"
}
},
"node_modules/@ampproject/remapping": {
@@ -1972,6 +1991,29 @@
"postcss": "^8.3"
}
},
+ "node_modules/@emotion/is-prop-valid": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.1.2.tgz",
+ "integrity": "sha512-3QnhqeL+WW88YjYbQL5gUIkthuMw7a0NGbZ7wfFVk2kg/CK5w8w5FFa0RzWjyY1+sujN0NWbtSHH6OJmWHtJpQ==",
+ "dependencies": {
+ "@emotion/memoize": "^0.7.4"
+ }
+ },
+ "node_modules/@emotion/memoize": {
+ "version": "0.7.5",
+ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.5.tgz",
+ "integrity": "sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ=="
+ },
+ "node_modules/@emotion/stylis": {
+ "version": "0.8.5",
+ "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz",
+ "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ=="
+ },
+ "node_modules/@emotion/unitless": {
+ "version": "0.7.5",
+ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
+ "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg=="
+ },
"node_modules/@eslint/eslintrc": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.2.tgz",
@@ -2841,6 +2883,29 @@
}
}
},
+ "node_modules/@reduxjs/toolkit": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.8.1.tgz",
+ "integrity": "sha512-Q6mzbTpO9nOYRnkwpDlFOAbQnd3g7zj7CtHAZWz5SzE5lcV97Tf8f3SzOO8BoPOMYBFgfZaqTUZqgGu+a0+Fng==",
+ "dependencies": {
+ "immer": "^9.0.7",
+ "redux": "^4.1.2",
+ "redux-thunk": "^2.4.1",
+ "reselect": "^4.1.5"
+ },
+ "peerDependencies": {
+ "react": "^16.9.0 || ^17.0.0 || ^18",
+ "react-redux": "^7.2.1 || ^8.0.0-beta"
+ },
+ "peerDependenciesMeta": {
+ "react": {
+ "optional": true
+ },
+ "react-redux": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@rollup/plugin-babel": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
@@ -3515,6 +3580,15 @@
"@types/node": "*"
}
},
+ "node_modules/@types/hoist-non-react-statics": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
+ "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
+ "dependencies": {
+ "@types/react": "*",
+ "hoist-non-react-statics": "^3.3.0"
+ }
+ },
"node_modules/@types/html-minifier-terser": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz",
@@ -3550,9 +3624,9 @@
}
},
"node_modules/@types/jest": {
- "version": "27.4.1",
- "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.4.1.tgz",
- "integrity": "sha512-23iPJADSmicDVrWk+HT58LMJtzLAnB2AgIzplQuq/bSrGaxCrlvRFjGbXmamnnk/mAmCdLStiGqggu28ocUyiw==",
+ "version": "27.5.0",
+ "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.5.0.tgz",
+ "integrity": "sha512-9RBFx7r4k+msyj/arpfaa0WOOEcaAZNmN+j80KFbFCoSqCJGHTz7YMAMGQW9Xmqm5w6l5c25vbSjMwlikJi5+g==",
"dependencies": {
"jest-matcher-utils": "^27.0.0",
"pretty-format": "^27.0.0"
@@ -3574,9 +3648,9 @@
"integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw=="
},
"node_modules/@types/node": {
- "version": "17.0.30",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.30.tgz",
- "integrity": "sha512-oNBIZjIqyHYP8VCNAV9uEytXVeXG2oR0w9lgAXro20eugRQfY002qr3CUl6BAe+Yf/z3CRjPdz27Pu6WWtuSRw=="
+ "version": "17.0.31",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.31.tgz",
+ "integrity": "sha512-AR0x5HbXGqkEx9CadRH3EBYx/VkiUgZIhP4wvPn/+5KIsgpNoyFaRlVe0Zlx9gRtg8fA06a9tskE2MSN7TcG4Q=="
},
"node_modules/@types/parse-json": {
"version": "4.0.0",
@@ -3626,6 +3700,35 @@
"@types/react": "*"
}
},
+ "node_modules/@types/react-redux": {
+ "version": "7.1.24",
+ "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.24.tgz",
+ "integrity": "sha512-7FkurKcS1k0FHZEtdbbgN8Oc6b+stGSfZYjQGicofJ0j4U0qIn/jaSvnP2pLwZKiai3/17xqqxkkrxTgN8UNbQ==",
+ "dependencies": {
+ "@types/hoist-non-react-statics": "^3.3.0",
+ "@types/react": "*",
+ "hoist-non-react-statics": "^3.3.0",
+ "redux": "^4.0.0"
+ }
+ },
+ "node_modules/@types/redux": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/@types/redux/-/redux-3.6.0.tgz",
+ "integrity": "sha1-8evh5UEVGAcuT9/KXHbhbnTBOZo=",
+ "deprecated": "This is a stub types definition for Redux (https://github.com/reactjs/redux). Redux provides its own type definitions, so you don't need @types/redux installed!",
+ "dependencies": {
+ "redux": "*"
+ }
+ },
+ "node_modules/@types/redux-thunk": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@types/redux-thunk/-/redux-thunk-2.1.0.tgz",
+ "integrity": "sha1-vCtulylhgxr7gqm/TwZybjUflBY=",
+ "deprecated": "This is a stub types definition for Redux Thunk (https://github.com/gaearon/redux-thunk). Redux Thunk provides its own type definitions, so you don't need @types/redux-thunk installed!",
+ "dependencies": {
+ "redux-thunk": "*"
+ }
+ },
"node_modules/@types/resolve": {
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
@@ -3674,6 +3777,17 @@
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz",
"integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw=="
},
+ "node_modules/@types/styled-components": {
+ "version": "5.1.25",
+ "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.25.tgz",
+ "integrity": "sha512-fgwl+0Pa8pdkwXRoVPP9JbqF0Ivo9llnmsm+7TCI330kbPIFd9qv1Lrhr37shf4tnxCOSu+/IgqM7uJXLWZZNQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/hoist-non-react-statics": "*",
+ "@types/react": "*",
+ "csstype": "^3.0.2"
+ }
+ },
"node_modules/@types/testing-library__jest-dom": {
"version": "5.14.3",
"resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.3.tgz",
@@ -3687,6 +3801,17 @@
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz",
"integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg=="
},
+ "node_modules/@types/use-sync-external-store": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
+ "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA=="
+ },
+ "node_modules/@types/uuid": {
+ "version": "8.3.4",
+ "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
+ "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==",
+ "dev": true
+ },
"node_modules/@types/ws": {
"version": "8.5.3",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
@@ -4702,6 +4827,26 @@
"@babel/core": "^7.0.0-0"
}
},
+ "node_modules/babel-plugin-styled-components": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.7.tgz",
+ "integrity": "sha512-i7YhvPgVqRKfoQ66toiZ06jPNA3p6ierpfUuEWxNF+fV27Uv5gxBkf8KZLHUCc1nFA9j6+80pYoIpqCeyW3/bA==",
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.16.0",
+ "@babel/helper-module-imports": "^7.16.0",
+ "babel-plugin-syntax-jsx": "^6.18.0",
+ "lodash": "^4.17.11",
+ "picomatch": "^2.3.0"
+ },
+ "peerDependencies": {
+ "styled-components": ">= 2"
+ }
+ },
+ "node_modules/babel-plugin-syntax-jsx": {
+ "version": "6.18.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz",
+ "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY="
+ },
"node_modules/babel-plugin-transform-react-remove-prop-types": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz",
@@ -5016,6 +5161,11 @@
"node": ">= 6"
}
},
+ "node_modules/camelize": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz",
+ "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs="
+ },
"node_modules/caniuse-api": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz",
@@ -5482,6 +5632,14 @@
"postcss": "^8.4"
}
},
+ "node_modules/css-color-keywords": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
+ "integrity": "sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU=",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/css-declaration-sorter": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.2.2.tgz",
@@ -5663,6 +5821,16 @@
"resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz",
"integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w=="
},
+ "node_modules/css-to-react-native": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.0.0.tgz",
+ "integrity": "sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ==",
+ "dependencies": {
+ "camelize": "^1.0.0",
+ "css-color-keywords": "^1.0.0",
+ "postcss-value-parser": "^4.0.2"
+ }
+ },
"node_modules/css-tree": {
"version": "1.0.0-alpha.37",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz",
@@ -7990,6 +8158,27 @@
"he": "bin/he"
}
},
+ "node_modules/history": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz",
+ "integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==",
+ "dependencies": {
+ "@babel/runtime": "^7.7.6"
+ }
+ },
+ "node_modules/hoist-non-react-statics": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
+ "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
+ "dependencies": {
+ "react-is": "^16.7.0"
+ }
+ },
+ "node_modules/hoist-non-react-statics/node_modules/react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+ },
"node_modules/hoopy": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz",
@@ -13406,11 +13595,62 @@
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz",
"integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg=="
},
+ "node_modules/react-icons": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.3.1.tgz",
+ "integrity": "sha512-cB10MXLTs3gVuXimblAdI71jrJx8njrJZmNMEMC+sQu5B/BIOmlsAjskdqpn81y8UBVEGuHODd7/ci5DvoSzTQ==",
+ "peerDependencies": {
+ "react": "*"
+ }
+ },
"node_modules/react-is": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
},
+ "node_modules/react-redux": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.1.tgz",
+ "integrity": "sha512-LMZMsPY4DYdZfLJgd7i79n5Kps5N9XVLCJJeWAaPYTV+Eah2zTuBjTxKtNEbjiyitbq80/eIkm55CYSLqAub3w==",
+ "dependencies": {
+ "@babel/runtime": "^7.12.1",
+ "@types/hoist-non-react-statics": "^3.3.1",
+ "@types/use-sync-external-store": "^0.0.3",
+ "hoist-non-react-statics": "^3.3.2",
+ "react-is": "^18.0.0",
+ "use-sync-external-store": "^1.0.0"
+ },
+ "peerDependencies": {
+ "@types/react": "^16.8 || ^17.0 || ^18.0",
+ "@types/react-dom": "^16.8 || ^17.0 || ^18.0",
+ "react": "^16.8 || ^17.0 || ^18.0",
+ "react-dom": "^16.8 || ^17.0 || ^18.0",
+ "react-native": ">=0.59",
+ "redux": "^4"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ },
+ "react-native": {
+ "optional": true
+ },
+ "redux": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-redux/node_modules/react-is": {
+ "version": "18.1.0",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz",
+ "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg=="
+ },
"node_modules/react-refresh": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
@@ -13419,6 +13659,30 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-router": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.3.0.tgz",
+ "integrity": "sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ==",
+ "dependencies": {
+ "history": "^5.2.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8"
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.3.0.tgz",
+ "integrity": "sha512-uaJj7LKytRxZNQV8+RbzJWnJ8K2nPsOOEuX7aQstlMZKQT0164C+X2w6bnkqU3sjtLvpd5ojrezAyfZ1+0sStw==",
+ "dependencies": {
+ "history": "^5.2.0",
+ "react-router": "6.3.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8",
+ "react-dom": ">=16.8"
+ }
+ },
"node_modules/react-scripts": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz",
@@ -13549,6 +13813,22 @@
"node": ">=8"
}
},
+ "node_modules/redux": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.0.tgz",
+ "integrity": "sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==",
+ "dependencies": {
+ "@babel/runtime": "^7.9.2"
+ }
+ },
+ "node_modules/redux-thunk": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.1.tgz",
+ "integrity": "sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==",
+ "peerDependencies": {
+ "redux": "^4"
+ }
+ },
"node_modules/regenerate": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
@@ -13691,6 +13971,11 @@
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
},
+ "node_modules/reselect": {
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.5.tgz",
+ "integrity": "sha512-uVdlz8J7OO+ASpBYoz1Zypgx0KasCY20H+N8JD13oUMtPvSHQuscrHop4KbXrbsBcdB9Ds7lVK7eRkBIfO43vQ=="
+ },
"node_modules/resolve": {
"version": "1.22.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz",
@@ -14172,6 +14457,11 @@
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
},
+ "node_modules/shallowequal": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
+ "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
+ },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -14569,6 +14859,36 @@
"webpack": "^5.0.0"
}
},
+ "node_modules/styled-components": {
+ "version": "5.3.5",
+ "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.5.tgz",
+ "integrity": "sha512-ndETJ9RKaaL6q41B69WudeqLzOpY1A/ET/glXkNZ2T7dPjPqpPCXXQjDFYZWwNnE5co0wX+gTCqx9mfxTmSIPg==",
+ "hasInstallScript": true,
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.0.0",
+ "@babel/traverse": "^7.4.5",
+ "@emotion/is-prop-valid": "^1.1.0",
+ "@emotion/stylis": "^0.8.4",
+ "@emotion/unitless": "^0.7.4",
+ "babel-plugin-styled-components": ">= 1.12.0",
+ "css-to-react-native": "^3.0.0",
+ "hoist-non-react-statics": "^3.0.0",
+ "shallowequal": "^1.1.0",
+ "supports-color": "^5.5.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/styled-components"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8.0",
+ "react-dom": ">= 16.8.0",
+ "react-is": ">= 16.8.0"
+ }
+ },
"node_modules/stylehacks": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.0.tgz",
@@ -15131,7 +15451,6 @@
"version": "4.6.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.4.tgz",
"integrity": "sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==",
- "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -15239,6 +15558,14 @@
"punycode": "^2.1.0"
}
},
+ "node_modules/use-sync-external-store": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.1.0.tgz",
+ "integrity": "sha512-SEnieB2FPKEVne66NpXPd1Np4R1lTNKfjuy3XdIoPQKYBAFdzbzSZlSn1KJZUiihQLQC5Znot4SBz1EOTBwQAQ==",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -17516,6 +17843,29 @@
"postcss-value-parser": "^4.2.0"
}
},
+ "@emotion/is-prop-valid": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.1.2.tgz",
+ "integrity": "sha512-3QnhqeL+WW88YjYbQL5gUIkthuMw7a0NGbZ7wfFVk2kg/CK5w8w5FFa0RzWjyY1+sujN0NWbtSHH6OJmWHtJpQ==",
+ "requires": {
+ "@emotion/memoize": "^0.7.4"
+ }
+ },
+ "@emotion/memoize": {
+ "version": "0.7.5",
+ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.5.tgz",
+ "integrity": "sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ=="
+ },
+ "@emotion/stylis": {
+ "version": "0.8.5",
+ "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz",
+ "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ=="
+ },
+ "@emotion/unitless": {
+ "version": "0.7.5",
+ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
+ "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg=="
+ },
"@eslint/eslintrc": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.2.tgz",
@@ -18139,6 +18489,17 @@
"source-map": "^0.7.3"
}
},
+ "@reduxjs/toolkit": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.8.1.tgz",
+ "integrity": "sha512-Q6mzbTpO9nOYRnkwpDlFOAbQnd3g7zj7CtHAZWz5SzE5lcV97Tf8f3SzOO8BoPOMYBFgfZaqTUZqgGu+a0+Fng==",
+ "requires": {
+ "immer": "^9.0.7",
+ "redux": "^4.1.2",
+ "redux-thunk": "^2.4.1",
+ "reselect": "^4.1.5"
+ }
+ },
"@rollup/plugin-babel": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
@@ -18620,6 +18981,15 @@
"@types/node": "*"
}
},
+ "@types/hoist-non-react-statics": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
+ "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
+ "requires": {
+ "@types/react": "*",
+ "hoist-non-react-statics": "^3.3.0"
+ }
+ },
"@types/html-minifier-terser": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz",
@@ -18655,9 +19025,9 @@
}
},
"@types/jest": {
- "version": "27.4.1",
- "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.4.1.tgz",
- "integrity": "sha512-23iPJADSmicDVrWk+HT58LMJtzLAnB2AgIzplQuq/bSrGaxCrlvRFjGbXmamnnk/mAmCdLStiGqggu28ocUyiw==",
+ "version": "27.5.0",
+ "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.5.0.tgz",
+ "integrity": "sha512-9RBFx7r4k+msyj/arpfaa0WOOEcaAZNmN+j80KFbFCoSqCJGHTz7YMAMGQW9Xmqm5w6l5c25vbSjMwlikJi5+g==",
"requires": {
"jest-matcher-utils": "^27.0.0",
"pretty-format": "^27.0.0"
@@ -18679,9 +19049,9 @@
"integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw=="
},
"@types/node": {
- "version": "17.0.30",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.30.tgz",
- "integrity": "sha512-oNBIZjIqyHYP8VCNAV9uEytXVeXG2oR0w9lgAXro20eugRQfY002qr3CUl6BAe+Yf/z3CRjPdz27Pu6WWtuSRw=="
+ "version": "17.0.31",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.31.tgz",
+ "integrity": "sha512-AR0x5HbXGqkEx9CadRH3EBYx/VkiUgZIhP4wvPn/+5KIsgpNoyFaRlVe0Zlx9gRtg8fA06a9tskE2MSN7TcG4Q=="
},
"@types/parse-json": {
"version": "4.0.0",
@@ -18731,6 +19101,33 @@
"@types/react": "*"
}
},
+ "@types/react-redux": {
+ "version": "7.1.24",
+ "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.24.tgz",
+ "integrity": "sha512-7FkurKcS1k0FHZEtdbbgN8Oc6b+stGSfZYjQGicofJ0j4U0qIn/jaSvnP2pLwZKiai3/17xqqxkkrxTgN8UNbQ==",
+ "requires": {
+ "@types/hoist-non-react-statics": "^3.3.0",
+ "@types/react": "*",
+ "hoist-non-react-statics": "^3.3.0",
+ "redux": "^4.0.0"
+ }
+ },
+ "@types/redux": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/@types/redux/-/redux-3.6.0.tgz",
+ "integrity": "sha1-8evh5UEVGAcuT9/KXHbhbnTBOZo=",
+ "requires": {
+ "redux": "*"
+ }
+ },
+ "@types/redux-thunk": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@types/redux-thunk/-/redux-thunk-2.1.0.tgz",
+ "integrity": "sha1-vCtulylhgxr7gqm/TwZybjUflBY=",
+ "requires": {
+ "redux-thunk": "*"
+ }
+ },
"@types/resolve": {
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
@@ -18779,6 +19176,17 @@
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz",
"integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw=="
},
+ "@types/styled-components": {
+ "version": "5.1.25",
+ "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.25.tgz",
+ "integrity": "sha512-fgwl+0Pa8pdkwXRoVPP9JbqF0Ivo9llnmsm+7TCI330kbPIFd9qv1Lrhr37shf4tnxCOSu+/IgqM7uJXLWZZNQ==",
+ "dev": true,
+ "requires": {
+ "@types/hoist-non-react-statics": "*",
+ "@types/react": "*",
+ "csstype": "^3.0.2"
+ }
+ },
"@types/testing-library__jest-dom": {
"version": "5.14.3",
"resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.3.tgz",
@@ -18792,6 +19200,17 @@
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz",
"integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg=="
},
+ "@types/use-sync-external-store": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
+ "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA=="
+ },
+ "@types/uuid": {
+ "version": "8.3.4",
+ "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
+ "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==",
+ "dev": true
+ },
"@types/ws": {
"version": "8.5.3",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
@@ -19520,6 +19939,23 @@
"@babel/helper-define-polyfill-provider": "^0.3.1"
}
},
+ "babel-plugin-styled-components": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.7.tgz",
+ "integrity": "sha512-i7YhvPgVqRKfoQ66toiZ06jPNA3p6ierpfUuEWxNF+fV27Uv5gxBkf8KZLHUCc1nFA9j6+80pYoIpqCeyW3/bA==",
+ "requires": {
+ "@babel/helper-annotate-as-pure": "^7.16.0",
+ "@babel/helper-module-imports": "^7.16.0",
+ "babel-plugin-syntax-jsx": "^6.18.0",
+ "lodash": "^4.17.11",
+ "picomatch": "^2.3.0"
+ }
+ },
+ "babel-plugin-syntax-jsx": {
+ "version": "6.18.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz",
+ "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY="
+ },
"babel-plugin-transform-react-remove-prop-types": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz",
@@ -19765,6 +20201,11 @@
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="
},
+ "camelize": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz",
+ "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs="
+ },
"caniuse-api": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz",
@@ -20120,6 +20561,11 @@
"postcss-selector-parser": "^6.0.9"
}
},
+ "css-color-keywords": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
+ "integrity": "sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU="
+ },
"css-declaration-sorter": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.2.2.tgz",
@@ -20227,6 +20673,16 @@
"resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz",
"integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w=="
},
+ "css-to-react-native": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.0.0.tgz",
+ "integrity": "sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ==",
+ "requires": {
+ "camelize": "^1.0.0",
+ "css-color-keywords": "^1.0.0",
+ "postcss-value-parser": "^4.0.2"
+ }
+ },
"css-tree": {
"version": "1.0.0-alpha.37",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz",
@@ -21911,6 +22367,29 @@
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
},
+ "history": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz",
+ "integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==",
+ "requires": {
+ "@babel/runtime": "^7.7.6"
+ }
+ },
+ "hoist-non-react-statics": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
+ "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
+ "requires": {
+ "react-is": "^16.7.0"
+ },
+ "dependencies": {
+ "react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+ }
+ }
+ },
"hoopy": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz",
@@ -25692,16 +26171,59 @@
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz",
"integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg=="
},
+ "react-icons": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.3.1.tgz",
+ "integrity": "sha512-cB10MXLTs3gVuXimblAdI71jrJx8njrJZmNMEMC+sQu5B/BIOmlsAjskdqpn81y8UBVEGuHODd7/ci5DvoSzTQ==",
+ "requires": {}
+ },
"react-is": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
},
+ "react-redux": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.1.tgz",
+ "integrity": "sha512-LMZMsPY4DYdZfLJgd7i79n5Kps5N9XVLCJJeWAaPYTV+Eah2zTuBjTxKtNEbjiyitbq80/eIkm55CYSLqAub3w==",
+ "requires": {
+ "@babel/runtime": "^7.12.1",
+ "@types/hoist-non-react-statics": "^3.3.1",
+ "@types/use-sync-external-store": "^0.0.3",
+ "hoist-non-react-statics": "^3.3.2",
+ "react-is": "^18.0.0",
+ "use-sync-external-store": "^1.0.0"
+ },
+ "dependencies": {
+ "react-is": {
+ "version": "18.1.0",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz",
+ "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg=="
+ }
+ }
+ },
"react-refresh": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
"integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A=="
},
+ "react-router": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.3.0.tgz",
+ "integrity": "sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ==",
+ "requires": {
+ "history": "^5.2.0"
+ }
+ },
+ "react-router-dom": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.3.0.tgz",
+ "integrity": "sha512-uaJj7LKytRxZNQV8+RbzJWnJ8K2nPsOOEuX7aQstlMZKQT0164C+X2w6bnkqU3sjtLvpd5ojrezAyfZ1+0sStw==",
+ "requires": {
+ "history": "^5.2.0",
+ "react-router": "6.3.0"
+ }
+ },
"react-scripts": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz",
@@ -25802,6 +26324,20 @@
"strip-indent": "^3.0.0"
}
},
+ "redux": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.0.tgz",
+ "integrity": "sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==",
+ "requires": {
+ "@babel/runtime": "^7.9.2"
+ }
+ },
+ "redux-thunk": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.1.tgz",
+ "integrity": "sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==",
+ "requires": {}
+ },
"regenerate": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
@@ -25913,6 +26449,11 @@
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
},
+ "reselect": {
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.5.tgz",
+ "integrity": "sha512-uVdlz8J7OO+ASpBYoz1Zypgx0KasCY20H+N8JD13oUMtPvSHQuscrHop4KbXrbsBcdB9Ds7lVK7eRkBIfO43vQ=="
+ },
"resolve": {
"version": "1.22.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz",
@@ -26254,6 +26795,11 @@
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
},
+ "shallowequal": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
+ "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
+ },
"shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -26553,6 +27099,23 @@
"integrity": "sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ==",
"requires": {}
},
+ "styled-components": {
+ "version": "5.3.5",
+ "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.5.tgz",
+ "integrity": "sha512-ndETJ9RKaaL6q41B69WudeqLzOpY1A/ET/glXkNZ2T7dPjPqpPCXXQjDFYZWwNnE5co0wX+gTCqx9mfxTmSIPg==",
+ "requires": {
+ "@babel/helper-module-imports": "^7.0.0",
+ "@babel/traverse": "^7.4.5",
+ "@emotion/is-prop-valid": "^1.1.0",
+ "@emotion/stylis": "^0.8.4",
+ "@emotion/unitless": "^0.7.4",
+ "babel-plugin-styled-components": ">= 1.12.0",
+ "css-to-react-native": "^3.0.0",
+ "hoist-non-react-statics": "^3.0.0",
+ "shallowequal": "^1.1.0",
+ "supports-color": "^5.5.0"
+ }
+ },
"stylehacks": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.0.tgz",
@@ -26982,8 +27545,7 @@
"typescript": {
"version": "4.6.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.4.tgz",
- "integrity": "sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==",
- "peer": true
+ "integrity": "sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg=="
},
"unbox-primitive": {
"version": "1.0.2",
@@ -27056,6 +27618,12 @@
"punycode": "^2.1.0"
}
},
+ "use-sync-external-store": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.1.0.tgz",
+ "integrity": "sha512-SEnieB2FPKEVne66NpXPd1Np4R1lTNKfjuy3XdIoPQKYBAFdzbzSZlSn1KJZUiihQLQC5Znot4SBz1EOTBwQAQ==",
+ "requires": {}
+ },
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
diff --git a/package.json b/package.json
index 7f031bc..a9686e1 100644
--- a/package.json
+++ b/package.json
@@ -3,12 +3,27 @@
"version": "0.1.0",
"private": true,
"dependencies": {
+ "@reduxjs/toolkit": "^1.8.1",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.1.1",
"@testing-library/user-event": "^13.5.0",
+ "@types/jest": "^27.5.0",
+ "@types/node": "^17.0.31",
+ "@types/react": "^18.0.8",
+ "@types/react-dom": "^18.0.3",
+ "@types/react-redux": "^7.1.24",
+ "@types/redux": "^3.6.0",
+ "@types/redux-thunk": "^2.1.0",
"react": "^18.1.0",
"react-dom": "^18.1.0",
+ "react-icons": "^4.3.1",
+ "react-redux": "^8.0.1",
+ "react-router-dom": "^6.3.0",
"react-scripts": "5.0.1",
+ "redux-thunk": "^2.4.1",
+ "styled-components": "^5.3.5",
+ "typescript": "^4.6.4",
+ "uuid": "^8.3.2",
"web-vitals": "^2.1.4"
},
"scripts": {
@@ -34,5 +49,9 @@
"last 1 firefox version",
"last 1 safari version"
]
+ },
+ "devDependencies": {
+ "@types/styled-components": "^5.1.25",
+ "@types/uuid": "^8.3.4"
}
}
diff --git a/src/App.js b/src/App.js
deleted file mode 100644
index 772afaf..0000000
--- a/src/App.js
+++ /dev/null
@@ -1,5 +0,0 @@
-function App() {
- return
15κΈ° νμ΄ν
π
;
-}
-
-export default App;
diff --git a/src/App.tsx b/src/App.tsx
new file mode 100644
index 0000000..bf97f70
--- /dev/null
+++ b/src/App.tsx
@@ -0,0 +1,34 @@
+import React from "react";
+import { Outlet } from "react-router-dom";
+import styled from "styled-components";
+
+import SideButtonContainer from "./containers/SideButtonContainer";
+
+const AppContainer = styled.div`
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ height: 100vh;
+ font-family: var(--ff-main);
+`
+
+const EmptyDiv = styled.div`
+ width: 5rem;
+ height: 3rem;
+ margin-left: 1rem;
+ flex: 1;
+`
+
+function App() {
+
+ return (
+
+
+
+
+ );
+}
+
+export default App;
diff --git a/src/GlobalStyle.tsx b/src/GlobalStyle.tsx
new file mode 100644
index 0000000..c9a0525
--- /dev/null
+++ b/src/GlobalStyle.tsx
@@ -0,0 +1,27 @@
+import {createGlobalStyle} from "styled-components";
+
+export default createGlobalStyle`
+ @import url("https://fonts.googleapis.com/css2?family=Noto+Sans+KR&display=swap");
+
+ :root{
+ --ff-main: "Noto Sans KR", sans-serif;
+ }
+
+ *,
+ ::after,
+ ::before {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+ }
+
+ body{
+ display: flex;
+ height: 100vh;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ background-color: #f0f0f0;
+ background-image: linear-gradient(45deg, #ff9a9e 0%, #fad0c4 99%, #fad0c4 100%);
+ }
+`;
diff --git a/src/components/ChatButtonComponent.tsx b/src/components/ChatButtonComponent.tsx
new file mode 100644
index 0000000..4ad7a4a
--- /dev/null
+++ b/src/components/ChatButtonComponent.tsx
@@ -0,0 +1,94 @@
+import React from 'react'
+import styled from 'styled-components'
+
+import { useAppDispatch, useAppSelector } from '../hooks'
+import { useNavigate } from 'react-router-dom'
+import { setActiveChat } from '../state/chatSlice'
+import { Chat } from '../types'
+
+const Component = styled.button`
+ width: 80%;
+ display: flex;
+ flex-direction: column;
+ align-content: flex-start;
+ justify-content: flex-start;
+ border-radius: 3rem;
+ background-color: rgba(255, 255, 255, 0.5);
+ margin: 1rem 0 1rem 0;
+ border: transparent;
+ padding: 0 2rem 0.5rem 2rem;
+ cursor: pointer;
+ &:hover {
+ box-shadow: 0px 0px 20px rgba(230, 230, 230, 1);
+ }
+`
+const ProfileDiv = styled.div`
+ width: 100%;
+ display: flex;
+ justify-content: flex-start;
+ height: 3rem;
+ align-items: center;
+`
+
+const NameDiv = styled.div`
+ background-color: rgba(255, 255, 255, 1);
+ border-radius: 3rem;
+ padding: 0.3rem 1rem 0.3rem 1rem;
+ margin-left: 0.5rem;
+ text-transform: lowercase;
+ color: rgb(130, 130, 130);
+ font-size: 1rem;
+ box-shadow: 0px 5px 10px rgba(230, 230, 230, 0.3);
+`
+const MessageDiv = styled.div`
+ width: 100%;
+ display: flex;
+ justify-content: space-between;
+ padding: 0.3rem 1rem 0.3rem 1rem;
+ text-transform: lowercase;
+ color: rgb(130, 130, 130);
+ font-size: 1.3rem;
+ box-shadow: 0px 5px 10px rgba(230, 230, 230, 0.3);
+`
+
+const TimeP = styled.p`
+ font-size: 1rem;
+`
+
+const ProfileImage = styled.img`
+ position: relative;
+ bottom: 1rem;
+ width: 4rem;
+ height: 4rem;
+ object-fit: cover;
+`
+
+function ChatButtonComponent({chat}:{chat:Chat}) {
+
+ const dispatch = useAppDispatch();
+ const navigate = useNavigate();
+ const clients = useAppSelector((state)=>state.client.clients);
+
+ return (
+ {
+ dispatch(setActiveChat(chat.chatId));
+ navigate(`/chats/${chat.chatId}`);
+ }}>
+ {chat.clients.map((client)=>{
+ if(client.id !== clients[0].id)
+ return(
+
+
+ {client.name}
+
+ )
+ })}
+
+ <>{chat.messages[chat.messages.length-1]? chat.messages[chat.messages.length-1].content : ""}>
+ {chat.messages[chat.messages.length-1]? chat.messages[chat.messages.length-1].timeString : ""}
+
+
+ )
+}
+
+export default ChatButtonComponent
\ No newline at end of file
diff --git a/src/components/MessageBubbleComponent.tsx b/src/components/MessageBubbleComponent.tsx
new file mode 100644
index 0000000..e647a2d
--- /dev/null
+++ b/src/components/MessageBubbleComponent.tsx
@@ -0,0 +1,93 @@
+import styled from 'styled-components'
+
+import { Message, MessageType } from '../types'
+
+const componentPadding = {
+ [MessageType.BASE]: "0.5rem 1rem 0.5rem 1rem",
+ [MessageType.HEAD]: "0.5rem 1rem 0rem 1rem",
+ [MessageType.BODY]: "0rem 1rem 0rem 1rem",
+ [MessageType.TAIL]: "0rem 1rem 0.5rem 1rem",
+}
+
+const bubbleRadiusSelected = {
+ [MessageType.BASE]: "1rem",
+ [MessageType.HEAD]: "1rem 1rem 0.3rem 1rem",
+ [MessageType.BODY]: "1rem 0.3rem 0.3rem 1rem",
+ [MessageType.TAIL]: "1rem 0.3rem 1rem 1rem",
+}
+const bubbleRadiusDeselected = {
+ [MessageType.BASE]: "1rem",
+ [MessageType.HEAD]: "1rem 1rem 1rem 0.3rem",
+ [MessageType.BODY]: "0.3rem 1rem 1rem 0.3rem",
+ [MessageType.TAIL]: "0.3rem 1rem 1rem 1rem",
+}
+
+
+const Component = styled.div<{selected: boolean, manner: MessageType}>`
+ width: 100%;
+ display: flex;
+ flex-direction: ${({selected})=>(selected? "row-reverse":"row")};
+ padding: ${({manner})=>componentPadding[manner]};
+`
+const MessageProfileImg = styled.img`
+ width: 3rem;
+ height: 3rem;
+ object-fit: contain;
+`
+const MessageProfileDiv = styled.div`
+ width: 3rem;
+ height: 3rem;
+`
+const MessageContentDiv = styled.div`
+ width: 30vw;
+ max-width: 20rem;
+ margin-left: 1rem;
+ margin-right: 1rem
+`
+const Name = styled.p<{selected: boolean}>`
+ font-size: 0.8rem;
+ text-align: ${({selected})=>(selected? "right":"left")};
+`
+
+const MessageHolderDiv = styled.div<{selected: boolean}>`
+ width: 100%;
+ display: flex;
+ flex-direction: ${({selected})=>(selected? "row-reverse":"row")};
+ align-items: flex-end;
+`
+
+const MessageBubbleDiv = styled.div<{selected: boolean, manner: MessageType}>`
+ max-width: calc(100% - 4rem);
+ height: 100%;
+ background-color: ${({selected})=>(selected? "#ff9a9e":"#ffecd2")};
+ border-radius: ${({selected, manner})=>(selected? bubbleRadiusSelected[manner]:bubbleRadiusDeselected[manner])};
+ padding: 0.8rem;
+ margin-top: 0.2rem;
+ color: ${({selected})=>(selected? "white":"black")};
+ word-wrap: break-word;
+`
+const Time = styled.p`
+ font-size: 0.6rem;
+ margin-left: 0.3rem;
+ margin-right: 0.3rem;
+`
+
+const MessageBubbleComponent = ({msg, timeString, selected, manner}: {msg:Message, timeString:string, selected:boolean, manner:MessageType}) => {
+
+ return(
+
+
+ {manner === MessageType.BASE || manner === MessageType.HEAD? : "" }
+
+
+ {manner === MessageType.BASE || manner === MessageType.HEAD? {msg.client.name} : "" }
+
+ {msg.content}
+ {manner === MessageType.BASE || manner === MessageType.TAIL? : "" }
+
+
+
+ )
+}
+
+export default MessageBubbleComponent;
\ No newline at end of file
diff --git a/src/containers/FriendsContainer.tsx b/src/containers/FriendsContainer.tsx
new file mode 100644
index 0000000..eedbf0e
--- /dev/null
+++ b/src/containers/FriendsContainer.tsx
@@ -0,0 +1,105 @@
+import React from 'react'
+import styled from 'styled-components'
+import { useNavigate } from 'react-router-dom'
+import {v4 as uuidv4} from "uuid";
+
+import { useAppDispatch, useAppSelector } from '../hooks'
+import { addChat, setActiveChat, setActiveClient } from '../state/chatSlice'
+
+const Container = styled.div`
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ align-content: flex-start;
+ flex-wrap: wrap;
+ padding: 1rem;
+ overflow: scroll;
+ height: 60vh;
+ &::-webkit-scrollbar {
+ display:none;
+ }
+`
+
+const ProfileDiv = styled.div`
+ width: 8rem;
+ height: 10rem;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ margin: 1rem;
+ background-color: rgba(255, 255, 255, 0.5);
+ border-radius: 2rem;
+ & p{
+ font-size: 0.8rem;
+ }
+ &:hover{
+ box-shadow: 0px 0px 20px rgba(0, 0, 0, 0.1);
+ }
+`
+
+const ProfileName = styled.p`
+ border-radius: 3rem;
+ background-color: rgba(255, 255, 255, 1);
+ color: rgba(150, 150, 150, 1);
+ margin: 1rem 0 1rem 0;
+ padding: 0.2rem 1rem 0.2rem 1rem;
+ text-align: center;
+ box-shadow: 0px 5px 10px rgba(230, 230, 230, 0.3)
+`
+
+const ProfileButton = styled.button`
+ width: 6rem;
+ height: 6rem;
+ border-radius: 0;
+ border: transparent;
+ cursor: pointer;
+ background-color: rgba(255, 255, 255, 0);
+ z-index: 1;
+ font-size: 4rem;
+ overflow: visible;
+`
+const ProfileImage = styled.img`
+ width: 6rem;
+ height: 6rem;
+ display: block;
+ object-fit: cover;
+ margin-left: auto;
+ margin-right: auto;
+`
+
+function FriendsContainer({query}:{query: string}) {
+ const {clients} = useAppSelector((state)=> state.client);
+ const dispatch = useAppDispatch();
+ const navigate = useNavigate();
+
+ return (
+
+ {clients.map(client=>{
+ if(client.name.includes(query) && client.id !== clients[0].id){
+ return(
+
+ {
+ const id = uuidv4();
+ dispatch(addChat({
+ chatId: id,
+ activeClient: clients[0],
+ clients: [clients[0], client],
+ messages: [],
+ }));
+ dispatch(setActiveChat(id));
+ dispatch(setActiveClient(clients[0]));
+ navigate(`/chats/${id}`);
+ }}>
+
+
+ {client.name}
+
+ )
+ }
+ })}
+
+ )
+}
+
+export default FriendsContainer
\ No newline at end of file
diff --git a/src/containers/InputContainer.tsx b/src/containers/InputContainer.tsx
new file mode 100644
index 0000000..4622e0f
--- /dev/null
+++ b/src/containers/InputContainer.tsx
@@ -0,0 +1,114 @@
+import React, {useState} from 'react'
+import {v4 as uuidv4} from "uuid";
+import styled from 'styled-components';
+
+import { useAppSelector, useAppDispatch } from '../hooks';
+import { addMessage } from '../state/chatSlice';
+import { MessageType, Client } from '../types';
+
+const Container = styled.div`
+ width: 90vw;
+ max-width: 35rem;
+ height: 4rem;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-bottom: 5vh;
+ z-index: 1;
+`
+
+const Form = styled.form`
+ width: 90vw;
+ max-width: 35rem;
+ display: flex;
+ flex-direction: row;
+ justify-content: space-around;
+`
+
+const Button = styled.button`
+ width: 4rem;
+ height: 4rem;
+ background-color: rgba(255, 255, 255, 0.5);
+ border-radius: 4rem;
+ border: none;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ cursor: pointer;
+ &:hover {
+ box-shadow: 0 0 10px rgba(255, 255, 255, 0.6);
+ background-color: rgba(255, 255, 255, 0.8);
+ }
+ & p {
+ font-size: 2.5rem;
+ width: 3rem;
+ text-align: center;
+ }
+`
+
+const Input = styled.input`
+ height: 4rem;
+ width: 70%;
+ border-radius: 4rem;
+ border-color: transparent;
+ padding-left: 1.2rem;
+ outline: none;
+ box-shadow: 0px 0px 20px rgba(255, 255, 255, 1);
+ &:hover {
+ box-shadow: 0 0 10px rgba(255, 255, 255, 0.6);
+ }
+ font-size: 1rem;
+`
+
+const handleTimeFormat = (time: Date):string[] => {
+ const hourString: string = time.getHours().toString();
+ const minString: string = time.getMinutes().toString();
+ const timeString: string = (hourString.length === 2? hourString : "0" + hourString) + ": " + (minString.length === 2? minString : "0" + minString)
+
+ return [timeString]
+};
+
+function InputContainer() {
+ const [message, setMessage] = useState("");
+
+ const activeClient:Client = useAppSelector((state)=>state.chat.chats[state.chat.acidx!].activeClient);
+ const dispatch = useAppDispatch();
+
+ const handleFormSubmit = (message: string, e?:React.FormEvent) => {
+
+ if(e) e.preventDefault();
+ if(message !== "" && activeClient){
+
+ const [timeString] = handleTimeFormat(new Date());
+
+ dispatch(addMessage({
+ msgId: uuidv4(),
+ client: activeClient,
+ content: message,
+ timeString: timeString,
+ messageType: MessageType.BASE,
+ }));
+
+ setMessage("");
+ }else if(message === ""){
+ alert("enter the message")
+ }else{
+ alert("select the profile")
+ }
+ }
+
+ return (
+
+
+
+
+ )
+}
+
+export default InputContainer
\ No newline at end of file
diff --git a/src/containers/MessageContainer.tsx b/src/containers/MessageContainer.tsx
new file mode 100644
index 0000000..44782d3
--- /dev/null
+++ b/src/containers/MessageContainer.tsx
@@ -0,0 +1,44 @@
+import React, { useEffect } from 'react'
+import styled from 'styled-components'
+
+import { useAppSelector } from '../hooks'
+import { Message } from "../types"
+import MessageBubbleComponent from '../components/MessageBubbleComponent'
+
+const Container = styled.div`
+ width: 90vw;
+ margin-top: 20vh;
+ height: 65vh;
+ z-index: 0;
+ max-width: 35rem;
+ position: absolute;
+ overflow: scroll;
+ &::-webkit-scrollbar {
+ display:none;
+ }
+`
+
+function MessageContainer() {
+
+ const messages = useAppSelector((state)=> state.chat.chats[state.chat.acidx!].messages);
+ const activeClient = useAppSelector((state)=>state.chat.chats[state.chat.acidx!].activeClient);
+ const containerRef = React.useRef(null);
+
+ useEffect(
+ ()=>{
+ containerRef.current!.scrollTop = containerRef.current!.scrollHeight
+ }
+ );
+
+ return (
+
+ {messages.map((message: Message)=>{
+ const timeString: string = message.timeString;
+ const selected: boolean = message.client.id === activeClient?.id;
+ return
+ })}
+
+ )
+}
+
+export default MessageContainer
\ No newline at end of file
diff --git a/src/containers/ProfileContainer.tsx b/src/containers/ProfileContainer.tsx
new file mode 100644
index 0000000..d4d6b22
--- /dev/null
+++ b/src/containers/ProfileContainer.tsx
@@ -0,0 +1,95 @@
+import React from 'react'
+import { useNavigate } from 'react-router-dom'
+import styled from 'styled-components'
+import { useAppDispatch, useAppSelector } from '../hooks'
+import { setActiveClient } from '../state/chatSlice'
+
+const Container = styled.div`
+ width: 50vw;
+ height: 8rem;
+ max-width: 35rem;
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ align-items: center;
+ margin-top: 3vh;
+ z-index: 1;
+`
+
+const ProfileDiv = styled.div`
+ width: 5em;
+ height: 8rem;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ align-items: center;
+ margin-left: 1rem;
+ margin-right: 1rem;
+ & p{
+ font-size: 0.8rem;
+ }
+`
+
+const ProfileName = styled.p<{selected: boolean}>`
+ border-radius: 0.4rem;
+ background-color: rgba(255, 255, 255, ${({selected})=>(selected? "1":"0")});
+ color: ${({selected})=>(selected? "rgba(150, 150, 150, 1)":"rgba(255, 255, 255, 1)")};
+ margin-top: 0.6rem;
+ padding: 0 1rem 0 1rem;
+ text-align: center;
+ box-shadow: 0px 0px 20px rgba(255, 255, 255, ${({selected})=>(selected? "1":"0")});
+ text-shadow: 0px 0px 10px rgba(255, 255, 255, ${({selected})=>(selected? "0":"1")});
+`
+
+const ProfileButton = styled.button<{selected: boolean}>`
+ width: 6rem;
+ height: 6rem;
+ border-radius: 3rem;
+ position: relative;
+ border: transparent;
+ cursor: pointer;
+ background-color: rgba(255, 255, 255, ${({selected})=>(selected? "1":"0")});
+ z-index: 1;
+ font-size: 4rem;
+ box-shadow: 0px 0px 20px rgba(255, 255, 255, ${({selected})=>(selected? "1":"0")});
+ &:hover{
+ text-shadow: 0px 0px 20px rgba(0, 0, 0, 0.1);
+ }
+`
+const ProfileImage = styled.img`
+ width: 80%;
+ height: 80%;
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
+`
+
+function ProfileContainer() {
+ const {clients, activeClient} = useAppSelector((state)=> state.chat.chats[state.chat.acidx!])
+ const dispatch = useAppDispatch();
+ const navigate = useNavigate();
+
+ return (
+
+
+ {
+ navigate(-1);
+ }}>π«²π»
+ {"λ€λ‘κ°κΈ°"}
+
+ {clients.map(client=>{
+ const selected = client.id === activeClient?.id? true : false
+ return(
+
+ {
+ dispatch(setActiveClient(client))
+ }}>
+ {client.name}
+
+ )
+ })}
+
+ )
+}
+
+export default ProfileContainer
\ No newline at end of file
diff --git a/src/containers/SideButtonContainer.tsx b/src/containers/SideButtonContainer.tsx
new file mode 100644
index 0000000..b0692b9
--- /dev/null
+++ b/src/containers/SideButtonContainer.tsx
@@ -0,0 +1,61 @@
+import React from 'react'
+import styled from 'styled-components'
+import { useNavigate } from 'react-router-dom'
+import { FiMessageCircle, FiInfo, FiSmile } from "react-icons/fi";
+
+const Container = styled.div`
+ display: flex;
+ width: 5rem;
+ margin-right: 1rem;
+ flex: 1;
+ align-items: center;
+ justify-content: center;
+`
+
+const ButtonDiv = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 1rem 0.5rem 1rem 0.5rem;
+ background-color: rgba(255, 255, 255, 0.5);
+ border-radius: 3rem;
+`
+
+const Button = styled.button`
+ border-radius: 3rem;
+ border: none;
+ padding: 0.5rem;
+ margin: 0.5rem;
+ background-color: rgba(255, 255, 255, 0.8);
+ box-shadow: 0 0.3rem 1rem 0.1rem rgba(255, 255, 255, 0.1);
+ cursor: pointer;
+ &:hover {
+ box-shadow: 0 0 10px rgba(255, 255, 255, 1);
+ background-color: rgba(255, 255, 255, 1);
+ }
+`
+
+const ButtonIcon = styled.div`
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ font-size: 1.5rem;
+ color: rgba(200, 200, 200, 1);
+`
+
+function SideButtonContainer() {
+ const navigate = useNavigate();
+
+ return (
+
+
+
+
+
+
+
+ )
+}
+
+export default SideButtonContainer
\ No newline at end of file
diff --git a/src/hooks.ts b/src/hooks.ts
new file mode 100644
index 0000000..32827e5
--- /dev/null
+++ b/src/hooks.ts
@@ -0,0 +1,5 @@
+import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
+import type {RootState, AppDispatch} from "./state/store";
+
+export const useAppDispatch = () => useDispatch()
+export const useAppSelector: TypedUseSelectorHook = useSelector
\ No newline at end of file
diff --git a/src/images/cat-face.png b/src/images/cat-face.png
new file mode 100644
index 0000000..b9e5176
Binary files /dev/null and b/src/images/cat-face.png differ
diff --git a/src/images/dog-face.png b/src/images/dog-face.png
new file mode 100644
index 0000000..284e875
Binary files /dev/null and b/src/images/dog-face.png differ
diff --git a/src/images/fox-face.png b/src/images/fox-face.png
new file mode 100644
index 0000000..046f010
Binary files /dev/null and b/src/images/fox-face.png differ
diff --git a/src/images/monkey-face.png b/src/images/monkey-face.png
new file mode 100644
index 0000000..d697fef
Binary files /dev/null and b/src/images/monkey-face.png differ
diff --git a/src/images/pig-face.png b/src/images/pig-face.png
new file mode 100644
index 0000000..e9029ae
Binary files /dev/null and b/src/images/pig-face.png differ
diff --git a/src/images/robot-face.png b/src/images/robot-face.png
new file mode 100644
index 0000000..44b6820
Binary files /dev/null and b/src/images/robot-face.png differ
diff --git a/src/index.js b/src/index.js
deleted file mode 100644
index 593edf1..0000000
--- a/src/index.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import React from 'react';
-import ReactDOM from 'react-dom/client';
-import App from './App';
-
-const root = ReactDOM.createRoot(document.getElementById('root'));
-root.render(
-
-
-
-);
diff --git a/src/index.tsx b/src/index.tsx
new file mode 100644
index 0000000..fc04003
--- /dev/null
+++ b/src/index.tsx
@@ -0,0 +1,39 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import App from './App';
+import { Provider } from 'react-redux';
+import { BrowserRouter, Routes, Route } from "react-router-dom";
+
+import GlobalStyle from "./GlobalStyle"
+import store from './state/store';
+import Chatroom from './pages/Chatroom';
+import AppInfo from './pages/AppInfo';
+import ChatList from './pages/ChatList';
+import FriendList from "./pages/FriendList"
+
+const root = ReactDOM.createRoot(document.getElementById('root')!);
+root.render(
+
+
+
+
+
+ }>
+ }/>
+ }/>
+ }/>
+ }/>
+
+ NOT FOUND
+
+ }
+ />
+
+
+
+
+
+);
diff --git a/src/jsons/clients.json b/src/jsons/clients.json
new file mode 100644
index 0000000..e69de29
diff --git a/src/jsons/messages.json b/src/jsons/messages.json
new file mode 100644
index 0000000..e69de29
diff --git a/src/pages/AppInfo.tsx b/src/pages/AppInfo.tsx
new file mode 100644
index 0000000..f6cac83
--- /dev/null
+++ b/src/pages/AppInfo.tsx
@@ -0,0 +1,47 @@
+import React from 'react'
+import styled from 'styled-components'
+
+const Wrapper = styled.div`
+ width: 90vw;
+ max-width: 35rem;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+`
+
+const MenuDiv = styled.div`
+ width: 80%;
+ height: 3rem;
+ border-radius: 3rem;
+ background-color: rgba(255, 255, 255, 0.5);
+ display: flex;
+ margin: 0.8rem 0 0.8rem 0;
+ padding-left: 2rem;
+ align-items: center;
+ cursor: pointer;
+ &:hover {
+ box-shadow: 0px 0px 20px rgba(230, 230, 230, 1);
+ }
+`
+const TextDiv = styled.div`
+ background-color: rgba(255, 255, 255, 1);
+ border-radius: 3rem;
+ padding: 0.3rem 1rem 0.3rem 1rem;
+ text-transform: lowercase;
+ color: rgb(130, 130, 130);
+ font-size: 1rem;
+ box-shadow: 0px 5px 10px rgba(230, 230, 230, 0.3);
+`
+
+const menuItems = ["Profile", "Privacy", "General", "Payments", "Notifications"]
+
+function AppInfo() {
+ return (
+
+ {menuItems.map((item)=>{item})}
+
+ )
+}
+
+export default AppInfo
\ No newline at end of file
diff --git a/src/pages/ChatList.tsx b/src/pages/ChatList.tsx
new file mode 100644
index 0000000..945ded1
--- /dev/null
+++ b/src/pages/ChatList.tsx
@@ -0,0 +1,30 @@
+import React from 'react'
+import styled from 'styled-components'
+
+import { useAppSelector } from '../hooks'
+import ChatButtonComponent from '../components/ChatButtonComponent'
+
+const Wrapper = styled.div`
+ width: 90vw;
+ max-width: 35rem;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+`
+
+function ChatList() {
+ const chats = useAppSelector((state)=>state.chat.chats);
+
+ return (
+
+ {chats.map(chat=>{
+ return(
+
+ )
+ })}
+
+ )
+}
+
+export default ChatList
\ No newline at end of file
diff --git a/src/pages/Chatroom.tsx b/src/pages/Chatroom.tsx
new file mode 100644
index 0000000..d6f5a9e
--- /dev/null
+++ b/src/pages/Chatroom.tsx
@@ -0,0 +1,25 @@
+import React from 'react'
+import styled from 'styled-components'
+
+import InputContainer from '../containers/InputContainer'
+import MessageContainer from '../containers/MessageContainer'
+import ProfileContainer from '../containers/ProfileContainer'
+
+const Wrapper = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: space-between;
+ height: 100vh;
+`
+
+function Chatroom() {
+ return (
+
+
+
+
+
+ )
+}
+export default Chatroom
diff --git a/src/pages/FriendList.tsx b/src/pages/FriendList.tsx
new file mode 100644
index 0000000..868134e
--- /dev/null
+++ b/src/pages/FriendList.tsx
@@ -0,0 +1,41 @@
+import React, {useState} from 'react'
+import styled from 'styled-components'
+
+import FriendsContainer from '../containers/FriendsContainer'
+
+const Wrapper = styled.div`
+ width: 90vw;
+ max-width: 35rem;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+`
+
+const Input = styled.input`
+ height: 3rem;
+ width: 80%;
+ border-radius: 4rem;
+ border-color: transparent;
+ padding-left: 1.2rem;
+ font-size: 1rem;
+ margin-bottom: 2rem;
+ outline: none;
+ box-shadow: 0px 0px 20px rgba(255, 255, 255, 1);
+ &:hover {
+ box-shadow: 0 0 10px rgba(255, 255, 255, 0.6);
+ }
+`
+
+function FriendList() {
+ const [query, setQuery] = useState("");
+
+ return (
+
+ setQuery(e.target.value)} value={query}/>
+
+
+ )
+}
+
+export default FriendList
\ No newline at end of file
diff --git a/src/state/chatSlice.ts b/src/state/chatSlice.ts
new file mode 100644
index 0000000..ccfbe35
--- /dev/null
+++ b/src/state/chatSlice.ts
@@ -0,0 +1,47 @@
+import {createSlice, PayloadAction} from "@reduxjs/toolkit"
+
+import { ChatState } from "./state.types"
+import { MessageType, Message, Chat, Client } from "../types";
+
+const chatState: ChatState = {
+ chats: []
+};
+
+const messageTypeHandler = (messages: Message[], payload: Message) => {
+ const prevMessage = messages[messages.length - 1]
+ if(prevMessage && prevMessage.timeString.slice(-2) === payload.timeString.slice(-2) && prevMessage.client.id === payload.client.id){
+ if(prevMessage.messageType === MessageType.TAIL){
+ prevMessage.messageType = MessageType.BODY;
+ payload.messageType = MessageType.TAIL;
+ }else{
+ prevMessage.messageType = MessageType.HEAD;
+ payload.messageType = MessageType.TAIL;
+ }
+ }
+};
+
+const chatSlice = createSlice({
+ name: "chat",
+ initialState: chatState,
+ reducers: {
+ addChat(state, action: PayloadAction){
+ state.chats.push(action.payload)
+ },
+ addMessage(state, action: PayloadAction) {
+ const idx = state.acidx!;
+ messageTypeHandler(state.chats[idx].messages, action.payload)
+ state.chats[idx].messages.push(action.payload)
+ },
+ setActiveChat(state, action: PayloadAction){
+ state.acidx = state.chats.findIndex((chat)=>chat.chatId === action.payload)
+ },
+ setActiveClient(state, action: PayloadAction){
+ const idx = state.acidx!;
+ state.chats[idx].activeClient = action.payload;
+ }
+ }
+})
+
+
+export const chatReducer = chatSlice.reducer
+export const {addChat, addMessage, setActiveChat, setActiveClient} = chatSlice.actions
diff --git a/src/state/clientSlice.ts b/src/state/clientSlice.ts
new file mode 100644
index 0000000..b1aa054
--- /dev/null
+++ b/src/state/clientSlice.ts
@@ -0,0 +1,57 @@
+import {createSlice, PayloadAction} from "@reduxjs/toolkit"
+
+import { ClientState } from "./state.types"
+import {Client} from "../types"
+
+const clientState: ClientState = {
+ clients: [
+ { id: "1",
+ imageUrl: require("../images/dog-face.png"),
+ name: "κ°μμ§",},
+ {
+ id: "2",
+ imageUrl: require("../images/cat-face.png"),
+ name: "κ³ μμ΄"
+ },
+ {
+ id: "3",
+ imageUrl: require("../images/fox-face.png"),
+ name: "μ¬μ°"
+ },
+ {
+ id: "4",
+ imageUrl: require("../images/monkey-face.png"),
+ name: "μμμ΄"
+ },
+ {
+ id: "5",
+ imageUrl: require("../images/pig-face.png"),
+ name: "λΌμ§"
+ },
+ {
+ id: "6",
+ imageUrl: require("../images/robot-face.png"),
+ name: "λ‘λ΄"
+ }
+ ]
+}
+
+const clientSlice = createSlice({
+ name: "client",
+ initialState: clientState,
+ reducers: {
+ // addClient(state, action: PayloadAction) {
+ // state.clients.push(action.payload)
+ // state.currentClient = action.payload;
+ // },
+ // removeClient(state, action: PayloadAction){
+ // state.clients = state.clients.filter((client)=>{
+ // if(state.currentClient && state.currentClient.id == action.payload.id)
+ // delete state.currentClient;
+ // return client.id !== action.payload.id})
+ // },
+ }
+})
+
+export const clientReducer = clientSlice.reducer
+export const {} = clientSlice.actions
\ No newline at end of file
diff --git a/src/state/state.types.ts b/src/state/state.types.ts
new file mode 100644
index 0000000..f9f351c
--- /dev/null
+++ b/src/state/state.types.ts
@@ -0,0 +1,14 @@
+import { Message, Client, Chat } from "../types";
+
+export type ChatState = {
+ chats: Chat[],
+ acidx?: number, //active chat index
+}
+
+export type MessageState = {
+ messages: Message[]
+};
+
+export type ClientState = {
+ clients: Client[]
+}
\ No newline at end of file
diff --git a/src/state/store.ts b/src/state/store.ts
new file mode 100644
index 0000000..832b908
--- /dev/null
+++ b/src/state/store.ts
@@ -0,0 +1,16 @@
+import { configureStore } from "@reduxjs/toolkit";
+
+import { chatReducer } from "./chatSlice";
+import { clientReducer } from "./clientSlice";
+
+const store = configureStore({
+ reducer: {
+ // message: messageReducer,
+ client: clientReducer,
+ chat: chatReducer,
+ }}
+)
+
+export default store;
+export type RootState = ReturnType;
+export type AppDispatch = typeof store.dispatch;
\ No newline at end of file
diff --git a/src/types.ts b/src/types.ts
new file mode 100644
index 0000000..8540252
--- /dev/null
+++ b/src/types.ts
@@ -0,0 +1,24 @@
+export type Message = {
+ msgId: string,
+ client: Client,
+ content: string,
+ timeString: string,
+ messageType: MessageType,
+}
+
+export type Client = {
+ id: string,
+ imageUrl: string,
+ name: string,
+}
+
+export type Chat = {
+ chatId: string,
+ activeClient: Client,
+ clients: Client[],
+ messages: Message[],
+}
+
+export enum MessageType {
+ HEAD, BODY, TAIL, BASE
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..66c8ee8
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,93 @@
+{
+ "compilerOptions": {
+ /* Visit https://aka.ms/tsconfig.json to read more about this file */
+ /* Projects */
+ // "incremental": true, /* Enable incremental compilation */
+ // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
+ // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
+ // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
+ // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
+ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
+ /* Language and Environment */
+ "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
+ // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
+ "jsx": "preserve", /* Specify what JSX code is generated. */
+ // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
+ // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
+ // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
+ // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
+ // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
+ // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
+ // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
+ // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
+ /* Modules */
+ "module": "commonjs", /* Specify what module code is generated. */
+ // "rootDir": "./", /* Specify the root folder within your source files. */
+ // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
+ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
+ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
+ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
+ // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
+ // "types": [], /* Specify type package names to be included without being referenced in a source file. */
+ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
+ // "resolveJsonModule": true, /* Enable importing .json files */
+ // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */
+ /* JavaScript Support */
+ // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
+ // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
+ // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
+ /* Emit */
+ // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
+ // "declarationMap": true, /* Create sourcemaps for d.ts files. */
+ // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
+ // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
+ // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
+ // "outDir": "./", /* Specify an output folder for all emitted files. */
+ // "removeComments": true, /* Disable emitting comments. */
+ // "noEmit": true, /* Disable emitting files from a compilation. */
+ // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
+ // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
+ // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
+ // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
+ // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
+ // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
+ // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
+ // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
+ // "newLine": "crlf", /* Set the newline character for emitting files. */
+ // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
+ // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
+ // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
+ // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
+ // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
+ // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
+ /* Interop Constraints */
+ // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
+ // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
+ "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
+ // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
+ "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
+ /* Type Checking */
+ "strict": true, /* Enable all strict type-checking options. */
+ // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
+ // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
+ // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
+ // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
+ // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
+ // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
+ // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
+ // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
+ // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
+ // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
+ // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
+ // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
+ // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
+ // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
+ // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
+ // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
+ // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
+ // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
+ /* Completeness */
+ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
+ "skipLibCheck": true /* Skip type checking all .d.ts files. */
+ }
+}
\ No newline at end of file