From 4abceae698fa280319b41a3682cdb8b7029103d3 Mon Sep 17 00:00:00 2001 From: Daeheon Jeong Date: Thu, 5 May 2022 15:45:42 +0900 Subject: [PATCH 01/10] =?UTF-8?q?FEAT:=20=EA=B8=B0=EB=B3=B8=20=ED=8B=80=20?= =?UTF-8?q?=EC=A0=9C=EC=9E=91,=20REDUX=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 318 +++++++++++++++++++++++++++-- package.json | 15 ++ src/App.js | 5 - src/App.tsx | 12 ++ src/container/InputContainer.tsx | 38 ++++ src/container/MessageContainer.tsx | 19 ++ src/container/ProfileContainer.tsx | 9 + src/hooks.ts | 5 + src/{index.js => index.tsx} | 6 +- src/state/slices.ts | 20 ++ src/state/store.ts | 12 ++ src/state/types.ts | 10 + tsconfig.json | 93 +++++++++ 13 files changed, 541 insertions(+), 21 deletions(-) delete mode 100644 src/App.js create mode 100644 src/App.tsx create mode 100644 src/container/InputContainer.tsx create mode 100644 src/container/MessageContainer.tsx create mode 100644 src/container/ProfileContainer.tsx create mode 100644 src/hooks.ts rename src/{index.js => index.tsx} (64%) create mode 100644 src/state/slices.ts create mode 100644 src/state/store.ts create mode 100644 src/state/types.ts create mode 100644 tsconfig.json diff --git a/package-lock.json b/package-lock.json index 43e807c..990fcbb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,13 +8,28 @@ "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-redux": "^8.0.1", "react-scripts": "5.0.1", + "redux-thunk": "^2.4.1", + "typescript": "^4.6.4", + "uuid": "^8.3.2", "web-vitals": "^2.1.4" + }, + "devDependencies": { + "@types/uuid": "^8.3.4" } }, "node_modules/@ampproject/remapping": { @@ -2841,6 +2856,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 +3553,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 +3597,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 +3621,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 +3673,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", @@ -3687,6 +3763,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", @@ -7990,6 +8077,19 @@ "he": "bin/he" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -13411,6 +13511,49 @@ "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", @@ -13549,6 +13692,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 +13850,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", @@ -15131,7 +15295,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 +15402,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", @@ -18139,6 +18310,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 +18802,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 +18846,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 +18870,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 +18922,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", @@ -18792,6 +19010,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", @@ -21911,6 +22140,21 @@ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" }, + "hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "requires": { + "react-is": "^16.7.0" + }, + "dependencies": { + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } + } + }, "hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -25697,6 +25941,26 @@ "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", @@ -25802,6 +26066,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 +26191,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", @@ -26982,8 +27265,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 +27338,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..8506f72 100644 --- a/package.json +++ b/package.json @@ -3,12 +3,24 @@ "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-redux": "^8.0.1", "react-scripts": "5.0.1", + "redux-thunk": "^2.4.1", + "typescript": "^4.6.4", + "uuid": "^8.3.2", "web-vitals": "^2.1.4" }, "scripts": { @@ -34,5 +46,8 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "devDependencies": { + "@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..892c084 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,12 @@ +import React from "react"; +import InputContainer from "./container/InputContainer"; +import MessageContainer from "./container/MessageContainer"; + +function App() { + return <>
15κΈ° νŒŒμ΄νŒ… πŸ’–
+ + + ; +} + +export default App; diff --git a/src/container/InputContainer.tsx b/src/container/InputContainer.tsx new file mode 100644 index 0000000..9cb5977 --- /dev/null +++ b/src/container/InputContainer.tsx @@ -0,0 +1,38 @@ +import React, {useState} from 'react' +import {v4 as uuidv4} from "uuid"; +import { useAppSelector, useAppDispatch } from '../hooks'; +import { addMessage } from '../state/slices'; + +function InputContainer() { + const [message, setMessage] = useState(""); + const dispatch = useAppDispatch(); + + const submitHandler = (e:React.FormEvent) => { + e.preventDefault(); + + if(message != ""){ + + dispatch(addMessage({ + msgId: uuidv4(), + ownerId: 0, + content: message, + time: new Date().toString(), + })); + + setMessage(""); + } + } + + return ( + <> +
InputContainer
+
submitHandler(e)}> + setMessage(e.target.value)} value={message}/> + +
+ + + ) +} + +export default InputContainer \ No newline at end of file diff --git a/src/container/MessageContainer.tsx b/src/container/MessageContainer.tsx new file mode 100644 index 0000000..550f292 --- /dev/null +++ b/src/container/MessageContainer.tsx @@ -0,0 +1,19 @@ +import React from 'react' +import { useAppSelector } from '../hooks' +import { Message } from '../state/types' + +function MessageContainer() { + + const messages = useAppSelector((state)=> state.message.messages) + + return ( + <> +
MessageContainer
+ {messages.map((message: Message)=>( +

{message.content}

+ ))} + + ) +} + +export default MessageContainer \ No newline at end of file diff --git a/src/container/ProfileContainer.tsx b/src/container/ProfileContainer.tsx new file mode 100644 index 0000000..d5acc2a --- /dev/null +++ b/src/container/ProfileContainer.tsx @@ -0,0 +1,9 @@ +import React from 'react' + +function ProfileContainer() { + return ( +
ProfileContainer
+ ) +} + +export default ProfileContainer \ 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/index.js b/src/index.tsx similarity index 64% rename from src/index.js rename to src/index.tsx index 593edf1..058aece 100644 --- a/src/index.js +++ b/src/index.tsx @@ -1,10 +1,14 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; +import { Provider } from 'react-redux'; +import store from './state/store'; -const root = ReactDOM.createRoot(document.getElementById('root')); +const root = ReactDOM.createRoot(document.getElementById('root')!); root.render( + + ); diff --git a/src/state/slices.ts b/src/state/slices.ts new file mode 100644 index 0000000..b7ddb3e --- /dev/null +++ b/src/state/slices.ts @@ -0,0 +1,20 @@ +import {createSlice, PayloadAction} from "@reduxjs/toolkit" +import { MessageState, Message } from "./types" + + +const messageState: MessageState = { + messages: [] +}; + +const messageSlice = createSlice({ + name: "message", + initialState: messageState, + reducers: { + addMessage(state, action: PayloadAction) { + state.messages.push(action.payload) + }, + } +}) + +export const messageReducer = messageSlice.reducer +export const {addMessage} = messageSlice.actions diff --git a/src/state/store.ts b/src/state/store.ts new file mode 100644 index 0000000..faf1d2a --- /dev/null +++ b/src/state/store.ts @@ -0,0 +1,12 @@ +import { configureStore } from "@reduxjs/toolkit"; +import { messageReducer } from "./slices"; + +const store = configureStore({ + reducer: { + message: messageReducer, + }} +) + +export default store; +export type RootState = ReturnType; +export type AppDispatch = typeof store.dispatch; \ No newline at end of file diff --git a/src/state/types.ts b/src/state/types.ts new file mode 100644 index 0000000..f43f35b --- /dev/null +++ b/src/state/types.ts @@ -0,0 +1,10 @@ +export type Message = { + msgId: string, + ownerId: number, + content: string, + time: string, +} + +export type MessageState = { + messages: Message[] +}; \ No newline at end of file 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 From 88d174cb8d7d220582e359512c4ad3e114e5f880 Mon Sep 17 00:00:00 2001 From: Daeheon Jeong Date: Sat, 7 May 2022 11:42:20 +0900 Subject: [PATCH 02/10] =?UTF-8?q?feat=20:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=84=A0=ED=83=9D,=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=A0=84?= =?UTF-8?q?=EC=86=A1,=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=EB=B2=84=EB=B8=94?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 207 +++++++++++++++++++++++ package.json | 2 + src/App.tsx | 22 ++- src/GlobalStyle.tsx | 26 +++ src/container/InputContainer.tsx | 38 ----- src/container/MessageContainer.tsx | 19 --- src/container/ProfileContainer.tsx | 9 - src/containers/InputContainer.tsx | 100 +++++++++++ src/containers/MessageContainer.tsx | 112 ++++++++++++ src/containers/ProfileContainer.tsx | 82 +++++++++ src/images/cat-face.png | Bin 0 -> 27768 bytes src/images/dog-face.png | Bin 0 -> 28419 bytes src/index.tsx | 4 +- src/jsons/clients.json | 0 src/jsons/messages.json | 0 src/state/clientSlice.ts | 38 +++++ src/state/{slices.ts => messageSlice.ts} | 2 + src/state/store.ts | 4 +- src/state/types.ts | 15 +- 19 files changed, 606 insertions(+), 74 deletions(-) create mode 100644 src/GlobalStyle.tsx delete mode 100644 src/container/InputContainer.tsx delete mode 100644 src/container/MessageContainer.tsx delete mode 100644 src/container/ProfileContainer.tsx create mode 100644 src/containers/InputContainer.tsx create mode 100644 src/containers/MessageContainer.tsx create mode 100644 src/containers/ProfileContainer.tsx create mode 100644 src/images/cat-face.png create mode 100644 src/images/dog-face.png create mode 100644 src/jsons/clients.json create mode 100644 src/jsons/messages.json create mode 100644 src/state/clientSlice.ts rename src/state/{slices.ts => messageSlice.ts} (92%) diff --git a/package-lock.json b/package-lock.json index 990fcbb..8008d25 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,11 +24,13 @@ "react-redux": "^8.0.1", "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" } }, @@ -1987,6 +1989,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", @@ -3750,6 +3775,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", @@ -4789,6 +4825,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", @@ -5103,6 +5159,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", @@ -5569,6 +5630,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", @@ -5750,6 +5819,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", @@ -14336,6 +14415,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", @@ -14733,6 +14817,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", @@ -17687,6 +17801,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", @@ -18997,6 +19134,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", @@ -19749,6 +19897,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", @@ -19994,6 +20159,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", @@ -20349,6 +20519,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", @@ -20456,6 +20631,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", @@ -26537,6 +26722,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", @@ -26836,6 +27026,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", diff --git a/package.json b/package.json index 8506f72..b44991d 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "react-redux": "^8.0.1", "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" @@ -48,6 +49,7 @@ ] }, "devDependencies": { + "@types/styled-components": "^5.1.25", "@types/uuid": "^8.3.4" } } diff --git a/src/App.tsx b/src/App.tsx index 892c084..f0e3536 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,12 +1,26 @@ import React from "react"; -import InputContainer from "./container/InputContainer"; -import MessageContainer from "./container/MessageContainer"; +import InputContainer from "./containers/InputContainer"; +import MessageContainer from "./containers/MessageContainer"; +import styled from "styled-components"; +import ProfileContainer from "./containers/ProfileContainer"; + + +const AppContainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-between; + height: 100vh; + font-family: var(--ff-main); +`; function App() { - return <>
15κΈ° νŒŒμ΄νŒ… πŸ’–
+ return ( + + - ; + ); } export default App; diff --git a/src/GlobalStyle.tsx b/src/GlobalStyle.tsx new file mode 100644 index 0000000..5469a01 --- /dev/null +++ b/src/GlobalStyle.tsx @@ -0,0 +1,26 @@ +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; + } +`; diff --git a/src/container/InputContainer.tsx b/src/container/InputContainer.tsx deleted file mode 100644 index 9cb5977..0000000 --- a/src/container/InputContainer.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import React, {useState} from 'react' -import {v4 as uuidv4} from "uuid"; -import { useAppSelector, useAppDispatch } from '../hooks'; -import { addMessage } from '../state/slices'; - -function InputContainer() { - const [message, setMessage] = useState(""); - const dispatch = useAppDispatch(); - - const submitHandler = (e:React.FormEvent) => { - e.preventDefault(); - - if(message != ""){ - - dispatch(addMessage({ - msgId: uuidv4(), - ownerId: 0, - content: message, - time: new Date().toString(), - })); - - setMessage(""); - } - } - - return ( - <> -
InputContainer
-
submitHandler(e)}> - setMessage(e.target.value)} value={message}/> - -
- - - ) -} - -export default InputContainer \ No newline at end of file diff --git a/src/container/MessageContainer.tsx b/src/container/MessageContainer.tsx deleted file mode 100644 index 550f292..0000000 --- a/src/container/MessageContainer.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react' -import { useAppSelector } from '../hooks' -import { Message } from '../state/types' - -function MessageContainer() { - - const messages = useAppSelector((state)=> state.message.messages) - - return ( - <> -
MessageContainer
- {messages.map((message: Message)=>( -

{message.content}

- ))} - - ) -} - -export default MessageContainer \ No newline at end of file diff --git a/src/container/ProfileContainer.tsx b/src/container/ProfileContainer.tsx deleted file mode 100644 index d5acc2a..0000000 --- a/src/container/ProfileContainer.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react' - -function ProfileContainer() { - return ( -
ProfileContainer
- ) -} - -export default ProfileContainer \ No newline at end of file diff --git a/src/containers/InputContainer.tsx b/src/containers/InputContainer.tsx new file mode 100644 index 0000000..b34048e --- /dev/null +++ b/src/containers/InputContainer.tsx @@ -0,0 +1,100 @@ +import React, {useState} from 'react' +import {v4 as uuidv4} from "uuid"; +import { useAppSelector, useAppDispatch } from '../hooks'; +import { addMessage } from '../state/messageSlice'; +import styled from 'styled-components'; +import { Client, Message } from '../state/types'; + +const Container = styled.div` + width: 90vw; + height: 4rem; + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 5vh; + z-index: 1; +` + +const Form = styled.form` + width: 90vw; + max-width: 45rem; + 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; +` +function InputContainer() { + const [message, setMessage] = useState(""); + + const {clients, currentClient} = useAppSelector((state)=>state.client); + const dispatch = useAppDispatch(); + + const submitHandler = (message: string, e?:React.FormEvent) => { + + if(e) e.preventDefault(); + if(message !== "" && currentClient){ + + dispatch(addMessage({ + msgId: uuidv4(), + client: currentClient, + content: message, + time: new Date().toString(), + })); + + setMessage(""); + }else if(message === ""){ + alert("enter the message") + }else{ + alert("select the profile") + } + } + + return ( + +
submitHandler(message, e)}> + + setMessage(e.target.value)} value={message}/> + +
+
+ + ) +} + +export default InputContainer \ No newline at end of file diff --git a/src/containers/MessageContainer.tsx b/src/containers/MessageContainer.tsx new file mode 100644 index 0000000..c3b24e1 --- /dev/null +++ b/src/containers/MessageContainer.tsx @@ -0,0 +1,112 @@ +import React, {useEffect} from 'react' +import { useAppSelector, useAppDispatch } from '../hooks' +import { Message } from '../state/types' +import styled from 'styled-components' + +const Container = styled.div` + width: 90vw; + margin-top: 15vh; + padding: 5vh 0 5vh 0; + height: 75vh; + z-index: 0; + max-width: 45rem; + position: absolute; + overflow: scroll; + &::-webkit-scrollbar { + display:none; + } +` + +const MessageDiv = styled.div<{selected: boolean}>` + width: 100%; + display: flex; + flex-direction: ${(props)=>(props.selected? "row-reverse":"row")}; + padding: 0.5rem 1rem 0.5rem 1rem; + +` + +const MessageProfileImg = styled.img` + width: 3rem; + height: 3rem; +` +const MessageContentDiv = styled.div` + width: 100%; + margin-left: 1rem; + margin-right: 1rem; +` +const Name = styled.p<{selected: boolean}>` + font-size: 0.8rem; + text-align: ${(props)=>(props.selected? "right":"left")}; +` + +const MessageHolderDiv = styled.div<{selected: boolean}>` + width: 100%; + display: flex; + flex-direction: ${(props)=>(props.selected? "row-reverse":"row")}; + align-items: flex-end; +` + +const MessageBubbleDiv = styled.div<{selected: boolean}>` + max-width: calc(100% - 4rem); + height: 100%; + background-color: ${(props)=>(props.selected? "#e07800":"#cbf542")}; + border-radius: 1rem; + padding: 0.8rem; + margin-top: 0.2rem; + color: ${(props)=>(props.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}: {msg:Message, timeString:string, selected:boolean}) => { + return( + + + + {msg.client.name} + +

{msg.content}

+ +
+
+
+ ) +} + +function MessageContainer() { + + const messages = useAppSelector((state)=> state.message.messages); + const {clients, currentClient} = useAppSelector((state)=>state.client); + const containerRef = React.useRef(null); + + useEffect( + ()=>{ + if(containerRef.current){ + containerRef.current.scrollTop = containerRef.current.scrollHeight + } + } + ); + + return ( + + {messages.map((message: Message)=>{ + const date: Date = new Date(message.time); + const hourString: string = date.getHours().toString() + const minString: string = date.getMinutes().toString() + const timeString: string = (hourString.length == 2? hourString : "0" + hourString) + ": " + (minString.length == 2? minString : "0" + minString) + const selected: boolean = message.client.clientId === currentClient?.clientId; + + 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..0f62b52 --- /dev/null +++ b/src/containers/ProfileContainer.tsx @@ -0,0 +1,82 @@ +import React from 'react' +import styled from 'styled-components' +import { useAppDispatch, useAppSelector } from '../hooks' +import { setClient } from '../state/clientSlice' + +const Container = styled.div` + width: 50vw; + height: 8rem; + max-width: 30rem; + 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, ${(props)=>(props.selected? "1":"0")}); + box-shadow: 0px 0px 5px rgba(255, 255, 255, 1); + color: rgba(150, 150, 150, 1); + margin-top: 0.6rem; + padding: 0 1rem 0 1rem; + text-align: center; +` + +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, ${(props)=>(props.selected? "0.5":"0")}); + z-index: 1; +` +const ProfileIamge = styled.img` + border-radius: inherit; + width: 80%; + height: 80%; + display: block; + margin-left: auto; + margin-right: auto; +` + +function ProfileContainer() { + const {clients, currentClient} = useAppSelector((state)=> state.client) + const dispatch = useAppDispatch(); + + return ( + + {clients.map(client=>{ + const selected = client.clientId === currentClient?.clientId? true : false + return( + + { + dispatch(setClient(client)) + }}> + {client.name} + + ) + })} + + ) +} + +export default ProfileContainer \ 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 0000000000000000000000000000000000000000..b9e5176f7ee5cbd86c929bc873e42d9d77dec879 GIT binary patch literal 27768 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