From 98517f7e9453511874379adc5d6196b052567bdd Mon Sep 17 00:00:00 2001 From: dhsin98 Date: Thu, 28 Sep 2023 16:22:01 +0900 Subject: [PATCH 01/21] =?UTF-8?q?[feat]:=20=ED=94=84=EB=A1=9C=EC=A0=9D?= =?UTF-8?q?=ED=8A=B8=20=EC=B4=88=EA=B8=B0=20=EC=84=B8=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 194 +++++++++++++----------------- package.json | 7 ++ src/App.js | 25 ---- src/{App.test.js => App.test.tsx} | 0 src/App.tsx | 17 +++ src/custom.d.tsx | 6 + src/logo.svg | 1 - src/static/images/App-Store.svg | 3 + src/static/images/Camera.svg | 3 + src/static/images/Dictation.svg | 3 + src/static/images/Friends.svg | 7 ++ src/static/images/Group.svg | 3 + src/static/images/Icon.svg | 3 + src/static/images/Vector (1).svg | 3 + src/static/images/Vector.svg | 3 + tsconfig.json | 26 ++++ 16 files changed, 166 insertions(+), 138 deletions(-) delete mode 100644 src/App.js rename src/{App.test.js => App.test.tsx} (100%) create mode 100644 src/App.tsx create mode 100644 src/custom.d.tsx delete mode 100644 src/logo.svg create mode 100644 src/static/images/App-Store.svg create mode 100644 src/static/images/Camera.svg create mode 100644 src/static/images/Dictation.svg create mode 100644 src/static/images/Friends.svg create mode 100644 src/static/images/Group.svg create mode 100644 src/static/images/Icon.svg create mode 100644 src/static/images/Vector (1).svg create mode 100644 src/static/images/Vector.svg create mode 100644 tsconfig.json diff --git a/package-lock.json b/package-lock.json index 82a715f..c9da98b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,9 +11,16 @@ "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "@types/jest": "^29.5.5", + "@types/node": "^20.7.1", + "@types/react": "^18.2.23", + "@types/react-dom": "^18.2.8", + "@types/react-router-dom": "^5.3.3", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-router-dom": "^6.16.0", "react-scripts": "5.0.1", + "typescript": "^5.2.2", "web-vitals": "^2.1.4" } }, @@ -3241,6 +3248,14 @@ } } }, + "node_modules/@remix-run/router": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.9.0.tgz", + "integrity": "sha512-bV63itrKBC0zdT27qYm6SDZHlkXwFL1xMBuhkn+X7l0+IIhNaH5wuuvZKp6eKhCD4KFhujhfhCT1YxXW6esUIA==", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -3559,104 +3574,6 @@ "url": "https://github.com/sponsors/gregberge" } }, - "node_modules/@testing-library/dom": { - "version": "9.3.3", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.3.tgz", - "integrity": "sha512-fB0R+fa3AUqbLHWyxXa2kGVtf1Fe1ZZFr0Zp6AIbIAzXb2mKbEXl+PCQNUOaq5lbTab5tfctfXRNsWXxa2f7Aw==", - "peer": true, - "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^5.0.1", - "aria-query": "5.1.3", - "chalk": "^4.1.0", - "dom-accessibility-api": "^0.5.9", - "lz-string": "^1.5.0", - "pretty-format": "^27.0.2" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@testing-library/dom/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@testing-library/dom/node_modules/aria-query": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", - "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", - "peer": true, - "dependencies": { - "deep-equal": "^2.0.5" - } - }, - "node_modules/@testing-library/dom/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@testing-library/dom/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@testing-library/dom/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "peer": true - }, - "node_modules/@testing-library/dom/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@testing-library/dom/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@testing-library/jest-dom": { "version": "5.17.0", "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.17.0.tgz", @@ -4006,6 +3923,11 @@ "@types/node": "*" } }, + "node_modules/@types/history": { + "version": "4.7.11", + "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", + "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==" + }, "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", @@ -4297,9 +4219,9 @@ "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" }, "node_modules/@types/node": { - "version": "20.6.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.6.4.tgz", - "integrity": "sha512-nU6d9MPY0NBUMiE/nXd2IIoC4OLvsLpwAjheoAeuzgvDZA1Cb10QYg+91AF6zQiKWRN5i1m07x6sMe0niBznoQ==" + "version": "20.7.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.7.1.tgz", + "integrity": "sha512-LT+OIXpp2kj4E2S/p91BMe+VgGX2+lfO+XTpfXhh+bCk2LkQtHZSub8ewFBMGP5ClysPjTDFa4sMI8Q3n4T0wg==" }, "node_modules/@types/parse-json": { "version": "4.0.0", @@ -4332,9 +4254,9 @@ "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" }, "node_modules/@types/react": { - "version": "18.2.22", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.22.tgz", - "integrity": "sha512-60fLTOLqzarLED2O3UQImc/lsNRgG0jE/a1mPW9KjMemY0LMITWEsbS4VvZ4p6rorEHd5YKxxmMKSDK505GHpA==", + "version": "18.2.23", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.23.tgz", + "integrity": "sha512-qHLW6n1q2+7KyBEYnrZpcsAmU/iiCh9WGCKgXvMxx89+TYdJWRjZohVIo9XTcoLhfX3+/hP0Pbulu3bCZQ9PSA==", "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -4342,13 +4264,32 @@ } }, "node_modules/@types/react-dom": { - "version": "18.2.7", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.7.tgz", - "integrity": "sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==", + "version": "18.2.8", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.8.tgz", + "integrity": "sha512-bAIvO5lN/U8sPGvs1Xm61rlRHHaq5rp5N3kp9C+NJ/Q41P8iqjkXSu0+/qu8POsjH9pNWb0OYabFez7taP7omw==", "dependencies": { "@types/react": "*" } }, + "node_modules/@types/react-router": { + "version": "5.1.20", + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz", + "integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*" + } + }, + "node_modules/@types/react-router-dom": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", + "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router": "*" + } + }, "node_modules/@types/resolve": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", @@ -14686,6 +14627,36 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.16.0.tgz", + "integrity": "sha512-VT4Mmc4jj5YyjpOi5jOf0I+TYzGpvzERy4ckNSvSh2RArv8LLoCxlsZ2D+tc7zgjxcY34oTz2hZaeX5RVprKqA==", + "dependencies": { + "@remix-run/router": "1.9.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.16.0.tgz", + "integrity": "sha512-aTfBLv3mk/gaKLxgRDUPbPw+s4Y/O+ma3rEN1u8EgEpLpPe6gNjIsWt9rxushMHHMb7mSwxRGdGlGdvmFsyPIg==", + "dependencies": { + "@remix-run/router": "1.9.0", + "react-router": "6.16.0" + }, + "engines": { + "node": ">=14.0.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", @@ -16653,16 +16624,15 @@ } }, "node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "peer": true, + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=14.17" } }, "node_modules/unbox-primitive": { diff --git a/package.json b/package.json index 49b3308..cee69c3 100644 --- a/package.json +++ b/package.json @@ -6,9 +6,16 @@ "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "@types/jest": "^29.5.5", + "@types/node": "^20.7.1", + "@types/react": "^18.2.23", + "@types/react-dom": "^18.2.8", + "@types/react-router-dom": "^5.3.3", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-router-dom": "^6.16.0", "react-scripts": "5.0.1", + "typescript": "^5.2.2", "web-vitals": "^2.1.4" }, "scripts": { diff --git a/src/App.js b/src/App.js deleted file mode 100644 index 3784575..0000000 --- a/src/App.js +++ /dev/null @@ -1,25 +0,0 @@ -import logo from './logo.svg'; -import './App.css'; - -function App() { - return ( -
-
- logo -

- Edit src/App.js and save to reload. -

- - Learn React - -
-
- ); -} - -export default App; diff --git a/src/App.test.js b/src/App.test.tsx similarity index 100% rename from src/App.test.js rename to src/App.test.tsx diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..b9c0434 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,17 @@ +import { BrowserRouter, Routes, Route } from "react-router-dom"; +import { useNavigate } from "react-router-dom"; +import Camera from "./static/images/Camera.svg" +function App() { const navigate = useNavigate(); + return ( +
+
+ + + + +
+
+ ); +} + +export default App; diff --git a/src/custom.d.tsx b/src/custom.d.tsx new file mode 100644 index 0000000..b528b80 --- /dev/null +++ b/src/custom.d.tsx @@ -0,0 +1,6 @@ +// 이미지 import 위한 확장자 설정 +declare module "*.jpg"; +declare module "*.png"; +declare module "*.jpeg"; +declare module "*.gif"; +declare module "*.svg"; diff --git a/src/logo.svg b/src/logo.svg deleted file mode 100644 index 9dfc1c0..0000000 --- a/src/logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/static/images/App-Store.svg b/src/static/images/App-Store.svg new file mode 100644 index 0000000..c0855ad --- /dev/null +++ b/src/static/images/App-Store.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/static/images/Camera.svg b/src/static/images/Camera.svg new file mode 100644 index 0000000..916dbf3 --- /dev/null +++ b/src/static/images/Camera.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/static/images/Dictation.svg b/src/static/images/Dictation.svg new file mode 100644 index 0000000..7e30da1 --- /dev/null +++ b/src/static/images/Dictation.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/static/images/Friends.svg b/src/static/images/Friends.svg new file mode 100644 index 0000000..dc73793 --- /dev/null +++ b/src/static/images/Friends.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/static/images/Group.svg b/src/static/images/Group.svg new file mode 100644 index 0000000..f04ec64 --- /dev/null +++ b/src/static/images/Group.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/static/images/Icon.svg b/src/static/images/Icon.svg new file mode 100644 index 0000000..41cece8 --- /dev/null +++ b/src/static/images/Icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/static/images/Vector (1).svg b/src/static/images/Vector (1).svg new file mode 100644 index 0000000..d41faaf --- /dev/null +++ b/src/static/images/Vector (1).svg @@ -0,0 +1,3 @@ + + + diff --git a/src/static/images/Vector.svg b/src/static/images/Vector.svg new file mode 100644 index 0000000..225d492 --- /dev/null +++ b/src/static/images/Vector.svg @@ -0,0 +1,3 @@ + + + diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..18d86a1 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": [ + "src" + ] + } \ No newline at end of file From 2cb0c6f1200148cb4a44a16ab8939ea0882f7245 Mon Sep 17 00:00:00 2001 From: dhsin98 Date: Fri, 29 Sep 2023 01:59:22 +0900 Subject: [PATCH 02/21] =?UTF-8?q?[feat]:=20status=20bar=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 269 ++++++++++++++++++ package.json | 3 + src/App.tsx | 17 +- src/{static => assets}/images/App-Store.svg | 0 src/assets/images/Battery.svg | 5 + src/{static => assets}/images/Camera.svg | 0 src/assets/images/Cellular Connection.svg | 3 + src/{static => assets}/images/Dictation.svg | 0 src/assets/images/Edit.svg | 3 + src/{static => assets}/images/Friends.svg | 0 src/{static => assets}/images/Icon.svg | 0 src/assets/images/LightBottomBar.svg | 3 + src/assets/images/LightStatusBar.svg | 8 + src/assets/images/Message Numbers.svg | 4 + src/assets/images/Time Style.svg | 3 + src/assets/images/Time.svg | 3 + .../Group.svg => assets/images/User.svg} | 0 src/{static => assets}/images/Vector (1).svg | 0 src/{static => assets}/images/Vector.svg | 0 src/assets/images/Wifi.svg | 3 + src/assets/images/back.svg | 3 + src/assets/images/video.svg | 3 + src/assets/images/write.svg | 3 + src/components/StatusBar/statusbar.tsx | 56 ++++ src/components/TopBar/topbar.tsx | 7 + src/index.css | 8 +- src/index.js | 17 -- src/index.tsx | 22 ++ src/pages/chat/chat.tsx | 10 + src/styles/colors.css | 7 + src/styles/globalStyle.ts | 30 ++ 31 files changed, 459 insertions(+), 31 deletions(-) rename src/{static => assets}/images/App-Store.svg (100%) create mode 100644 src/assets/images/Battery.svg rename src/{static => assets}/images/Camera.svg (100%) create mode 100644 src/assets/images/Cellular Connection.svg rename src/{static => assets}/images/Dictation.svg (100%) create mode 100644 src/assets/images/Edit.svg rename src/{static => assets}/images/Friends.svg (100%) rename src/{static => assets}/images/Icon.svg (100%) create mode 100644 src/assets/images/LightBottomBar.svg create mode 100644 src/assets/images/LightStatusBar.svg create mode 100644 src/assets/images/Message Numbers.svg create mode 100644 src/assets/images/Time Style.svg create mode 100644 src/assets/images/Time.svg rename src/{static/images/Group.svg => assets/images/User.svg} (100%) rename src/{static => assets}/images/Vector (1).svg (100%) rename src/{static => assets}/images/Vector.svg (100%) create mode 100644 src/assets/images/Wifi.svg create mode 100644 src/assets/images/back.svg create mode 100644 src/assets/images/video.svg create mode 100644 src/assets/images/write.svg create mode 100644 src/components/StatusBar/statusbar.tsx create mode 100644 src/components/TopBar/topbar.tsx delete mode 100644 src/index.js create mode 100644 src/index.tsx create mode 100644 src/pages/chat/chat.tsx create mode 100644 src/styles/colors.css create mode 100644 src/styles/globalStyle.ts diff --git a/package-lock.json b/package-lock.json index c9da98b..3d83b6f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,10 +16,13 @@ "@types/react": "^18.2.23", "@types/react-dom": "^18.2.8", "@types/react-router-dom": "^5.3.3", + "@types/styled-components": "^5.1.28", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.16.0", "react-scripts": "5.0.1", + "styled-components": "^6.0.8", + "styled-reset": "^4.5.1", "typescript": "^5.2.2", "web-vitals": "^2.1.4" } @@ -60,6 +63,83 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/cli": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.23.0.tgz", + "integrity": "sha512-17E1oSkGk2IwNILM4jtfAvgjt+ohmpfBky8aLerUfYZhiPNg7ca+CRCxZn8QDxwNhV/upsc2VHBCqGFIR+iBfA==", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.17", + "commander": "^4.0.1", + "convert-source-map": "^2.0.0", + "fs-readdir-recursive": "^1.1.0", + "glob": "^7.2.0", + "make-dir": "^2.1.0", + "slash": "^2.0.0" + }, + "bin": { + "babel": "bin/babel.js", + "babel-external-helpers": "bin/babel-external-helpers.js" + }, + "engines": { + "node": ">=6.9.0" + }, + "optionalDependencies": { + "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.3", + "chokidar": "^3.4.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/cli/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/@babel/cli/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" + }, + "node_modules/@babel/cli/node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/cli/node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/cli/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/@babel/cli/node_modules/slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "engines": { + "node": ">=6" + } + }, "node_modules/@babel/code-frame": { "version": "7.22.13", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", @@ -537,6 +617,20 @@ "@babel/core": "^7.13.0" } }, + "node_modules/@babel/plugin-external-helpers": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-external-helpers/-/plugin-external-helpers-7.22.5.tgz", + "integrity": "sha512-ngnNEWxmykPk82mH4ajZT0qTztr3Je6hrMuKAslZVM8G1YZTENJSYwrIGtt6KOtznug3exmAtF4so/nPqJuA4A==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-proposal-class-properties": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", @@ -603,6 +697,25 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-proposal-object-rest-spread": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", + "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead.", + "dependencies": { + "@babel/compat-data": "^7.20.5", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-proposal-optional-chaining": { "version": "7.21.0", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz", @@ -2277,6 +2390,24 @@ "postcss-selector-parser": "^6.0.10" } }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz", + "integrity": "sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==", + "dependencies": { + "@emotion/memoize": "^0.8.1" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + }, + "node_modules/@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -3139,6 +3270,12 @@ "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==" }, + "node_modules/@nicolo-ribaudo/chokidar-2": { + "version": "2.1.8-no-fsevents.3", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz", + "integrity": "sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==", + "optional": true + }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", @@ -3928,6 +4065,15 @@ "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==" }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-YIQtIg4PKr7ZyqNPZObpxfHsHEmuB8dXCxd6qVcGuQVDK2bpsF7bYNnBJ4Nn7giuACZg+WewExgrtAJ3XnA4Xw==", + "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", @@ -4353,6 +4499,21 @@ "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.28", + "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.28.tgz", + "integrity": "sha512-nu0VKNybkjvUqJAXWtRqKd7j3iRUl8GbYSTvZNuIBJcw/HUp1Y4QUXNLlj7gcnRV/t784JnHAlvRnSnE3nPbJA==", + "dependencies": { + "@types/hoist-non-react-statics": "*", + "@types/react": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/stylis": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.1.tgz", + "integrity": "sha512-OSaMrXUKxVigGlKRrET39V2xdhzlztQ9Aqumn1WbCBKHOi9ry7jKSd7rkyj0GzmWaU960Rd+LpOFpLfx5bMQAg==" + }, "node_modules/@types/testing-library__jest-dom": { "version": "5.14.9", "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.9.tgz", @@ -5767,6 +5928,14 @@ "node": ">= 6" } }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", @@ -6202,6 +6371,14 @@ "postcss": "^8.4" } }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "engines": { + "node": ">=4" + } + }, "node_modules/css-declaration-sorter": { "version": "6.4.1", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz", @@ -6383,6 +6560,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.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "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", @@ -8490,6 +8677,11 @@ "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.4.tgz", "integrity": "sha512-INM/fWAxMICjttnD0DX1rBvinKskj5G1w+oy/pnm9u/tSlnBrzFonJMcalKJ30P8RRsPzKcCG7Q8l0jx5Fh9YQ==" }, + "node_modules/fs-readdir-recursive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", + "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==" + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -8845,6 +9037,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", @@ -15484,6 +15689,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", @@ -15980,6 +16190,60 @@ "webpack": "^5.0.0" } }, + "node_modules/styled-components": { + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.0.8.tgz", + "integrity": "sha512-AwI02MTWZwqjzfXgR5QcbmcSn5xVjY4N2TLjSuYnmuBGF3y7GicHz3ysbpUq2EMJP5M8/Nc22vcmF3V3WNZDFA==", + "dependencies": { + "@babel/cli": "^7.21.0", + "@babel/core": "^7.21.0", + "@babel/helper-module-imports": "^7.18.6", + "@babel/plugin-external-helpers": "^7.18.6", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-proposal-object-rest-spread": "^7.20.7", + "@babel/preset-env": "^7.20.2", + "@babel/preset-react": "^7.18.6", + "@babel/preset-typescript": "^7.21.0", + "@babel/traverse": "^7.21.2", + "@emotion/is-prop-valid": "^1.2.1", + "@emotion/unitless": "^0.8.0", + "@types/stylis": "^4.0.2", + "css-to-react-native": "^3.2.0", + "csstype": "^3.1.2", + "postcss": "^8.4.23", + "shallowequal": "^1.1.0", + "stylis": "^4.3.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "babel-plugin-styled-components": ">= 2", + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0" + }, + "peerDependenciesMeta": { + "babel-plugin-styled-components": { + "optional": true + } + } + }, + "node_modules/styled-reset": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/styled-reset/-/styled-reset-4.5.1.tgz", + "integrity": "sha512-6EvFWZRwaFRFxiPYMwmnzOe33rDkw5r9jIU0eEi49bkt6VSrvjeMp2ZOw/YFbw5SVs81llIY+5fzHtR2/VBZfQ==", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "styled-components": ">=4.0.0 || >=5.0.0 || >=6.0.0" + } + }, "node_modules/stylehacks": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", @@ -15995,6 +16259,11 @@ "postcss": "^8.2.15" } }, + "node_modules/stylis": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.0.tgz", + "integrity": "sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ==" + }, "node_modules/sucrase": { "version": "3.34.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz", diff --git a/package.json b/package.json index cee69c3..3cb8e36 100644 --- a/package.json +++ b/package.json @@ -11,10 +11,13 @@ "@types/react": "^18.2.23", "@types/react-dom": "^18.2.8", "@types/react-router-dom": "^5.3.3", + "@types/styled-components": "^5.1.28", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.16.0", "react-scripts": "5.0.1", + "styled-components": "^6.0.8", + "styled-reset": "^4.5.1", "typescript": "^5.2.2", "web-vitals": "^2.1.4" }, diff --git a/src/App.tsx b/src/App.tsx index b9c0434..c6479fe 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,17 +1,8 @@ import { BrowserRouter, Routes, Route } from "react-router-dom"; -import { useNavigate } from "react-router-dom"; -import Camera from "./static/images/Camera.svg" -function App() { const navigate = useNavigate(); - return ( -
-
- - - - -
-
- ); +// import { useNavigate } from "react-router-dom"; +import Chat from "./pages/chat/chat"; +function App() { + return ; } export default App; diff --git a/src/static/images/App-Store.svg b/src/assets/images/App-Store.svg similarity index 100% rename from src/static/images/App-Store.svg rename to src/assets/images/App-Store.svg diff --git a/src/assets/images/Battery.svg b/src/assets/images/Battery.svg new file mode 100644 index 0000000..4a45d70 --- /dev/null +++ b/src/assets/images/Battery.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/static/images/Camera.svg b/src/assets/images/Camera.svg similarity index 100% rename from src/static/images/Camera.svg rename to src/assets/images/Camera.svg diff --git a/src/assets/images/Cellular Connection.svg b/src/assets/images/Cellular Connection.svg new file mode 100644 index 0000000..ebe9186 --- /dev/null +++ b/src/assets/images/Cellular Connection.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/static/images/Dictation.svg b/src/assets/images/Dictation.svg similarity index 100% rename from src/static/images/Dictation.svg rename to src/assets/images/Dictation.svg diff --git a/src/assets/images/Edit.svg b/src/assets/images/Edit.svg new file mode 100644 index 0000000..ed9701c --- /dev/null +++ b/src/assets/images/Edit.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/static/images/Friends.svg b/src/assets/images/Friends.svg similarity index 100% rename from src/static/images/Friends.svg rename to src/assets/images/Friends.svg diff --git a/src/static/images/Icon.svg b/src/assets/images/Icon.svg similarity index 100% rename from src/static/images/Icon.svg rename to src/assets/images/Icon.svg diff --git a/src/assets/images/LightBottomBar.svg b/src/assets/images/LightBottomBar.svg new file mode 100644 index 0000000..2553c20 --- /dev/null +++ b/src/assets/images/LightBottomBar.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/LightStatusBar.svg b/src/assets/images/LightStatusBar.svg new file mode 100644 index 0000000..24e6034 --- /dev/null +++ b/src/assets/images/LightStatusBar.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/assets/images/Message Numbers.svg b/src/assets/images/Message Numbers.svg new file mode 100644 index 0000000..6fe3f42 --- /dev/null +++ b/src/assets/images/Message Numbers.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/images/Time Style.svg b/src/assets/images/Time Style.svg new file mode 100644 index 0000000..4f4cd9d --- /dev/null +++ b/src/assets/images/Time Style.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/Time.svg b/src/assets/images/Time.svg new file mode 100644 index 0000000..4c781ef --- /dev/null +++ b/src/assets/images/Time.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/static/images/Group.svg b/src/assets/images/User.svg similarity index 100% rename from src/static/images/Group.svg rename to src/assets/images/User.svg diff --git a/src/static/images/Vector (1).svg b/src/assets/images/Vector (1).svg similarity index 100% rename from src/static/images/Vector (1).svg rename to src/assets/images/Vector (1).svg diff --git a/src/static/images/Vector.svg b/src/assets/images/Vector.svg similarity index 100% rename from src/static/images/Vector.svg rename to src/assets/images/Vector.svg diff --git a/src/assets/images/Wifi.svg b/src/assets/images/Wifi.svg new file mode 100644 index 0000000..c00f1c7 --- /dev/null +++ b/src/assets/images/Wifi.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/back.svg b/src/assets/images/back.svg new file mode 100644 index 0000000..cd3366b --- /dev/null +++ b/src/assets/images/back.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/video.svg b/src/assets/images/video.svg new file mode 100644 index 0000000..0071623 --- /dev/null +++ b/src/assets/images/video.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/write.svg b/src/assets/images/write.svg new file mode 100644 index 0000000..2f6a79a --- /dev/null +++ b/src/assets/images/write.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/StatusBar/statusbar.tsx b/src/components/StatusBar/statusbar.tsx new file mode 100644 index 0000000..0b20eb0 --- /dev/null +++ b/src/components/StatusBar/statusbar.tsx @@ -0,0 +1,56 @@ +import styled from "styled-components"; +import timeIcon from "../../assets/images/Time Style.svg"; +import cellularIcon from "../../assets/images/Cellular Connection.svg"; +import wifiIcon from "../../assets/images/Wifi.svg"; +import batteryIcon from "../../assets/images/Battery.svg"; + +const StatusBar = () => { + return ( + + + + + + + + + ); +}; + +const StatusBarContainer = styled.div` + width: 23.4375rem; + height: 2.75rem; + /* width: 100vw; + height: 5vh; */ + position: fixed; +`; +const TimeIcon = styled.img` + width: 3.375rem; + padding: 0.69rem 0 1.31rem 0.81rem; + right: 16.33rem; +`; + +const CellularIcon = styled.img` + width: 1.0625rem; + height: 0.66669rem; +`; +const WifiIcon = styled.img` + width: 0.95831rem; + height: 0.6875rem; +`; +const BatteryIcon = styled.img` + width: 1.5205rem; + height: 0.70831rem; +`; + +const StatusBarRight = styled.div` + position: absolute; + display: inline-flex; + align-items: center; + gap: 0.31rem; + left: 18.37rem; + right: 0.9rem; + top: 1.08rem; + bottom: 0.96rem; +`; +export default StatusBar; diff --git a/src/components/TopBar/topbar.tsx b/src/components/TopBar/topbar.tsx new file mode 100644 index 0000000..bcb1a93 --- /dev/null +++ b/src/components/TopBar/topbar.tsx @@ -0,0 +1,7 @@ +import styled from "styled-components"; +import StatusBar from "../StatusBar/statusbar"; +const TopBar = () => { + return ; +}; + +export default TopBar; diff --git a/src/index.css b/src/index.css index ec2585e..3fd4163 100644 --- a/src/index.css +++ b/src/index.css @@ -1,13 +1,19 @@ body { margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + font-family: "SF Pro Text",-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; + font-size: 1.125rem; } code { font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; } + +:root{ + padding:0; + margin:0; +} \ No newline at end of file diff --git a/src/index.js b/src/index.js deleted file mode 100644 index d563c0f..0000000 --- a/src/index.js +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import './index.css'; -import App from './App'; -import reportWebVitals from './reportWebVitals'; - -const root = ReactDOM.createRoot(document.getElementById('root')); -root.render( - - - -); - -// If you want to start measuring performance in your app, pass a function -// to log results (for example: reportWebVitals(console.log)) -// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals -reportWebVitals(); diff --git a/src/index.tsx b/src/index.tsx new file mode 100644 index 0000000..e302672 --- /dev/null +++ b/src/index.tsx @@ -0,0 +1,22 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import "./index.css"; +import App from "./App"; +import reportWebVitals from "./reportWebVitals"; +import GlobalStyle from "./styles/globalStyle"; + +const root = ReactDOM.createRoot( + document.getElementById("root") as HTMLElement +); + +root.render( + + + + +); + +// If you want to start measuring performance in your app, pass a function +// to log results (for example: reportWebVitals(console.log)) +// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals +reportWebVitals(); diff --git a/src/pages/chat/chat.tsx b/src/pages/chat/chat.tsx new file mode 100644 index 0000000..6f2170d --- /dev/null +++ b/src/pages/chat/chat.tsx @@ -0,0 +1,10 @@ +import NavBar from "../../components/TopBar/topbar"; + +function Chat() { + return ( +
+ +
+ ); +} +export default Chat; diff --git a/src/styles/colors.css b/src/styles/colors.css new file mode 100644 index 0000000..4257af6 --- /dev/null +++ b/src/styles/colors.css @@ -0,0 +1,7 @@ +:root{ + --black : #000; + --gray-1 : #909093; + --gray-2: rgba(118, 118, 128, 0.12); + -blue : #3478F6;/*비강조, 두루두루*/ + +} \ No newline at end of file diff --git a/src/styles/globalStyle.ts b/src/styles/globalStyle.ts new file mode 100644 index 0000000..ad7a0eb --- /dev/null +++ b/src/styles/globalStyle.ts @@ -0,0 +1,30 @@ +import { createGlobalStyle } from "styled-components"; +import reset from "styled-reset"; +import "../styles/colors.css" +const GlobalStyle = createGlobalStyle` + ${reset} + *{ + box-sizing:border-box; + padding: 0; + margin: 0; + } + html,body{ + height:100%; + display: flex; + justify-content: center; + align-items: center; + + } + .pageWrapper{ + margin: 0; + padding:0; + display: flex; + /* align-items: center; */ + justify-content: center; + width: 23.4375rem; + height: 50.75rem; + border: solid var(--gray-1); + } +`; + +export default GlobalStyle; \ No newline at end of file From 64396500c25e0d052e5a00e347a1a1a4d8e5c4af Mon Sep 17 00:00:00 2001 From: dhsin98 Date: Fri, 29 Sep 2023 13:25:35 +0900 Subject: [PATCH 03/21] [feat]: input field --- src/assets/images/Arrow.svg | 5 ++ src/assets/images/Facetime.svg | 6 ++ src/components/ChatInput/chatinput.tsx | 91 ++++++++++++++++++++++++++ src/components/ChatTitle/chatTitle.tsx | 36 ++++++++++ src/components/TopBar/topbar.tsx | 18 ++++- src/pages/chat/chat.tsx | 22 ++++++- src/styles/colors.css | 1 + 7 files changed, 176 insertions(+), 3 deletions(-) create mode 100644 src/assets/images/Arrow.svg create mode 100644 src/assets/images/Facetime.svg create mode 100644 src/components/ChatInput/chatinput.tsx create mode 100644 src/components/ChatTitle/chatTitle.tsx diff --git a/src/assets/images/Arrow.svg b/src/assets/images/Arrow.svg new file mode 100644 index 0000000..0c1506b --- /dev/null +++ b/src/assets/images/Arrow.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/images/Facetime.svg b/src/assets/images/Facetime.svg new file mode 100644 index 0000000..3a6feb9 --- /dev/null +++ b/src/assets/images/Facetime.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/components/ChatInput/chatinput.tsx b/src/components/ChatInput/chatinput.tsx new file mode 100644 index 0000000..1b6101f --- /dev/null +++ b/src/components/ChatInput/chatinput.tsx @@ -0,0 +1,91 @@ +import React, { useState } from "react"; +import styled from "styled-components"; +import cameraIcon from "../../assets/images/Camera.svg"; +import bottomBar from "../../assets/images/LightBottomBar.svg"; +import appStoreIcon from "../../assets/images/App-Store.svg"; +import dictationIcon from "../../assets/images/Dictation.svg"; + +interface ChatInputProps { + onSend: (message: string) => void; +} + +const ChatInput: React.FC = ({ onSend }) => { + const [inputValue, setInputValue] = useState(""); + + const handleInputChange = (e: React.ChangeEvent) => { + setInputValue(e.target.value); + }; + + const handleInputEnter = (e: React.KeyboardEvent) => { + if (e.key === "Enter") { + e.preventDefault(); + onSend(inputValue); + setInputValue(""); + } + }; + + return ( + + + + setInputValue(e.target.value)} + // onChange={handleInputChange} + onKeyDown={handleInputEnter} + /> + + {/* */} + + ); +}; + +const BottomBarContainer = styled.div` + display: flex; + align-items: center; + gap: 1.38rem; + position: relative; + width: 22.3125rem; + height: 2.5rem; + margin-bottom: 0.81rem; +`; + +const Camera = styled.img` + width: 1.92713rem; + height: 1.51313rem; + margin-left: 1.06rem; +`; + +const AppStore = styled.img` + width: 2.18363rem; + height: 1.58331rem; +`; + +const BottomBarIcon = styled.img` + width: 23.4375rem; + height: 2.125rem; + position: absolute; + bottom: 0; +`; + +const Dictation = styled.img` + width: 1.6875rem; + height: 1.6875rem; +`; + +const Input = styled.input` + width: 100%; + height: 100%; + border: 1px solid var(--gray-3); + outline: none; + padding-left: 0.84rem; + border-radius: 1.875rem; + resize: none; + + &:focus { + box-shadow: none; + } +`; + +export default ChatInput; diff --git a/src/components/ChatTitle/chatTitle.tsx b/src/components/ChatTitle/chatTitle.tsx new file mode 100644 index 0000000..e6d6629 --- /dev/null +++ b/src/components/ChatTitle/chatTitle.tsx @@ -0,0 +1,36 @@ +import styled from "styled-components"; +import arrowIcon from "../../assets/images/Arrow.svg"; +import userIcon from "../../assets/images/User.svg"; +import facetime from "../../assets/images/Facetime.svg"; +const ChatTitle = () => { + return ( + + + +
+ +
+ ); +}; + +const ChatTitleContainer = styled.div` + display: flex; + align-items: center; + position: relative; + width: 22.3125rem; + height: 2.5rem; +`; +const ArrowIcon = styled.img` + width: 2.5rem; + height: 2.5rem; +`; + +const UserIcon = styled.img` + width: 2.06244rem; + height: 2.06238rem; +`; +const Facetime = styled.img` + margin-right: 1.12rem; +`; + +export default ChatTitle; diff --git a/src/components/TopBar/topbar.tsx b/src/components/TopBar/topbar.tsx index bcb1a93..71f0254 100644 --- a/src/components/TopBar/topbar.tsx +++ b/src/components/TopBar/topbar.tsx @@ -1,7 +1,23 @@ import styled from "styled-components"; import StatusBar from "../StatusBar/statusbar"; +import ChatTitle from "../ChatTitle/chatTitle"; + const TopBar = () => { - return ; + return ( + + + {/* */} + + ); }; +const TopBarContainer = styled.div` + width: 23.4375rem; + height: 6.5625rem; + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-between; +`; + export default TopBar; diff --git a/src/pages/chat/chat.tsx b/src/pages/chat/chat.tsx index 6f2170d..f7b8c8a 100644 --- a/src/pages/chat/chat.tsx +++ b/src/pages/chat/chat.tsx @@ -1,10 +1,28 @@ -import NavBar from "../../components/TopBar/topbar"; +import styled from "styled-components"; +import TopBar from "../../components/TopBar/topbar"; +import ChatInput from "../../components/ChatInput/chatinput"; + +import { useState } from "react"; function Chat() { + const [newchat, setNewchat] = useState(""); + return (
- + + + console.log(message)} /> +
); } + +const ChatContainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-between; + position: relative; +`; + export default Chat; diff --git a/src/styles/colors.css b/src/styles/colors.css index 4257af6..1c0017b 100644 --- a/src/styles/colors.css +++ b/src/styles/colors.css @@ -2,6 +2,7 @@ --black : #000; --gray-1 : #909093; --gray-2: rgba(118, 118, 128, 0.12); + --gray-3: rgba(60, 60, 67, 0.30); -blue : #3478F6;/*비강조, 두루두루*/ } \ No newline at end of file From 6c39a6c1b53b1b294833967a9623f806ab5a3821 Mon Sep 17 00:00:00 2001 From: dhsin98 Date: Fri, 29 Sep 2023 21:30:54 +0900 Subject: [PATCH 04/21] =?UTF-8?q?[feat]:bar=20=EC=88=98=EC=A0=95=20&=20mes?= =?UTF-8?q?sage=20=EC=B0=BD=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/datas/chatdata.json | 80 ++++++++++++++++++++++++++ src/assets/datas/userdata.json | 17 ++++++ src/components/ChatInput/chatinput.tsx | 64 +++++++++++---------- src/components/ChatTitle/chatTitle.tsx | 43 +++++++++++++- src/components/Message/message.tsx | 0 src/components/Message/messageList.tsx | 33 +++++++++++ src/components/StatusBar/statusbar.tsx | 2 +- src/components/TopBar/topbar.tsx | 4 +- src/pages/chat/chat.tsx | 28 --------- src/styles/colors.css | 2 +- src/styles/globalStyle.ts | 3 +- 11 files changed, 211 insertions(+), 65 deletions(-) create mode 100644 src/assets/datas/chatdata.json create mode 100644 src/assets/datas/userdata.json create mode 100644 src/components/Message/message.tsx create mode 100644 src/components/Message/messageList.tsx diff --git a/src/assets/datas/chatdata.json b/src/assets/datas/chatdata.json new file mode 100644 index 0000000..61a12d9 --- /dev/null +++ b/src/assets/datas/chatdata.json @@ -0,0 +1,80 @@ +[ + { + "id": 0, + "sender": "신동현", + "content": "ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ", + "showIcon": false + }, + { + "id": 1, + "sender": "신동현", + "content": "아니 나 졸작", + "showIcon": false + }, + { + "id": 2, + "sender": "신동현", + "content": "작업하는데 천이 부족해서", + "showIcon": false + }, + { + "id": 3, + "sender": "신동현", + "content": "동대문 들렀다가 집 감", + "showIcon": false + }, + { + "id": 4, + "sender": "이예진", + "content": "그것 참 불쌍하게 되었군", + "showIcon": false + }, + { + "id": 5, + "sender": "이예진", + "content": "동대문에서 집까지", + "showIcon": false + }, + { + "id": 6, + "sender": "이예진", + "content": "한 시간 넘게 걸리지 않아?", + "showIcon": false + }, + { + "id": 7, + "sender": "신동현", + "content": "한 시간 반? 정도", + "showIcon": false + }, + { + "id": 8, + "sender": "신동현", + "content": "그렇게 멀지는 않은데", + "showIcon": false + }, + { + "id": 9, + "sender": "신동현", + "content": "짐이 많아서 힘든 겨", + "showIcon": false + }, + { + "id": 10, + "sender": "이예진", + "content": "이따가 갈 때", + "showIcon": false + }, + { + "id": 11, + "sender": "이예진", + "content": "심심하면 영통하자", + "showIcon": false + }, + { + "id": 12, + "sender": "신동현", + "content": "오키 알겠음\n\n 그럼 이따가 연락할게", + "showIcon": false + } +] \ No newline at end of file diff --git a/src/assets/datas/userdata.json b/src/assets/datas/userdata.json new file mode 100644 index 0000000..abc940f --- /dev/null +++ b/src/assets/datas/userdata.json @@ -0,0 +1,17 @@ +{ + "users": [ + { + "id": 1, + "name": "신동현", + "instagram":"@s_d0nghyun" + + }, + { + "id": 2, + "name": "이예진", + "instagram":"@leebyvae" + + } + ] + } + diff --git a/src/components/ChatInput/chatinput.tsx b/src/components/ChatInput/chatinput.tsx index 1b6101f..57a12aa 100644 --- a/src/components/ChatInput/chatinput.tsx +++ b/src/components/ChatInput/chatinput.tsx @@ -1,10 +1,11 @@ import React, { useState } from "react"; import styled from "styled-components"; import cameraIcon from "../../assets/images/Camera.svg"; -import bottomBar from "../../assets/images/LightBottomBar.svg"; import appStoreIcon from "../../assets/images/App-Store.svg"; import dictationIcon from "../../assets/images/Dictation.svg"; +//채팅 입력받는 component + interface ChatInputProps { onSend: (message: string) => void; } @@ -12,14 +13,14 @@ interface ChatInputProps { const ChatInput: React.FC = ({ onSend }) => { const [inputValue, setInputValue] = useState(""); - const handleInputChange = (e: React.ChangeEvent) => { - setInputValue(e.target.value); - }; - const handleInputEnter = (e: React.KeyboardEvent) => { if (e.key === "Enter") { e.preventDefault(); - onSend(inputValue); + if (inputValue.trim() !== "") { + // 입력값이 공백인 경우 예외 처리 + onSend(inputValue); + } + // 입력값 초기화 setInputValue(""); } }; @@ -28,15 +29,17 @@ const ChatInput: React.FC = ({ onSend }) => { - setInputValue(e.target.value)} - // onChange={handleInputChange} - onKeyDown={handleInputEnter} - /> - - {/* */} + + setInputValue(e.target.value)} + onKeyPress={handleInputEnter} + //한글 입력의 경우: 마지막 단어가 input으로 들어가는 에러 방지하기 위해 + /> + + ); }; @@ -46,7 +49,7 @@ const BottomBarContainer = styled.div` align-items: center; gap: 1.38rem; position: relative; - width: 22.3125rem; + width: 100%; height: 2.5rem; margin-bottom: 0.81rem; `; @@ -62,21 +65,11 @@ const AppStore = styled.img` height: 1.58331rem; `; -const BottomBarIcon = styled.img` - width: 23.4375rem; - height: 2.125rem; - position: absolute; - bottom: 0; -`; - -const Dictation = styled.img` - width: 1.6875rem; - height: 1.6875rem; -`; - const Input = styled.input` width: 100%; - height: 100%; + /* height: 100%; */ + margin-right: 1.03rem; + height: 2.125rem; border: 1px solid var(--gray-3); outline: none; padding-left: 0.84rem; @@ -88,4 +81,17 @@ const Input = styled.input` } `; +const InputContainer = styled.span` + display: flex; + align-items: center; + flex-grow: 1; +`; + +const Dictation = styled.img` + width: 1.6875rem; + height: 1.6875rem; + position: absolute; //input fiield 안에 위치 + right: 1.25rem; +`; + export default ChatInput; diff --git a/src/components/ChatTitle/chatTitle.tsx b/src/components/ChatTitle/chatTitle.tsx index e6d6629..55f64a2 100644 --- a/src/components/ChatTitle/chatTitle.tsx +++ b/src/components/ChatTitle/chatTitle.tsx @@ -2,12 +2,25 @@ import styled from "styled-components"; import arrowIcon from "../../assets/images/Arrow.svg"; import userIcon from "../../assets/images/User.svg"; import facetime from "../../assets/images/Facetime.svg"; -const ChatTitle = () => { + +interface ChatHeaderProps { + chatName: string; + chatID: string; + changeUser?: () => void; +} +const ChatTitle: React.FC = ({ + chatName, + chatID, + changeUser, +}) => { return ( - + -
+ + {chatName} + {chatID} +
); @@ -18,7 +31,30 @@ const ChatTitleContainer = styled.div` align-items: center; position: relative; width: 22.3125rem; + width: 100%; height: 2.5rem; + padding-bottom: 0.7rem; + border-bottom: solid rgba(144, 144, 147, 0.5); +`; +const UserSection = styled.div` + display: flex; + flex-direction: column; + width: 100%; +`; +const UserName = styled.div` + color: var(--black); + font-size: 1.125rem; + font-style: normal; + font-weight: 600; + line-height: normal; + word-wrap: break-word; +`; +const InstagramID = styled.div` + color: var(--gray-1); + font-size: 0.75rem; + font-style: normal; + font-weight: 400; + line-height: 125%; /* 0.9375rem */ `; const ArrowIcon = styled.img` width: 2.5rem; @@ -28,6 +64,7 @@ const ArrowIcon = styled.img` const UserIcon = styled.img` width: 2.06244rem; height: 2.06238rem; + margin-right: 0.64rem; `; const Facetime = styled.img` margin-right: 1.12rem; diff --git a/src/components/Message/message.tsx b/src/components/Message/message.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/components/Message/messageList.tsx b/src/components/Message/messageList.tsx new file mode 100644 index 0000000..9a7fca8 --- /dev/null +++ b/src/components/Message/messageList.tsx @@ -0,0 +1,33 @@ +import React, { useRef, useEffect } from "react"; +import styled from "styled-components"; + +interface MessageListProps { + children: React.ReactNode; +} + +//메시지 리스트 스크롤 관리 + +const MessageList: React.FC = ({ children }) => { + const scrollRef = useRef(null); + + useEffect(() => { + // 현재 스크롤 위치 :scrollRef.current.scrollTop + // 스크롤 길이 : scrollRef.current.scrollHeight + if (scrollRef.current) { + scrollRef.current.scrollTop = scrollRef.current.scrollHeight; + } + }, [children]); + + return ( + {children} + ); +}; + +const MessageListContainer = styled.div` + overflow-y: auto; + width: 100%; + height: 39.33rem; + scrollbar-width: none; +`; + +export default MessageList; diff --git a/src/components/StatusBar/statusbar.tsx b/src/components/StatusBar/statusbar.tsx index 0b20eb0..8658e94 100644 --- a/src/components/StatusBar/statusbar.tsx +++ b/src/components/StatusBar/statusbar.tsx @@ -22,7 +22,7 @@ const StatusBarContainer = styled.div` height: 2.75rem; /* width: 100vw; height: 5vh; */ - position: fixed; + position: relative; `; const TimeIcon = styled.img` width: 3.375rem; diff --git a/src/components/TopBar/topbar.tsx b/src/components/TopBar/topbar.tsx index 71f0254..1519751 100644 --- a/src/components/TopBar/topbar.tsx +++ b/src/components/TopBar/topbar.tsx @@ -6,15 +6,15 @@ const TopBar = () => { return ( - {/* */} ); }; const TopBarContainer = styled.div` width: 23.4375rem; - height: 6.5625rem; + height: 2.75rem; display: flex; + position: relative; flex-direction: column; align-items: center; justify-content: space-between; diff --git a/src/pages/chat/chat.tsx b/src/pages/chat/chat.tsx index f7b8c8a..e69de29 100644 --- a/src/pages/chat/chat.tsx +++ b/src/pages/chat/chat.tsx @@ -1,28 +0,0 @@ -import styled from "styled-components"; -import TopBar from "../../components/TopBar/topbar"; -import ChatInput from "../../components/ChatInput/chatinput"; - -import { useState } from "react"; - -function Chat() { - const [newchat, setNewchat] = useState(""); - - return ( -
- - - console.log(message)} /> - -
- ); -} - -const ChatContainer = styled.div` - display: flex; - flex-direction: column; - align-items: center; - justify-content: space-between; - position: relative; -`; - -export default Chat; diff --git a/src/styles/colors.css b/src/styles/colors.css index 1c0017b..2b533f7 100644 --- a/src/styles/colors.css +++ b/src/styles/colors.css @@ -3,6 +3,6 @@ --gray-1 : #909093; --gray-2: rgba(118, 118, 128, 0.12); --gray-3: rgba(60, 60, 67, 0.30); - -blue : #3478F6;/*비강조, 두루두루*/ + --blue : #3478F6;/*비강조, 두루두루*/ } \ No newline at end of file diff --git a/src/styles/globalStyle.ts b/src/styles/globalStyle.ts index ad7a0eb..9965160 100644 --- a/src/styles/globalStyle.ts +++ b/src/styles/globalStyle.ts @@ -23,7 +23,8 @@ const GlobalStyle = createGlobalStyle` justify-content: center; width: 23.4375rem; height: 50.75rem; - border: solid var(--gray-1); + border-radius:1.5rem; + border: solid var(--gray-1); //나중에 없애도됨 } `; From 5fd1243ea3d00ffd709d4d41dcaddfd77bbd82da Mon Sep 17 00:00:00 2001 From: dhsin98 Date: Fri, 29 Sep 2023 21:41:46 +0900 Subject: [PATCH 05/21] =?UTF-8?q?[feat]:=EC=B1=84=ED=8C=85=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD,=20=EC=8A=A4=ED=83=80=EC=9D=BC=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Message/message.tsx | 76 +++++++++++++++++++ src/pages/chat/chat.tsx | 116 +++++++++++++++++++++++++++++ 2 files changed, 192 insertions(+) diff --git a/src/components/Message/message.tsx b/src/components/Message/message.tsx index e69de29..5a92133 100644 --- a/src/components/Message/message.tsx +++ b/src/components/Message/message.tsx @@ -0,0 +1,76 @@ +import React from "react"; +import styled from "styled-components"; +import userIcon from "../../assets/images/User.svg"; + +interface MessageProps { + sender: string; + content: string; + nowUser: string; + showIcon: boolean; +} + +const Message: React.FC = ({ + sender, + content, + nowUser, + showIcon, +}) => { + return ( + + {showIcon && } + + {content} + + + ); +}; + +const MessageContainer = styled.div<{ sender: string; nowUser: string }>` + display: flex; + align-items: center; + margin: 0.44rem 0; + gap: 0.44rem; + max-width: 100%; + flex-direction: ${(props) => + props.sender === props.nowUser ? "row-reverse" : "row"}; + //왼, 오른쪽 기준 나누기 +`; + +const MessageItem = styled.div<{ + sender: string; + content: string; + nowUser: string; + showIcon: boolean; +}>` + /* width: 100%; */ + padding: 0.7rem; + border-radius: 1.25rem; + margin-right: 0.69rem; + background-color: ${({ sender, nowUser }) => + sender === nowUser ? "var(--blue)" : "rgba(118, 118, 128, 0.12)"}; + color: ${({ sender, nowUser }) => + sender === nowUser ? "#FFFFFF" : "#000000"}; + text-align: ${({ sender, nowUser }) => + sender === nowUser ? "right" : "left"}; + margin-left: ${(msg) => + !msg.showIcon && msg.sender !== msg.nowUser ? "3.75rem" : "0.69rem"}; + // showIcon가 false인 경우 margin-left를 한방에 설정 + font-size: 0.9375rem; + font-weight: 400; + line-height: normal; + box-sizing: border-box; + word-break: break-all; +`; + +const UserIconShow = styled.img` + width: 2.06244rem; + height: 2.06238rem; + margin-left: 1rem; +`; + +export default Message; diff --git a/src/pages/chat/chat.tsx b/src/pages/chat/chat.tsx index e69de29..25865f1 100644 --- a/src/pages/chat/chat.tsx +++ b/src/pages/chat/chat.tsx @@ -0,0 +1,116 @@ +import React, { useEffect, useRef, useState } from "react"; +import styled from "styled-components"; +import TopBar from "../../components/TopBar/topbar"; +import ChatInput from "../../components/ChatInput/chatinput"; +import userData from "../../assets/datas/userdata.json"; +import chatData from "../../assets/datas/chatdata.json"; +import ChatTitle from "../../components/ChatTitle/chatTitle"; +import MessageList from "../../components/Message/messageList"; +import Message from "../../components/Message/message"; +import "../../styles/colors.css"; +import bottomBar from "../../assets/images/LightBottomBar.svg"; + +interface Message { + id: number; + sender: string; + content: string; + showIcon: boolean; +} + +function Chat() { + const savedChats = JSON.parse( + localStorage.getItem("chatMessages") || JSON.stringify(chatData) + ); + const savedMessageId = JSON.parse( + localStorage.getItem("currMessageId") || JSON.stringify(chatData.length) + ); + const [messages, setMessages] = useState(savedChats); + const [messageId, setMessageId] = useState(savedMessageId); + const [nowUser, setNowUser] = useState(userData.users[0]); + + const handleSendMessage = (content: string) => { + const newMessage: Message = { + id: messageId, + sender: nowUser.name, + content, + showIcon: false, + }; + const updatedMessages = [...messages, newMessage]; + setMessages(updatedMessages); + // 다음 메시지를 위해 messageId 업데이트 + setMessageId(messageId + 1); + // 로컬 스토리지에 채팅 데이터 저장 + localStorage.setItem("currMessageId", messageId.toString()); + localStorage.setItem("chatMessages", JSON.stringify(updatedMessages)); + }; + + const changeUser = () => { + setNowUser((prev) => + prev.name === userData.users[0].name + ? userData.users[1] + : userData.users[0] + ); + + // 채팅 데이터를 불러온 후 showIcon을 변경하고 다시 저장 + + // const updatedMessages = messages.map((message) => { + // if (message.sender !== nowUser.name) { + // // 현재 사용자가 아닌 경우 showIcon을 true로 설정 + // return { ...message, showIcon: true }; + // } + // return message; + // }); + + // 로컬 스토리지에 채팅 데이터 저장 + // localStorage.setItem("chatMessages", JSON.stringify(updatedMessages)); + }; + + const partner = + nowUser.name === userData.users[0].name + ? userData.users[1] + : userData.users[0]; + + const messageContainers = messages.map((message: Message) => { + return ( + + ); + }); + + return ( +
+ + + + {messageContainers} + + + +
+ ); +} + +const ChatContainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; + /* justify-content: space-between; */ + /* margin-bottom: 1.31rem; */ + position: relative; +`; + +const BottomBarIcon = styled.img` + width: 100%; + height: 2.125rem; +`; + +export default Chat; From 2d037a08e3c14ff4a45972d326cf6b27089dc3c6 Mon Sep 17 00:00:00 2001 From: dhsin98 Date: Fri, 29 Sep 2023 23:03:35 +0900 Subject: [PATCH 06/21] =?UTF-8?q?[feat]=20showIcon=20=ED=86=B5=ED=95=9C=20?= =?UTF-8?q?=EC=95=84=EC=9D=B4=EC=BD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/index.html | 2 +- src/assets/datas/chatdata.json | 8 +++--- src/components/ChatInput/chatinput.tsx | 6 ++--- src/pages/chat/chat.tsx | 35 +++++++++++++++----------- 4 files changed, 28 insertions(+), 23 deletions(-) diff --git a/public/index.html b/public/index.html index aa069f2..9aaf4d4 100644 --- a/public/index.html +++ b/public/index.html @@ -24,7 +24,7 @@ work correctly both with client-side routing and a non-root public URL. Learn how to configure a non-root public URL by running `npm run build`. --> - React App + CEOS Messenger service diff --git a/src/assets/datas/chatdata.json b/src/assets/datas/chatdata.json index 61a12d9..014bf67 100644 --- a/src/assets/datas/chatdata.json +++ b/src/assets/datas/chatdata.json @@ -21,7 +21,7 @@ "id": 3, "sender": "신동현", "content": "동대문 들렀다가 집 감", - "showIcon": false + "showIcon": true }, { "id": 4, @@ -39,7 +39,7 @@ "id": 6, "sender": "이예진", "content": "한 시간 넘게 걸리지 않아?", - "showIcon": false + "showIcon": true }, { "id": 7, @@ -57,7 +57,7 @@ "id": 9, "sender": "신동현", "content": "짐이 많아서 힘든 겨", - "showIcon": false + "showIcon": true }, { "id": 10, @@ -69,7 +69,7 @@ "id": 11, "sender": "이예진", "content": "심심하면 영통하자", - "showIcon": false + "showIcon": true }, { "id": 12, diff --git a/src/components/ChatInput/chatinput.tsx b/src/components/ChatInput/chatinput.tsx index 57a12aa..0db1347 100644 --- a/src/components/ChatInput/chatinput.tsx +++ b/src/components/ChatInput/chatinput.tsx @@ -14,7 +14,7 @@ const ChatInput: React.FC = ({ onSend }) => { const [inputValue, setInputValue] = useState(""); const handleInputEnter = (e: React.KeyboardEvent) => { - if (e.key === "Enter") { + if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); if (inputValue.trim() !== "") { // 입력값이 공백인 경우 예외 처리 @@ -36,6 +36,7 @@ const ChatInput: React.FC = ({ onSend }) => { value={inputValue} onChange={(e) => setInputValue(e.target.value)} onKeyPress={handleInputEnter} + //한글 입력의 경우: 마지막 단어가 input으로 들어가는 에러 방지하기 위해 /> @@ -67,7 +68,6 @@ const AppStore = styled.img` const Input = styled.input` width: 100%; - /* height: 100%; */ margin-right: 1.03rem; height: 2.125rem; border: 1px solid var(--gray-3); @@ -75,7 +75,7 @@ const Input = styled.input` padding-left: 0.84rem; border-radius: 1.875rem; resize: none; - + word-break: break-all; &:focus { box-shadow: none; } diff --git a/src/pages/chat/chat.tsx b/src/pages/chat/chat.tsx index 25865f1..8531994 100644 --- a/src/pages/chat/chat.tsx +++ b/src/pages/chat/chat.tsx @@ -28,6 +28,19 @@ function Chat() { const [messageId, setMessageId] = useState(savedMessageId); const [nowUser, setNowUser] = useState(userData.users[0]); + const updateMessagesWithShowIcon = (messages: Message[]) => { + const updatedMessages = [...messages]; + + for (let i = 1; i < updatedMessages.length; i++) { + if (updatedMessages[i - 1].sender !== updatedMessages[i].sender) { + // 이전 메시지랑 sender가 다른경우 showIcon을 true로 설정 + updatedMessages[i - 1].showIcon = true; + } + } + + return updatedMessages; + }; + const handleSendMessage = (content: string) => { const newMessage: Message = { id: messageId, @@ -35,8 +48,11 @@ function Chat() { content, showIcon: false, }; + const updatedMessages = [...messages, newMessage]; - setMessages(updatedMessages); + const messagesWithShowIcon = updateMessagesWithShowIcon(updatedMessages); + + setMessages(messagesWithShowIcon); // 다음 메시지를 위해 messageId 업데이트 setMessageId(messageId + 1); // 로컬 스토리지에 채팅 데이터 저장 @@ -50,19 +66,6 @@ function Chat() { ? userData.users[1] : userData.users[0] ); - - // 채팅 데이터를 불러온 후 showIcon을 변경하고 다시 저장 - - // const updatedMessages = messages.map((message) => { - // if (message.sender !== nowUser.name) { - // // 현재 사용자가 아닌 경우 showIcon을 true로 설정 - // return { ...message, showIcon: true }; - // } - // return message; - // }); - - // 로컬 스토리지에 채팅 데이터 저장 - // localStorage.setItem("chatMessages", JSON.stringify(updatedMessages)); }; const partner = @@ -71,13 +74,15 @@ function Chat() { : userData.users[0]; const messageContainers = messages.map((message: Message) => { + const isCurrentUser = message.sender === nowUser.name; + return ( ); }); From a6b784b6c9fdd7b861c71f6d059e29102c3408d8 Mon Sep 17 00:00:00 2001 From: dhsin98 Date: Fri, 29 Sep 2023 23:04:34 +0900 Subject: [PATCH 07/21] =?UTF-8?q?[style]:=20=EA=B0=84=EA=B2=A9=20=EC=A1=B0?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Message/message.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Message/message.tsx b/src/components/Message/message.tsx index 5a92133..17692eb 100644 --- a/src/components/Message/message.tsx +++ b/src/components/Message/message.tsx @@ -58,7 +58,7 @@ const MessageItem = styled.div<{ text-align: ${({ sender, nowUser }) => sender === nowUser ? "right" : "left"}; margin-left: ${(msg) => - !msg.showIcon && msg.sender !== msg.nowUser ? "3.75rem" : "0.69rem"}; + !msg.showIcon && msg.sender !== msg.nowUser ? "3.75rem" : "0rem"}; // showIcon가 false인 경우 margin-left를 한방에 설정 font-size: 0.9375rem; font-weight: 400; From c90d303741eef7f09e6cb1bbbf3071206e3f4ae8 Mon Sep 17 00:00:00 2001 From: dhsin98 Date: Sat, 30 Sep 2023 18:17:31 +0900 Subject: [PATCH 08/21] =?UTF-8?q?[style]:=20=EC=A0=84=EC=B2=B4=20=EC=8A=A4?= =?UTF-8?q?=ED=83=80=EC=9D=BC=20=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/datas/chatdata.json | 2 +- src/components/ChatInput/chatinput.tsx | 3 +++ src/components/ChatTitle/chatTitle.tsx | 4 ++-- src/components/Message/message.tsx | 1 + src/components/Message/messageList.tsx | 3 ++- src/components/StatusBar/statusbar.tsx | 17 ++++++++------ src/components/TopBar/topbar.tsx | 16 +------------ src/index.css | 6 +---- src/pages/chat/chat.tsx | 31 +++++++++++++++++++++++--- src/styles/globalStyle.ts | 8 +++---- 10 files changed, 52 insertions(+), 39 deletions(-) diff --git a/src/assets/datas/chatdata.json b/src/assets/datas/chatdata.json index 014bf67..43f1fbf 100644 --- a/src/assets/datas/chatdata.json +++ b/src/assets/datas/chatdata.json @@ -74,7 +74,7 @@ { "id": 12, "sender": "신동현", - "content": "오키 알겠음\n\n 그럼 이따가 연락할게", + "content": "오키 알겠음\n그럼 이따가 연락할게", "showIcon": false } ] \ No newline at end of file diff --git a/src/components/ChatInput/chatinput.tsx b/src/components/ChatInput/chatinput.tsx index 0db1347..709d0d9 100644 --- a/src/components/ChatInput/chatinput.tsx +++ b/src/components/ChatInput/chatinput.tsx @@ -52,6 +52,8 @@ const BottomBarContainer = styled.div` position: relative; width: 100%; height: 2.5rem; + position: fixed; + bottom: 2.125rem; margin-bottom: 0.81rem; `; @@ -85,6 +87,7 @@ const InputContainer = styled.span` display: flex; align-items: center; flex-grow: 1; + width: 100%; `; const Dictation = styled.img` diff --git a/src/components/ChatTitle/chatTitle.tsx b/src/components/ChatTitle/chatTitle.tsx index 55f64a2..bf0f5ee 100644 --- a/src/components/ChatTitle/chatTitle.tsx +++ b/src/components/ChatTitle/chatTitle.tsx @@ -29,8 +29,8 @@ const ChatTitle: React.FC = ({ const ChatTitleContainer = styled.div` display: flex; align-items: center; - position: relative; - width: 22.3125rem; + position: fixed; + top: 2.75rem; width: 100%; height: 2.5rem; padding-bottom: 0.7rem; diff --git a/src/components/Message/message.tsx b/src/components/Message/message.tsx index 17692eb..90ab304 100644 --- a/src/components/Message/message.tsx +++ b/src/components/Message/message.tsx @@ -65,6 +65,7 @@ const MessageItem = styled.div<{ line-height: normal; box-sizing: border-box; word-break: break-all; + white-space: keep-all; //들여쓰기 `; const UserIconShow = styled.img` diff --git a/src/components/Message/messageList.tsx b/src/components/Message/messageList.tsx index 9a7fca8..778639d 100644 --- a/src/components/Message/messageList.tsx +++ b/src/components/Message/messageList.tsx @@ -26,7 +26,8 @@ const MessageList: React.FC = ({ children }) => { const MessageListContainer = styled.div` overflow-y: auto; width: 100%; - height: 39.33rem; + max-height: 100%; + flex: 1; scrollbar-width: none; `; diff --git a/src/components/StatusBar/statusbar.tsx b/src/components/StatusBar/statusbar.tsx index 8658e94..e98dc33 100644 --- a/src/components/StatusBar/statusbar.tsx +++ b/src/components/StatusBar/statusbar.tsx @@ -18,15 +18,18 @@ const StatusBar = () => { }; const StatusBarContainer = styled.div` - width: 23.4375rem; + width: 100%; height: 2.75rem; - /* width: 100vw; - height: 5vh; */ position: relative; + display: flex; + justify-content: space-between; + align-items: center; `; const TimeIcon = styled.img` width: 3.375rem; - padding: 0.69rem 0 1.31rem 0.81rem; + height: 1.3125rem; + padding-bottom: 0.17rem; + margin-left: 0.2rem; right: 16.33rem; `; @@ -41,16 +44,16 @@ const WifiIcon = styled.img` const BatteryIcon = styled.img` width: 1.5205rem; height: 0.70831rem; + margin-right: 1.12rem; `; const StatusBarRight = styled.div` - position: absolute; display: inline-flex; align-items: center; gap: 0.31rem; left: 18.37rem; right: 0.9rem; - top: 1.08rem; - bottom: 0.96rem; + /* top: 1.08rem; + bottom: 0.96rem; */ `; export default StatusBar; diff --git a/src/components/TopBar/topbar.tsx b/src/components/TopBar/topbar.tsx index 1519751..766ea79 100644 --- a/src/components/TopBar/topbar.tsx +++ b/src/components/TopBar/topbar.tsx @@ -3,21 +3,7 @@ import StatusBar from "../StatusBar/statusbar"; import ChatTitle from "../ChatTitle/chatTitle"; const TopBar = () => { - return ( - - - - ); + return ; }; -const TopBarContainer = styled.div` - width: 23.4375rem; - height: 2.75rem; - display: flex; - position: relative; - flex-direction: column; - align-items: center; - justify-content: space-between; -`; - export default TopBar; diff --git a/src/index.css b/src/index.css index 3fd4163..206ffdc 100644 --- a/src/index.css +++ b/src/index.css @@ -1,3 +1,4 @@ +@import url('https://fonts.cdnfonts.com/css/sf-pro-display'); body { margin: 0; font-family: "SF Pro Text",-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', @@ -12,8 +13,3 @@ code { font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; } - -:root{ - padding:0; - margin:0; -} \ No newline at end of file diff --git a/src/pages/chat/chat.tsx b/src/pages/chat/chat.tsx index 8531994..9487855 100644 --- a/src/pages/chat/chat.tsx +++ b/src/pages/chat/chat.tsx @@ -91,13 +91,19 @@ function Chat() {
+ - {messageContainers} + + + {messageContainers} + + +
@@ -108,14 +114,33 @@ const ChatContainer = styled.div` display: flex; flex-direction: column; align-items: center; - /* justify-content: space-between; */ - /* margin-bottom: 1.31rem; */ + position: absolute; + border-radius: 1.5rem; + height: 100vh; + width: 100vw; + border: solid var(--gray-1); position: relative; `; +const MessageContainer = styled.div` + flex: 1; + overflow-y: auto; + width: 100%; + height: 100%; + + padding: 2.75rem 0; //Topbar랑 안겹치게 + margin-bottom: 2.5rem; //아래 부분이랑 안겹치게 + display: flex; + flex-direction: column; + align-items: center; +`; + const BottomBarIcon = styled.img` width: 100%; height: 2.125rem; + margin-bottom: 0; + bottom: 0; + position: fixed; `; export default Chat; diff --git a/src/styles/globalStyle.ts b/src/styles/globalStyle.ts index 9965160..fcac5f5 100644 --- a/src/styles/globalStyle.ts +++ b/src/styles/globalStyle.ts @@ -19,12 +19,10 @@ const GlobalStyle = createGlobalStyle` margin: 0; padding:0; display: flex; - /* align-items: center; */ justify-content: center; - width: 23.4375rem; - height: 50.75rem; - border-radius:1.5rem; - border: solid var(--gray-1); //나중에 없애도됨 + height:100vh; + width:100vw; + } `; From 13a7c1c60e10485046378880574535aea8961942 Mon Sep 17 00:00:00 2001 From: dhsin98 Date: Sat, 30 Sep 2023 18:23:43 +0900 Subject: [PATCH 09/21] =?UTF-8?q?[style]:=20position=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ChatInput/chatinput.tsx | 2 -- src/components/ChatTitle/chatTitle.tsx | 3 +-- src/pages/chat/chat.tsx | 7 +++---- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/components/ChatInput/chatinput.tsx b/src/components/ChatInput/chatinput.tsx index 709d0d9..7f2eb96 100644 --- a/src/components/ChatInput/chatinput.tsx +++ b/src/components/ChatInput/chatinput.tsx @@ -52,8 +52,6 @@ const BottomBarContainer = styled.div` position: relative; width: 100%; height: 2.5rem; - position: fixed; - bottom: 2.125rem; margin-bottom: 0.81rem; `; diff --git a/src/components/ChatTitle/chatTitle.tsx b/src/components/ChatTitle/chatTitle.tsx index bf0f5ee..986f87e 100644 --- a/src/components/ChatTitle/chatTitle.tsx +++ b/src/components/ChatTitle/chatTitle.tsx @@ -29,8 +29,7 @@ const ChatTitle: React.FC = ({ const ChatTitleContainer = styled.div` display: flex; align-items: center; - position: fixed; - top: 2.75rem; + position: relative; width: 100%; height: 2.5rem; padding-bottom: 0.7rem; diff --git a/src/pages/chat/chat.tsx b/src/pages/chat/chat.tsx index 9487855..d687427 100644 --- a/src/pages/chat/chat.tsx +++ b/src/pages/chat/chat.tsx @@ -127,9 +127,8 @@ const MessageContainer = styled.div` overflow-y: auto; width: 100%; height: 100%; - - padding: 2.75rem 0; //Topbar랑 안겹치게 - margin-bottom: 2.5rem; //아래 부분이랑 안겹치게 + /* padding: 2.75rem 0; //Topbar랑 안겹치게 + margin-bottom: 2.5rem; //아래 부분이랑 안겹치게 */ display: flex; flex-direction: column; align-items: center; @@ -140,7 +139,7 @@ const BottomBarIcon = styled.img` height: 2.125rem; margin-bottom: 0; bottom: 0; - position: fixed; + position: relative; `; export default Chat; From cc49fc48315c80eb4ad64701621634e94e289da1 Mon Sep 17 00:00:00 2001 From: dhsin98 Date: Sat, 30 Sep 2023 18:40:32 +0900 Subject: [PATCH 10/21] =?UTF-8?q?[style]:=20css=20=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ChatInput/chatinput.tsx | 2 +- src/components/StatusBar/statusbar.tsx | 7 +++---- src/pages/chat/chat.tsx | 6 ++---- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/components/ChatInput/chatinput.tsx b/src/components/ChatInput/chatinput.tsx index 7f2eb96..4104da1 100644 --- a/src/components/ChatInput/chatinput.tsx +++ b/src/components/ChatInput/chatinput.tsx @@ -52,7 +52,7 @@ const BottomBarContainer = styled.div` position: relative; width: 100%; height: 2.5rem; - margin-bottom: 0.81rem; + /* margin-bottom: 0.81rem; */ `; const Camera = styled.img` diff --git a/src/components/StatusBar/statusbar.tsx b/src/components/StatusBar/statusbar.tsx index e98dc33..15d2296 100644 --- a/src/components/StatusBar/statusbar.tsx +++ b/src/components/StatusBar/statusbar.tsx @@ -27,10 +27,9 @@ const StatusBarContainer = styled.div` `; const TimeIcon = styled.img` width: 3.375rem; - height: 1.3125rem; - padding-bottom: 0.17rem; - margin-left: 0.2rem; - right: 16.33rem; + height: 2.3125rem; + padding-bottom: 0.6rem; + margin-left: 0.4rem; `; const CellularIcon = styled.img` diff --git a/src/pages/chat/chat.tsx b/src/pages/chat/chat.tsx index d687427..e64278d 100644 --- a/src/pages/chat/chat.tsx +++ b/src/pages/chat/chat.tsx @@ -9,6 +9,7 @@ import MessageList from "../../components/Message/messageList"; import Message from "../../components/Message/message"; import "../../styles/colors.css"; import bottomBar from "../../assets/images/LightBottomBar.svg"; +import StatusBar from "../../components/StatusBar/statusbar"; interface Message { id: number; @@ -90,8 +91,7 @@ function Chat() { return (
- - + Date: Sun, 1 Oct 2023 15:42:27 +0900 Subject: [PATCH 11/21] =?UTF-8?q?[feat]:=20input=20bar=20=EC=88=98?= =?UTF-8?q?=EC=A0=95,=20=EC=95=84=EC=9D=B4=EC=BD=98=20show=20=20=EC=88=98?= =?UTF-8?q?=EC=A0=95,=20message=20=EC=B5=9C=EB=8C=80=ED=81=AC=EA=B8=B0=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ChatInput/chatinput.tsx | 19 ++++++++- src/components/Message/message.tsx | 10 ++--- src/pages/chat/chat.tsx | 53 +++++++++++++++----------- src/styles/colors.css | 3 +- 4 files changed, 53 insertions(+), 32 deletions(-) diff --git a/src/components/ChatInput/chatinput.tsx b/src/components/ChatInput/chatinput.tsx index 4104da1..780d474 100644 --- a/src/components/ChatInput/chatinput.tsx +++ b/src/components/ChatInput/chatinput.tsx @@ -13,7 +13,8 @@ interface ChatInputProps { const ChatInput: React.FC = ({ onSend }) => { const [inputValue, setInputValue] = useState(""); - const handleInputEnter = (e: React.KeyboardEvent) => { + //여러줄 입력 위해 input=> textarea로 변경 + const handleInputEnter = (e: React.KeyboardEvent) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); if (inputValue.trim() !== "") { @@ -23,6 +24,10 @@ const ChatInput: React.FC = ({ onSend }) => { // 입력값 초기화 setInputValue(""); } + if (e.key === "Enter" && e.shiftKey) { + e.preventDefault(); + setInputValue((prev) => prev + "\n"); + } }; return ( @@ -66,8 +71,10 @@ const AppStore = styled.img` height: 1.58331rem; `; -const Input = styled.input` +const Input = styled.textarea` width: 100%; + font-family: "SF Pro Text"; + font-size: 0.9375rem; margin-right: 1.03rem; height: 2.125rem; border: 1px solid var(--gray-3); @@ -76,9 +83,17 @@ const Input = styled.input` border-radius: 1.875rem; resize: none; word-break: break-all; + overflow-y: auto; //스크롤바 + padding-top: 0.5rem; &:focus { box-shadow: none; } + &::placeholder { + display: flex; + + align-items: center; + } + padding-right: 2rem; //input 길어질때 방지 하기 위해 `; const InputContainer = styled.span` diff --git a/src/components/Message/message.tsx b/src/components/Message/message.tsx index 90ab304..878b530 100644 --- a/src/components/Message/message.tsx +++ b/src/components/Message/message.tsx @@ -55,17 +55,17 @@ const MessageItem = styled.div<{ sender === nowUser ? "var(--blue)" : "rgba(118, 118, 128, 0.12)"}; color: ${({ sender, nowUser }) => sender === nowUser ? "#FFFFFF" : "#000000"}; - text-align: ${({ sender, nowUser }) => - sender === nowUser ? "right" : "left"}; - margin-left: ${(msg) => - !msg.showIcon && msg.sender !== msg.nowUser ? "3.75rem" : "0rem"}; // showIcon가 false인 경우 margin-left를 한방에 설정 + margin-left: ${(msg) => + !msg.showIcon && msg.sender !== msg.nowUser ? "3.75rem" : "0"}; + max-width: 90%; // 데이터가 길어질 경우 + font-size: 0.9375rem; font-weight: 400; line-height: normal; box-sizing: border-box; word-break: break-all; - white-space: keep-all; //들여쓰기 + white-space: pre-line; //띄어쓰기도 인식할 수 있게 `; const UserIconShow = styled.img` diff --git a/src/pages/chat/chat.tsx b/src/pages/chat/chat.tsx index e64278d..885f2d4 100644 --- a/src/pages/chat/chat.tsx +++ b/src/pages/chat/chat.tsx @@ -1,6 +1,5 @@ import React, { useEffect, useRef, useState } from "react"; import styled from "styled-components"; -import TopBar from "../../components/TopBar/topbar"; import ChatInput from "../../components/ChatInput/chatinput"; import userData from "../../assets/datas/userdata.json"; import chatData from "../../assets/datas/chatdata.json"; @@ -22,26 +21,11 @@ function Chat() { const savedChats = JSON.parse( localStorage.getItem("chatMessages") || JSON.stringify(chatData) ); - const savedMessageId = JSON.parse( - localStorage.getItem("currMessageId") || JSON.stringify(chatData.length) - ); + const [messages, setMessages] = useState(savedChats); - const [messageId, setMessageId] = useState(savedMessageId); + const [messageId, setMessageId] = useState(chatData.length); const [nowUser, setNowUser] = useState(userData.users[0]); - const updateMessagesWithShowIcon = (messages: Message[]) => { - const updatedMessages = [...messages]; - - for (let i = 1; i < updatedMessages.length; i++) { - if (updatedMessages[i - 1].sender !== updatedMessages[i].sender) { - // 이전 메시지랑 sender가 다른경우 showIcon을 true로 설정 - updatedMessages[i - 1].showIcon = true; - } - } - - return updatedMessages; - }; - const handleSendMessage = (content: string) => { const newMessage: Message = { id: messageId, @@ -50,14 +34,20 @@ function Chat() { showIcon: false, }; + // 메세지가 추가됐을 때 가장 마지막 index의 메세지와 비교하여 showIcon 업데이트 + if ( + messages.length > 0 && + messages[messages.length - 1].sender !== nowUser.name + ) { + messages[messages.length - 1].showIcon = true; + } + const updatedMessages = [...messages, newMessage]; - const messagesWithShowIcon = updateMessagesWithShowIcon(updatedMessages); + setMessages(updatedMessages); - setMessages(messagesWithShowIcon); // 다음 메시지를 위해 messageId 업데이트 setMessageId(messageId + 1); // 로컬 스토리지에 채팅 데이터 저장 - localStorage.setItem("currMessageId", messageId.toString()); localStorage.setItem("chatMessages", JSON.stringify(updatedMessages)); }; @@ -67,6 +57,23 @@ function Chat() { ? userData.users[1] : userData.users[0] ); + + //사용자 바뀌면, 마지막 상대방 메시지 showIcon 처리해주는 부분 + setMessages((prev) => { + const lastMessageIndex = prev.length - 1; + //userChange 동시에 여러번 누르면 아이콘 동시에 뜨는것 방지하기 위해 + if (lastMessageIndex >= 1) { + if ( + prev[lastMessageIndex - 1].sender === prev[lastMessageIndex].sender && + prev[lastMessageIndex - 1].showIcon + ) { + prev[lastMessageIndex - 1].showIcon = false; + } + prev[lastMessageIndex].showIcon = true; + } + + return [...prev]; + }); }; const partner = @@ -74,7 +81,7 @@ function Chat() { ? userData.users[1] : userData.users[0]; - const messageContainers = messages.map((message: Message) => { + const messageContainers = messages.map((message: Message, index) => { const isCurrentUser = message.sender === nowUser.name; return ( @@ -127,7 +134,7 @@ const MessageContainer = styled.div` width: 100%; height: 100%; /* padding: 2.75rem 0; //Topbar랑 안겹치게 - margin-bottom: 2.5rem; //아래 부분이랑 안겹치게 */ + margin-bottom: 2.5rem; //아래 부분이랑 안겹치게 */ display: flex; flex-direction: column; align-items: center; diff --git a/src/styles/colors.css b/src/styles/colors.css index 2b533f7..8e4ea72 100644 --- a/src/styles/colors.css +++ b/src/styles/colors.css @@ -3,6 +3,5 @@ --gray-1 : #909093; --gray-2: rgba(118, 118, 128, 0.12); --gray-3: rgba(60, 60, 67, 0.30); - --blue : #3478F6;/*비강조, 두루두루*/ - + --blue : #3478F6; } \ No newline at end of file From f52395c3a23fd99dd892cbe65378e86d8dc9ec08 Mon Sep 17 00:00:00 2001 From: dhsin98 Date: Sun, 1 Oct 2023 16:03:19 +0900 Subject: [PATCH 12/21] =?UTF-8?q?[feat]:=20user=20change=20=ED=81=B4?= =?UTF-8?q?=EB=A6=AD=20=EC=9C=84=EC=B9=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ChatInput/chatinput.tsx | 3 +++ src/components/ChatTitle/chatTitle.tsx | 12 +++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/components/ChatInput/chatinput.tsx b/src/components/ChatInput/chatinput.tsx index 780d474..cbddfee 100644 --- a/src/components/ChatInput/chatinput.tsx +++ b/src/components/ChatInput/chatinput.tsx @@ -93,6 +93,9 @@ const Input = styled.textarea` align-items: center; } + &:hover { + cursor: pointer; + } padding-right: 2rem; //input 길어질때 방지 하기 위해 `; diff --git a/src/components/ChatTitle/chatTitle.tsx b/src/components/ChatTitle/chatTitle.tsx index 986f87e..df9fe68 100644 --- a/src/components/ChatTitle/chatTitle.tsx +++ b/src/components/ChatTitle/chatTitle.tsx @@ -14,10 +14,10 @@ const ChatTitle: React.FC = ({ changeUser, }) => { return ( - + - - + + {chatName} {chatID} @@ -39,6 +39,9 @@ const UserSection = styled.div` display: flex; flex-direction: column; width: 100%; + &:hover { + cursor: pointer; + } `; const UserName = styled.div` color: var(--black); @@ -64,6 +67,9 @@ const UserIcon = styled.img` width: 2.06244rem; height: 2.06238rem; margin-right: 0.64rem; + &:hover { + cursor: pointer; + } `; const Facetime = styled.img` margin-right: 1.12rem; From fe2f338e85cb7e260ba9fa3a57f12d2ba64bff6c Mon Sep 17 00:00:00 2001 From: dhsin98 Date: Mon, 9 Oct 2023 16:57:05 +0900 Subject: [PATCH 13/21] =?UTF-8?q?[fix]:=20QA=202=5F=EB=A9=94=EC=8B=9C?= =?UTF-8?q?=EC=A7=80=20=EB=B2=84=EB=B8=94=20=EA=B0=84=EA=B2=A9=20=EC=A1=B0?= =?UTF-8?q?=EC=A0=88=20&=20README=20update?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 13 ++++++++++++- src/components/Message/message.tsx | 4 +--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6ab8e98..1436026 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,20 @@ # 서론 -안녕하세요 🙌🏻 18기 프론트 운영진 김문기입니다. 이번 미션에서는 드디어 투두리스트에서 벗어나 새로운 프로젝트인 **messenger** 만들기를 진행합니다. +안녕하세요 🙌🏻 18기 프론트 신동현입니다. 이번 미션에서는 드디어 투두리스트에서 벗어나 새로운 프로젝트인 **messenger** 만들기를 진행합니다. 이번주는 특별히 **디자이너와의 협업**으로 진행되는 미션입니다. 디자이너분께서 열심히 리디자인 한 메신저 프로젝트를 여러분들께서 구현해주시면 됩니다. +## 구현된 기능 + +- 피그마를 보고 [배포링크](https://ceos-messenger.vercel.app/)과 같이 구현합니다. +- 디자인 시스템을 구축합니다. +- 채팅방 상단의 프로필아이콘, 프로필 이름을 클릭하면 사용자를 변경할 수 있습니다. +- 메세지를 보내면 채팅방 하단으로 스크롤을 이동시킵니다. +- 메세지에 유저 정보(프로필 사진, 이름)를 표시합니다. +- user와 message 데이터를 json 파일에 저장합니다. +- UI는 **반응형을 제외**하고 피그마파일을 따라서 진행합니다. +- input창에서 shiftenter 키 입력을 통해 여러 줄의 input을 받을 수 있습니다. + 동시에, 이번주부터는 새로 **TypeScript**를 적용해보려고 합니다. 프로젝트의 규모가 커지게 될 수록, 컴포넌트가 가지는 props의 종류 또한 다양해지게 됩니다. 무지성 코딩을 하다보면 가끔 이 props의 구성과 이름, 어떤 타입이 들어가야 하는지 헷갈리기 마련이죠. 보통 그럴 때 다시 컴포넌트 정의 부분으로 돌아가 props의 구성을 보고 오곤 합니다. diff --git a/src/components/Message/message.tsx b/src/components/Message/message.tsx index 878b530..d9f189c 100644 --- a/src/components/Message/message.tsx +++ b/src/components/Message/message.tsx @@ -34,7 +34,6 @@ const MessageContainer = styled.div<{ sender: string; nowUser: string }>` display: flex; align-items: center; margin: 0.44rem 0; - gap: 0.44rem; max-width: 100%; flex-direction: ${(props) => props.sender === props.nowUser ? "row-reverse" : "row"}; @@ -59,7 +58,6 @@ const MessageItem = styled.div<{ margin-left: ${(msg) => !msg.showIcon && msg.sender !== msg.nowUser ? "3.75rem" : "0"}; max-width: 90%; // 데이터가 길어질 경우 - font-size: 0.9375rem; font-weight: 400; line-height: normal; @@ -69,7 +67,7 @@ const MessageItem = styled.div<{ `; const UserIconShow = styled.img` - width: 2.06244rem; + width: 2.75rem; height: 2.06238rem; margin-left: 1rem; `; From de442f5ff97bc32be851df62a2efe49a6ca939de Mon Sep 17 00:00:00 2001 From: dhsin98 Date: Wed, 11 Oct 2023 17:07:38 +0900 Subject: [PATCH 14/21] =?UTF-8?q?[feat]:=20QA1-=20input=20bar=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/images/sendMessage.svg | 4 ++ src/components/ChatInput/chatinput.tsx | 58 +++++++++++++++++++++----- 2 files changed, 51 insertions(+), 11 deletions(-) create mode 100644 src/assets/images/sendMessage.svg diff --git a/src/assets/images/sendMessage.svg b/src/assets/images/sendMessage.svg new file mode 100644 index 0000000..aff31f1 --- /dev/null +++ b/src/assets/images/sendMessage.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/components/ChatInput/chatinput.tsx b/src/components/ChatInput/chatinput.tsx index cbddfee..ff16b6b 100644 --- a/src/components/ChatInput/chatinput.tsx +++ b/src/components/ChatInput/chatinput.tsx @@ -3,7 +3,7 @@ import styled from "styled-components"; import cameraIcon from "../../assets/images/Camera.svg"; import appStoreIcon from "../../assets/images/App-Store.svg"; import dictationIcon from "../../assets/images/Dictation.svg"; - +import sendIcon from "../../assets/images/sendMessage.svg"; //채팅 입력받는 component interface ChatInputProps { @@ -12,6 +12,7 @@ interface ChatInputProps { const ChatInput: React.FC = ({ onSend }) => { const [inputValue, setInputValue] = useState(""); + const [isInputFocused, setIsInputFocused] = useState(false); //여러줄 입력 위해 input=> textarea로 변경 const handleInputEnter = (e: React.KeyboardEvent) => { @@ -22,6 +23,7 @@ const ChatInput: React.FC = ({ onSend }) => { onSend(inputValue); } // 입력값 초기화 + setIsInputFocused(false); setInputValue(""); } if (e.key === "Enter" && e.shiftKey) { @@ -30,21 +32,46 @@ const ChatInput: React.FC = ({ onSend }) => { } }; + const handleSendClick = () => { + if (inputValue.trim() !== "") { + console.log("inputValue"); + onSend(inputValue); + } + setInputValue(""); + }; + + const imageClick = () => { + console.log("Click"); + }; + + const handleInputChange = (e: React.ChangeEvent) => { + setInputValue(e.target.value); + setIsInputFocused(e.target.value !== ""); // isInputFocused true로 설정 + }; + return ( - - + {!isInputFocused && } + {!isInputFocused && } + setInputValue(e.target.value)} + onChange={handleInputChange} onKeyPress={handleInputEnter} - //한글 입력의 경우: 마지막 단어가 input으로 들어가는 에러 방지하기 위해 + onFocus={() => setIsInputFocused(true)} + onBlur={() => setIsInputFocused(false)} + required /> - + {isInputFocused ? ( + //handleSendClick 작동?? + + ) : ( + + )} ); @@ -57,13 +84,14 @@ const BottomBarContainer = styled.div` position: relative; width: 100%; height: 2.5rem; + margin-left: 1.38rem; /* margin-bottom: 0.81rem; */ `; const Camera = styled.img` width: 1.92713rem; height: 1.51313rem; - margin-left: 1.06rem; + /* margin-left: 1.06rem; */ `; const AppStore = styled.img` @@ -93,9 +121,7 @@ const Input = styled.textarea` align-items: center; } - &:hover { - cursor: pointer; - } + padding-right: 2rem; //input 길어질때 방지 하기 위해 `; @@ -103,7 +129,7 @@ const InputContainer = styled.span` display: flex; align-items: center; flex-grow: 1; - width: 100%; + width: 90%; `; const Dictation = styled.img` @@ -113,4 +139,14 @@ const Dictation = styled.img` right: 1.25rem; `; +const Send = styled.img` + width: 1.6875rem; + height: 1.6875rem; + position: absolute; //input fiield 안에 위치 + right: 1.25rem; + &:hover { + cursor: pointer; + } +`; + export default ChatInput; From c479c9420569ada7fb92f2130ab2934afa4eb34e Mon Sep 17 00:00:00 2001 From: dhsin98 Date: Fri, 3 Nov 2023 17:10:41 +0900 Subject: [PATCH 15/21] =?UTF-8?q?[feat]:=20navigate=20=EC=B6=94=EA=B0=80,?= =?UTF-8?q?=20searchbar=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 93 +++++++------------ src/App.tsx | 25 ++++- src/assets/datas/chatdata.json | 70 ++++++++++---- src/assets/datas/userdata.json | 63 ++++++++++++- src/assets/images/Arrow.svg | 6 +- src/assets/images/BackIcon.svg | 3 + src/assets/images/BigIcon.svg | 3 + src/assets/images/BigIconLogo.svg | 4 + src/assets/images/Group.svg | 3 + src/assets/images/LeftArrow.svg | 5 + src/assets/images/SearchIcon.svg | 3 + src/assets/images/Searchbars.svg | 7 ++ src/assets/images/backArrow.svg | 3 + src/assets/images/bubbleStatusImg.svg | 18 ++++ src/assets/images/chatBackArrow.svg | 3 + src/assets/images/github.svg | 3 + src/assets/images/instagram.svg | 5 + src/assets/images/record.svg | 3 + .../images/{Icon.svg => rightarrowIcon.svg} | 0 src/components/ChatInput/chatinput.tsx | 1 - src/components/ChatTitle/chatTitle.tsx | 10 +- src/components/SearchBar/serachbar.tsx | 68 ++++++++++++++ src/components/TopBar/topbar.tsx | 43 ++++++++- src/pages/chat/chat.tsx | 91 +++++++++++++----- src/styles/globalStyle.ts | 8 ++ 25 files changed, 432 insertions(+), 109 deletions(-) create mode 100644 src/assets/images/BackIcon.svg create mode 100644 src/assets/images/BigIcon.svg create mode 100644 src/assets/images/BigIconLogo.svg create mode 100644 src/assets/images/Group.svg create mode 100644 src/assets/images/LeftArrow.svg create mode 100644 src/assets/images/SearchIcon.svg create mode 100644 src/assets/images/Searchbars.svg create mode 100644 src/assets/images/backArrow.svg create mode 100644 src/assets/images/bubbleStatusImg.svg create mode 100644 src/assets/images/chatBackArrow.svg create mode 100644 src/assets/images/github.svg create mode 100644 src/assets/images/instagram.svg create mode 100644 src/assets/images/record.svg rename src/assets/images/{Icon.svg => rightarrowIcon.svg} (100%) create mode 100644 src/components/SearchBar/serachbar.tsx diff --git a/README.md b/README.md index 1436026..3582bf2 100644 --- a/README.md +++ b/README.md @@ -1,77 +1,56 @@ -# 서론 +# 4주차 미션: React-Messenger 💌 -안녕하세요 🙌🏻 18기 프론트 신동현입니다. 이번 미션에서는 드디어 투두리스트에서 벗어나 새로운 프로젝트인 **messenger** 만들기를 진행합니다. +## 서론 -이번주는 특별히 **디자이너와의 협업**으로 진행되는 미션입니다. 디자이너분께서 열심히 리디자인 한 메신저 프로젝트를 여러분들께서 구현해주시면 됩니다. +안녕하세요 🙌🏻 프론트엔드 18기 신동현입니다. -## 구현된 기능 +다들 저번주 미션은 어떠셨나요? 이번주에는 저번 과제를 확장하여 보다 더 완성도 높은 메신저 서비스를 만들어 봅시다. -- 피그마를 보고 [배포링크](https://ceos-messenger.vercel.app/)과 같이 구현합니다. -- 디자인 시스템을 구축합니다. -- 채팅방 상단의 프로필아이콘, 프로필 이름을 클릭하면 사용자를 변경할 수 있습니다. -- 메세지를 보내면 채팅방 하단으로 스크롤을 이동시킵니다. -- 메세지에 유저 정보(프로필 사진, 이름)를 표시합니다. -- user와 message 데이터를 json 파일에 저장합니다. -- UI는 **반응형을 제외**하고 피그마파일을 따라서 진행합니다. -- input창에서 shiftenter 키 입력을 통해 여러 줄의 input을 받을 수 있습니다. +이번주 과제의 목표는 React에서 **Routing**을 구현하는 방법과 **상태를 관리**하는 방법에 대해 익숙해지는 것입니다. 해당 부분을 잘 고려하시면서 미션을 수행해 주세요! -동시에, 이번주부터는 새로 **TypeScript**를 적용해보려고 합니다. +또한, 이번주에는 디자이너 측에서 QA를 전달해주실 예정입니다. 전달받은 QA에 대해 디자이너와 소통 후, 이를 고쳐보시는 과정도 수행해주세요! -프로젝트의 규모가 커지게 될 수록, 컴포넌트가 가지는 props의 종류 또한 다양해지게 됩니다. 무지성 코딩을 하다보면 가끔 이 props의 구성과 이름, 어떤 타입이 들어가야 하는지 헷갈리기 마련이죠. 보통 그럴 때 다시 컴포넌트 정의 부분으로 돌아가 props의 구성을 보고 오곤 합니다. +그럼 이번주도 파이팅입니다 😤 -하지만 이럴 때, typescript를 적용한다면 컴포넌트의 구성과 그 이름, 심지어 타입까지 한번에 자동완성으로 편리하게 관리할 수 있고, 생산성이 증대되겠죠. +## 미션 -또한, **React Hooks**에 조금 더 익숙해지는 것을 목표로 합니다. 여러 Hook들이 있지만 그 중에서도 `useState`, `useEffect`, `useRef`를 중점적으로 사용해 보는 미션인데요, React를 사용하면서 굉장히 자주 쓰이는 Hook들이기 때문에 이 부분을 집중적으로 공부해 보아요! +### 미션 목표 -그럼 이번 미션도 파이팅입니다!!🎉 +- SPA의 개념을 이해하고, 그에 따른 라우팅을 구현합니다. +- 디자이너로부터 QA를 전달받고, 이에 대한 대응합니다. +- React에서 사용하는 상태 관리 방법에 익숙해집니다. +- UI 컴포넌트의 중복을 줄여 봅니다. +- 코드를 확장/재사용/리팩토링하는 방법을 이해합니다. -# 미션 +### 기한 -## Key Questions - -- JavaScript를 사용할때에 비해 TypeScript를 사용할 때의 장점은 무엇인가요? -- 디자이너로부터 전달받은 피그마 링크 혹은, 피그마 캡처본 -- 컴포넌트를 분리한 기준은 무엇인가요? -- 디자인 시스템을 적용하면서 느낀 점은 무엇인가요? -- 디자이너와 소통하며 느낀점은 무엇인가요? - -## 미션 목표 +2023년 11월 3일 금요일 (기한 엄수!) -- TypeScript를 사용해봅시다. -- useState로 컴포넌트의 상태를 관리합니다. -- useEffect와 useRef의 사용법을 이해합니다. -- styled-components를 통한 CSS-in-JS 및 CSS Preprocessor의 사용법에 익숙해집니다. +### 필수 요건 -## 기한 +- 친구 목록 페이지, 채팅 목록 페이지, 설정 페이지 세 부분으로 구성합니다. +- 채팅 목록을 누르면 3주차 미션에서 구현했던 채팅방으로 이동합니다. +- 검색 기능을 추가하여 검색한 내용과 일치하는 이름을 가진 사용자만 화면에 표시합니다. +- (선택) 각자 메신저에 추가하고 싶거나, 구현하고 싶은 기능 마음껏 구현합니다. ✨ +- Custom hooks를 통해 중복되는 로직을 줄입니다. -2023년 9월 29일 금요일 +### 선택 사항 -## 필수 구현 기능 +- Recoil, Redux 등의 상태 관리 라이브러리를 적용해 봅니다. +- Base UI component system을 통해 UI 컴포넌트의 코드 재사용성을 높입니다. -- 피그마를 보고 [결과화면](https://3th-fb-messenger.netlify.app)과 같이 구현합니다. -- 디자인 시스템을 구축합니다. -- 채팅방 상단의 프로필을 클릭하면 사용자를 변경할 수 있습니다. -- 메세지를 보내면 채팅방 하단으로 스크롤을 이동시킵니다. -- 메세지에 유저 정보(프로필 사진, 이름)를 표시합니다. -- user와 message 데이터를 json 파일에 저장합니다. -- UI는 **반응형을 제외**하고 피그마파일을 따라서 진행합니다. - -### 추가 구현 기능 - -- 더블 클릭 하면 감정표현을 추가합니다. -- 그 외 추가하고 싶은 기능이 있다면 마음껏 추가해 주세요! +## Key Questions -참고로 이번 과제는 다음주까지 이어지는 과제이므로 **확장성**을 충분히 고려해 주세요. 참고로 **4주차 과제에서는 유저 및 기능 추가와 Routing을** 진행합니다. 이를 위해 [recoil](https://recoiljs.org/ko/)이나 [redux](https://ko.redux.js.org/introduction/getting-started/)를 이용한 상태관리를 미리 해보시는 것을 추천합니다!! 모두 공식문서 많이 읽어보시고 자신만의 상태관리 조합도 찾아보면 재밌을 거에요 XD +- 디자이너로부터 받은 QA 목록 +- QA 반영한 커밋(or 브랜치) 링크 (커밋 분리 필수!!!) +- Routing +- SPA +- 상태관리 ## 링크 및 참고자료 -- [React docs - Hook](https://ko.reactjs.org/docs/hooks-intro.html) -- [React의 Hooks 완벽 정복하기](https://velog.io/@velopert/react-hooks#1-usestate) -- [useEffect 완벽 가이드](https://overreacted.io/ko/a-complete-guide-to-useeffect/) -- [코딩 컨벤션](https://ui.toast.com/fe-guide/ko_CODING-CONVENTION) -- [타입스크립트 핸드북](https://joshua1988.github.io/ts/intro.html) -- [리액트 프로젝트에서 타입스크립트 사용하기 (시리즈)](https://velog.io/@velopert/series/react-with-typescript) -- [디자인 시스템 구축기](https://yozm.wishket.com/magazine/detail/1830/) -- [[영상] : 컴포넌트에 대한 이해](https://www.youtube.com/watch?v=21eiJc90ggo) -- [Styled Component로 디자인 시스템 구축하기](https://zaat.dev/blog/building-a-design-system-in-react-with-styled-components/) -- [ts 절대경로 설정하기](https://tesseractjh.tistory.com/232) +- [React Router v6 튜토리얼](https://velog.io/@velopert/react-router-v6-tutorial) +- [(선택) react-router v6에서는 어떤 것들이 변했을까?](https://blog.woolta.com/categories/1/posts/211) +- [React 상태 관리 가이드](https://www.stevy.dev/react-state-management-guide/) +- [Flux 패턴이란?](https://velog.io/@huurray/React%EC%9D%98-%ED%83%84%EC%83%9D%EA%B3%BC-Flux-%ED%8C%A8%ED%84%B4%EC%97%90-%EB%8C%80%ED%95%98%EC%97%AC) +- [useReducer](https://www.daleseo.com/react-hooks-use-reducer/) diff --git a/src/App.tsx b/src/App.tsx index c6479fe..06e1004 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,8 +1,29 @@ import { BrowserRouter, Routes, Route } from "react-router-dom"; -// import { useNavigate } from "react-router-dom"; +import { useNavigate } from "react-router-dom"; import Chat from "./pages/chat/chat"; +import ChatList from "./pages/chatlist/chatList"; +import Profile from "./pages/profile/profile"; +import Friends from "./pages/friends/friends"; +import StatusBar from "./components/StatusBar/statusbar"; + +import chatData from "./assets/datas/chatdata.json"; +import userData from "./assets/datas/userdata.json"; + function App() { - return ; + return ( +
+ + {/* */} + + } /> + } /> + } /> + } /> + } /> + + +
+ ); } export default App; diff --git a/src/assets/datas/chatdata.json b/src/assets/datas/chatdata.json index 43f1fbf..be26c84 100644 --- a/src/assets/datas/chatdata.json +++ b/src/assets/datas/chatdata.json @@ -1,80 +1,118 @@ -[ +{"2":[ { "id": 0, "sender": "신동현", "content": "ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ", - "showIcon": false + "showIcon": false, + "timestamp": "2023-11-01T04:29:17.011Z" + + }, { "id": 1, "sender": "신동현", "content": "아니 나 졸작", - "showIcon": false + "showIcon": false, + "timestamp": "2023-11-01T04:29:17.011Z" }, { "id": 2, "sender": "신동현", "content": "작업하는데 천이 부족해서", - "showIcon": false + "showIcon": false, + "timestamp": "2023-11-01T04:29:17.011Z" }, { "id": 3, "sender": "신동현", "content": "동대문 들렀다가 집 감", - "showIcon": true + "showIcon": true, + "timestamp": "2023-11-01T04:29:17.011Z" }, { "id": 4, "sender": "이예진", "content": "그것 참 불쌍하게 되었군", - "showIcon": false + "showIcon": false, + "timestamp": "2023-11-01T04:29:17.011Z" }, { "id": 5, "sender": "이예진", "content": "동대문에서 집까지", - "showIcon": false + "showIcon": false, + "timestamp": "2023-11-01T04:29:17.011Z" }, { "id": 6, "sender": "이예진", "content": "한 시간 넘게 걸리지 않아?", - "showIcon": true + "showIcon": true, + "timestamp": "2023-11-01T04:29:17.011Z" }, { "id": 7, "sender": "신동현", "content": "한 시간 반? 정도", - "showIcon": false + "showIcon": false, + "timestamp": "2023-11-01T04:29:17.011Z" }, { "id": 8, "sender": "신동현", "content": "그렇게 멀지는 않은데", - "showIcon": false + "showIcon": false, + "timestamp": "2023-11-01T04:29:17.011Z" }, { "id": 9, "sender": "신동현", "content": "짐이 많아서 힘든 겨", - "showIcon": true + "showIcon": true, + "timestamp": "2023-11-01T04:29:17.011Z" }, { "id": 10, "sender": "이예진", "content": "이따가 갈 때", - "showIcon": false + "showIcon": false, + "timestamp": "2023-11-01T04:29:17.011Z" }, { "id": 11, "sender": "이예진", "content": "심심하면 영통하자", - "showIcon": true + "showIcon": true, + "timestamp": "2023-11-01T04:29:17.011Z" }, { "id": 12, "sender": "신동현", "content": "오키 알겠음\n그럼 이따가 연락할게", - "showIcon": false - } -] \ No newline at end of file + "showIcon": false, + "timestamp": "2023-11-01T04:29:17.011Z" + }], +"5":[ + { + "id": 0, + "sender": "신동현", + "content": "안녕하세여", + "showIcon": false, + "timestamp": "2023-11-03T07:44:53.152Z" + }, + { + "id": 1, + "sender": "신동현", + "content": "ㅎㅎㅎ", + "showIcon": false, + "timestamp": "2023-11-03T07:44:55.011Z" + }, + { + "id": 2, + "sender": "고세희 선배님", + "content": "ㅎㅇㅎㅇ", + "showIcon": false, + "timestamp": "2023-11-03T07:45:45.536Z" + } +] +} \ No newline at end of file diff --git a/src/assets/datas/userdata.json b/src/assets/datas/userdata.json index abc940f..6b86792 100644 --- a/src/assets/datas/userdata.json +++ b/src/assets/datas/userdata.json @@ -3,15 +3,72 @@ { "id": 1, "name": "신동현", - "instagram":"@s_d0nghyun" + "instagram":"@s_d0nghyun", + "status":"" }, { "id": 2, "name": "이예진", - "instagram":"@leebyvae" + "instagram":"@leebyvae", + "status":"" - } + }, + { + "id": 3, + "name": "강은정", + "instagram":"@0o0go", + "status":"👍" + + },{ + "id": 4, + "name": "강은비", + "instagram":"@siverrainy_", + "status":"날씨 완전 가을🍁" + + },{ + "id": 5, + "name": "고세희 선배님", + "instagram":"@saysehi", + "status":"" + + },{ + "id": 6, + "name": "곽수연", + "instagram":"@jusdjkl", + "status":"우하하하" + + },{ + "id": 7, + "name": "권은수", + "instagram":"@jusdjkl", + "status":"" + + },{ + "id": 8, + "name": "김진솔", + "instagram":"@jusdjkl", + "status":"가을가을가을" + + },{ + "id": 9, + "name": "김가영", + "instagram":"@odeegayo", + "status":"" + + },{ + "id": 10, + "name": "김하은 오빠", + "instagram":"@ggksenvl", + "status":"" + + },{ + "id":11, + "name": "문지영 언니", + "instagram":"@m00nzi0", + "status":"공부?" + + } ] } diff --git a/src/assets/images/Arrow.svg b/src/assets/images/Arrow.svg index 0c1506b..41cece8 100644 --- a/src/assets/images/Arrow.svg +++ b/src/assets/images/Arrow.svg @@ -1,5 +1,3 @@ - - - - + + diff --git a/src/assets/images/BackIcon.svg b/src/assets/images/BackIcon.svg new file mode 100644 index 0000000..01ab4ed --- /dev/null +++ b/src/assets/images/BackIcon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/BigIcon.svg b/src/assets/images/BigIcon.svg new file mode 100644 index 0000000..06ac6c6 --- /dev/null +++ b/src/assets/images/BigIcon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/BigIconLogo.svg b/src/assets/images/BigIconLogo.svg new file mode 100644 index 0000000..668378c --- /dev/null +++ b/src/assets/images/BigIconLogo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/images/Group.svg b/src/assets/images/Group.svg new file mode 100644 index 0000000..f04ec64 --- /dev/null +++ b/src/assets/images/Group.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/LeftArrow.svg b/src/assets/images/LeftArrow.svg new file mode 100644 index 0000000..0c1506b --- /dev/null +++ b/src/assets/images/LeftArrow.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/images/SearchIcon.svg b/src/assets/images/SearchIcon.svg new file mode 100644 index 0000000..a8b2328 --- /dev/null +++ b/src/assets/images/SearchIcon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/Searchbars.svg b/src/assets/images/Searchbars.svg new file mode 100644 index 0000000..e1f0272 --- /dev/null +++ b/src/assets/images/Searchbars.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/assets/images/backArrow.svg b/src/assets/images/backArrow.svg new file mode 100644 index 0000000..6ec2b83 --- /dev/null +++ b/src/assets/images/backArrow.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/bubbleStatusImg.svg b/src/assets/images/bubbleStatusImg.svg new file mode 100644 index 0000000..c0575ca --- /dev/null +++ b/src/assets/images/bubbleStatusImg.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/assets/images/chatBackArrow.svg b/src/assets/images/chatBackArrow.svg new file mode 100644 index 0000000..40a5259 --- /dev/null +++ b/src/assets/images/chatBackArrow.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/github.svg b/src/assets/images/github.svg new file mode 100644 index 0000000..dd1591b --- /dev/null +++ b/src/assets/images/github.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/instagram.svg b/src/assets/images/instagram.svg new file mode 100644 index 0000000..4a05ea4 --- /dev/null +++ b/src/assets/images/instagram.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/images/record.svg b/src/assets/images/record.svg new file mode 100644 index 0000000..ea6718a --- /dev/null +++ b/src/assets/images/record.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/Icon.svg b/src/assets/images/rightarrowIcon.svg similarity index 100% rename from src/assets/images/Icon.svg rename to src/assets/images/rightarrowIcon.svg diff --git a/src/components/ChatInput/chatinput.tsx b/src/components/ChatInput/chatinput.tsx index ff16b6b..ff90c54 100644 --- a/src/components/ChatInput/chatinput.tsx +++ b/src/components/ChatInput/chatinput.tsx @@ -118,7 +118,6 @@ const Input = styled.textarea` } &::placeholder { display: flex; - align-items: center; } diff --git a/src/components/ChatTitle/chatTitle.tsx b/src/components/ChatTitle/chatTitle.tsx index df9fe68..848ca1d 100644 --- a/src/components/ChatTitle/chatTitle.tsx +++ b/src/components/ChatTitle/chatTitle.tsx @@ -1,7 +1,8 @@ import styled from "styled-components"; -import arrowIcon from "../../assets/images/Arrow.svg"; +import arrowIcon from "../../assets/images/chatBackArrow.svg"; import userIcon from "../../assets/images/User.svg"; import facetime from "../../assets/images/Facetime.svg"; +import { useNavigate } from "react-router-dom"; interface ChatHeaderProps { chatName: string; @@ -13,9 +14,14 @@ const ChatTitle: React.FC = ({ chatID, changeUser, }) => { + const navigate = useNavigate(); return ( - + navigate("/chatlist")} + /> {chatName} diff --git a/src/components/SearchBar/serachbar.tsx b/src/components/SearchBar/serachbar.tsx new file mode 100644 index 0000000..1f75925 --- /dev/null +++ b/src/components/SearchBar/serachbar.tsx @@ -0,0 +1,68 @@ +// searchIcon & recording + +import styled from "styled-components"; +import { ChangeEvent } from "react"; +import searchIcon from "../../assets/images/SearchIcon.svg"; +import recordIcon from "../../assets/images/record.svg"; + +interface SearchBarProps { + value: string; + onChange: (event: ChangeEvent) => void; +} +const SearchBar: React.FC = ({ value, onChange }) => { + return ( + + + + + + ); +}; + +const InputContainer = styled.span` + display: flex; + align-items: center; + width: 100%; + /* height: 2.25rem; */ + padding: 0 1rem; + font-family: "SF Pro Text"; +`; +const Input = styled.input` + width: 100%; + font-family: "SF Pro Text"; + height: 2.25rem; + padding-left: 2.87rem; + background: var(--gray-2); + &:focus { + box-shadow: none; + } + outline: none; + border: none; + border-radius: 0.5rem; +`; + +const Search = styled.img` + position: absolute; + font-size: 1.0625rem; + font-style: normal; + font-weight: 400; + line-height: normal; + letter-spacing: -0.017rem; + left: 1.5rem; +`; + +const Record = styled.img` + position: absolute; + font-size: 1.0625rem; + font-style: normal; + font-weight: 400; + line-height: normal; + letter-spacing: -0.017rem; + right: 1.63rem; +`; +export default SearchBar; diff --git a/src/components/TopBar/topbar.tsx b/src/components/TopBar/topbar.tsx index 766ea79..4f3a437 100644 --- a/src/components/TopBar/topbar.tsx +++ b/src/components/TopBar/topbar.tsx @@ -1,9 +1,48 @@ import styled from "styled-components"; import StatusBar from "../StatusBar/statusbar"; import ChatTitle from "../ChatTitle/chatTitle"; - +import backArrow from "../../assets/images/backArrow.svg"; +import backText from "../../assets/images/BackIcon.svg"; +import editIcon from "../../assets/images/Edit.svg"; +import { useNavigate } from "react-router-dom"; const TopBar = () => { - return ; + const navigate = useNavigate(); + const handleBackClick = () => { + navigate("/profile"); + }; + return ( + +
+ + +
+ + + ); }; +const TopBarContainer = styled.div` + width: 100%; + height: 1.25rem; + padding: 0rem 1.12rem; + margin-top: 0.81rem; + margin-bottom: 1rem; + position: relative; + display: flex; + justify-content: space-between; + align-items: center; +`; +const BackArrow = styled.img` + width: 0.56088rem; + height: 0.9745rem; + margin-right: 0.31rem; +`; +const BackText = styled.img` + width: 2.1875rem; + height: 1.125rem; +`; +const EditIcon = styled.img` + width: 1.3125rem; + height: 1.25rem; +`; export default TopBar; diff --git a/src/pages/chat/chat.tsx b/src/pages/chat/chat.tsx index 885f2d4..7b7d6dd 100644 --- a/src/pages/chat/chat.tsx +++ b/src/pages/chat/chat.tsx @@ -9,29 +9,68 @@ import Message from "../../components/Message/message"; import "../../styles/colors.css"; import bottomBar from "../../assets/images/LightBottomBar.svg"; import StatusBar from "../../components/StatusBar/statusbar"; - +import { useParams } from "react-router-dom"; interface Message { id: number; sender: string; content: string; showIcon: boolean; + timestamp: Date; // message list 페이지에 시간 추가 필요헤서 필드 추가 } -function Chat() { - const savedChats = JSON.parse( - localStorage.getItem("chatMessages") || JSON.stringify(chatData) - ); +interface ChatData { + [key: string]: Message[]; +} - const [messages, setMessages] = useState(savedChats); - const [messageId, setMessageId] = useState(chatData.length); +function Chat() { + // URL에서 채팅 ID를 추출, 예: /chat/1 + const { chatId } = useParams<{ chatId: string }>(); + const [messages, setMessages] = useState([]); + const [messageId, setMessageId] = useState(0); const [nowUser, setNowUser] = useState(userData.users[0]); + useEffect(() => { + if (typeof chatId !== "undefined") { + const savedChats = JSON.parse( + localStorage.getItem("chatMessages") || JSON.stringify(chatData) + ) as ChatData; + setMessages(savedChats[chatId] || []); + setMessageId(savedChats[chatId]?.length || 0); + } + }, [chatId]); + + // 사용자가 바뀔 때마다 실행 + useEffect(() => { + setMessages((prevMessages) => { + const newMessages = [...prevMessages]; + const lastMessageIndex = newMessages.length - 1; + + if (lastMessageIndex >= 0) { + // 모든 메시지를 순회하며 showIcon을 설정 + for (let i = 0; i < lastMessageIndex; i++) { + const currentSender = newMessages[i].sender; + const nextSender = newMessages[i + 1].sender; + newMessages[i].showIcon = currentSender !== nextSender; + } + // 마지막 메시지는 항상 showIcon을 true 로 설정 + newMessages[lastMessageIndex].showIcon = true; + } + + return newMessages; + }); + }, [nowUser]); + + if (!chatId) { + return
없는 친구 ID입니다.
; + } + const handleSendMessage = (content: string) => { const newMessage: Message = { id: messageId, sender: nowUser.name, content, showIcon: false, + timestamp: new Date(), // 현재 시간 추가 }; // 메세지가 추가됐을 때 가장 마지막 index의 메세지와 비교하여 showIcon 업데이트 @@ -48,37 +87,48 @@ function Chat() { // 다음 메시지를 위해 messageId 업데이트 setMessageId(messageId + 1); // 로컬 스토리지에 채팅 데이터 저장 - localStorage.setItem("chatMessages", JSON.stringify(updatedMessages)); - }; + const savedChats = JSON.parse( + localStorage.getItem("chatMessages") || JSON.stringify(chatData) + ) as ChatData; + savedChats[chatId] = updatedMessages; + localStorage.setItem("chatMessages", JSON.stringify(savedChats)); + }; const changeUser = () => { setNowUser((prev) => prev.name === userData.users[0].name - ? userData.users[1] + ? userData.users[chatIdNumber - 1] : userData.users[0] ); - //사용자 바뀌면, 마지막 상대방 메시지 showIcon 처리해주는 부분 - setMessages((prev) => { - const lastMessageIndex = prev.length - 1; + setMessages((prevMessages) => { + const newMessages = [...prevMessages]; + const lastMessageIndex = newMessages.length - 1; //userChange 동시에 여러번 누르면 아이콘 동시에 뜨는것 방지하기 위해 if (lastMessageIndex >= 1) { + const lastMessageSender = newMessages[lastMessageIndex].sender; + const secondLastMessageSender = + newMessages[lastMessageIndex - 1].sender; + + // 직전 메시지와 비교하여 showIcon을 설정합니다. if ( - prev[lastMessageIndex - 1].sender === prev[lastMessageIndex].sender && - prev[lastMessageIndex - 1].showIcon + secondLastMessageSender === lastMessageSender && + newMessages[lastMessageIndex - 1].showIcon ) { - prev[lastMessageIndex - 1].showIcon = false; + newMessages[lastMessageIndex - 1].showIcon = false; } - prev[lastMessageIndex].showIcon = true; + newMessages[lastMessageIndex].showIcon = true; } - return [...prev]; + return newMessages; }); }; + //chatId를 integer 값으로 바꿔준 결과 + const chatIdNumber = +chatId; const partner = nowUser.name === userData.users[0].name - ? userData.users[1] + ? userData.users[chatIdNumber - 1] : userData.users[0]; const messageContainers = messages.map((message: Message, index) => { @@ -104,13 +154,10 @@ function Chat() { chatID={partner.instagram} changeUser={changeUser} /> - {messageContainers} - -
diff --git a/src/styles/globalStyle.ts b/src/styles/globalStyle.ts index fcac5f5..68a3488 100644 --- a/src/styles/globalStyle.ts +++ b/src/styles/globalStyle.ts @@ -7,6 +7,8 @@ const GlobalStyle = createGlobalStyle` box-sizing:border-box; padding: 0; margin: 0; + font-family: "SF Pro Text", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + } html,body{ height:100%; @@ -19,11 +21,17 @@ const GlobalStyle = createGlobalStyle` margin: 0; padding:0; display: flex; + flex-direction:column; justify-content: center; height:100vh; width:100vw; } + :hover { + cursor: pointer; + } + + `; export default GlobalStyle; \ No newline at end of file From 20ae6e1526cbfa86525672230b48993a454488ea Mon Sep 17 00:00:00 2001 From: dhsin98 Date: Fri, 3 Nov 2023 17:11:58 +0900 Subject: [PATCH 16/21] =?UTF-8?q?profile(=EA=B8=B0=EB=B3=B8=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80)=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/profile/profile.tsx | 167 ++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 src/pages/profile/profile.tsx diff --git a/src/pages/profile/profile.tsx b/src/pages/profile/profile.tsx new file mode 100644 index 0000000..7959f86 --- /dev/null +++ b/src/pages/profile/profile.tsx @@ -0,0 +1,167 @@ +import styled from "styled-components"; +import bottomBar from "../../assets/images/LightBottomBar.svg"; +import StatusBar from "../../components/StatusBar/statusbar"; +import BigIconLogo from "../../assets/images/BigIconLogo.svg"; +import Github from "../../assets/images/github.svg"; +import RightArrow from "../../assets/images/Arrow.svg"; +import Instagram from "../../assets/images/instagram.svg"; +import { useNavigate } from "react-router-dom"; +import userData from "../../assets/datas/userdata.json"; + +export default function Profile() { + const navigate = useNavigate(); + + return ( +
+ + + + My Profile + + + {userData.users[0].name} + donghyun98@gmail.com + + window.open("https://github.com/dhshin98", "_blank")} + > + + + GitHub + https://github.com/dhshin98 + + + + + + + + Instagram + @인스타 아이디 + + + + + navigate("/chatlist")}> + + + Instagram + {userData.users[0].instagram} + + + + + +
+ ); +} +const ProfileContainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; + position: absolute; + border-radius: 1.5rem; + height: 100vh; + width: 100vw; + border: solid var(--gray-1); +`; + +const Title = styled.text` + font-family: "SF Pro Text"; + font-size: 2.125rem; + font-style: normal; + font-weight: 700; + line-height: 80%; + width: 100%; + padding-left: 1.37rem; + padding-top: 2.94rem; +`; +const BigIcon = styled.img` + width: 5.9375rem; + height: 5.93725rem; + margin-top: 2.62rem; +`; +const Rectangle = styled.div` + width: 90%; + height: 4.1875rem; + border-radius: 1.25rem; + display: flex; + align-items: center; + margin: 0rem 1rem; + box-shadow: 0px 0px 15px 0px rgba(0, 0, 0, 0.1); + margin-bottom: 0.69rem; +`; +const Icon = styled.img` + /* width: ${(props) => + props.src === "Instagram" ? "2.1875rem" : "2.77813rem"}; + height: ${(props) => + props.src === "Instagram" ? "2.1875rem" : "2.77813rem"}; + */ + margin: 0.69rem 0.35rem 0.72rem 0.75rem; + width: ${(props) => props.width}; + height: ${(props) => props.width}; +`; +const RectangleInfo = styled.div` + display: flex; + flex-direction: column; + gap: 0.06rem; + height: 100%; + width: 100%; + padding-left: 1rem; + padding-right: 0.44rem; + padding-bottom: 1.12rem; + padding-top: 0.94rem; +`; + +const RightArrowIcon = styled.img` + width: 1rem; + height: 1rem; + position: relative; + margin-right: 1.44rem; +`; +const Subtitle = styled.text` + font-family: "SF Pro Text"; + font-size: 0.9375rem; + font-style: normal; + font-weight: 400; + line-height: normal; +`; +const SubtitleInfo = styled.text` + color: var(--gray-1); + font-family: "SF Pro Text"; + font-size: 0.75rem; + font-style: normal; + font-weight: 400; + line-height: 125%; +`; + +const BottomBarIcon = styled.img` + width: 100%; + height: 2.125rem; + margin-bottom: 0; + /* position: relative; */ + position: absolute; + bottom: 0; +`; + +const Name = styled.text` + color: var(--black); + font-family: "SF Pro Text"; + font-size: 1.125rem; + font-style: normal; + font-weight: 600; + line-height: normal; + margin-top: 0.69rem; +`; + +const Email = styled.text` + color: var(--gray-1); + text-align: right; + font-family: "SF Pro Text"; + font-size: 0.75rem; + font-style: normal; + font-weight: 400; + line-height: 0.9375rem; + text-decoration-line: underline; + margin-bottom: 3.25rem; +`; From 703ff0509e6770f3eae180b801fe996b47e88335 Mon Sep 17 00:00:00 2001 From: dhsin98 Date: Fri, 3 Nov 2023 17:12:42 +0900 Subject: [PATCH 17/21] =?UTF-8?q?[feat]:=20chatlist=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/chatlist/chatList.tsx | 278 ++++++++++++++++++++++++++++++++ 1 file changed, 278 insertions(+) create mode 100644 src/pages/chatlist/chatList.tsx diff --git a/src/pages/chatlist/chatList.tsx b/src/pages/chatlist/chatList.tsx new file mode 100644 index 0000000..27f6d7a --- /dev/null +++ b/src/pages/chatlist/chatList.tsx @@ -0,0 +1,278 @@ +import styled from "styled-components"; +import { useState, useEffect } from "react"; +import bottomBar from "../../assets/images/LightBottomBar.svg"; +import StatusBar from "../../components/StatusBar/statusbar"; +import TopBar from "../../components/TopBar/topbar"; +import SearchBar from "../../components/SearchBar/serachbar"; +import friendsIcon from "../../assets/images/Friends.svg"; +import { useNavigate } from "react-router-dom"; +import userData from "../../assets/datas/userdata.json"; +import chatData from "../../assets/datas/chatdata.json"; +import groupIcon from "../../assets/images/Group.svg"; +import rightarrowIcon from "../../assets/images/rightarrowIcon.svg"; +interface Message { + id: number; + sender: string; + content: string; + showIcon: boolean; + timestamp: string; //메시지 보내는 시간 정보 추가 + unread?: boolean; // 읽지 않은 메시지인지 여부를 나타내는 속성 추가 +} +interface User { + id: number; + name: string; +} +interface ChatData { + [key: string]: Message[]; +} + +function formatTimestamp(isoString?: string) { + if (!isoString) return "No last message"; + + const date = new Date(isoString); + date.setHours(0, 0, 0, 0); + + const now = new Date(); + now.setHours(0, 0, 0, 0); + + const diffTime = now.getTime() - date.getTime(); + const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24)); + + // 날짜 차이에 따른 문자열 반환 + //오늘은 시간 정보, 전날은 "어제", 그 전날은 '~일전'으로 반환해줌 + if (diffDays === 0) { + const date = new Date(isoString); + let hours = date.getHours(); + const minutes = date.getMinutes(); + const ampm = hours >= 12 ? "PM" : "AM"; + hours = hours % 12; + hours = hours ? hours : 12; + const strHours = hours < 10 ? `0${hours}` : `${hours}`; + const strMinutes = minutes < 10 ? `0${minutes}` : `${minutes}`; + return `${strHours}:${strMinutes} ${ampm}`; + } else if (diffDays === 1) { + return "어제"; + } else { + return `${diffDays}일 전`; + } +} + +export default function ChatList() { + const navigate = useNavigate(); + const [lastMessages, setLastMessages] = useState< + Record + >({}); + + const [sortedUsers, setSortedUsers] = useState(userData.users); + + useEffect(() => { + // localStorage에서 chatData 가져오기 + const savedChats = JSON.parse( + localStorage.getItem("chatMessages") || JSON.stringify(chatData) + ) as ChatData; + + // 모든 사용자의 마지막 메시지와 시간 가져오기 + const newLastMessages: Record = {}; + const usersWithLastMessage = userData.users + .filter((user) => savedChats[user.id.toString()]?.length > 0) // chatData가 있는 사용자만 가져옴 + .map((user) => { + const userChat = savedChats[user.id.toString()]; + const lastChat = userChat ? userChat[userChat.length - 1] : null; + newLastMessages[user.id] = lastChat; + return { + ...user, + lastMessageTimestamp: lastChat + ? new Date(lastChat.timestamp) + : new Date(0), + }; + }); + + const newLastMessagesWithUnread: Record = {}; + // 모든 사용자의 마지막 메시지에 대한 unread 상태 업데이트 : + // 마지막 sender가 내가 아닌 경우 unread 로 처리 + userData.users.forEach((user) => { + const lastMsg = newLastMessages[user.id]; + if (lastMsg) { + const updatedMessage = { + ...lastMsg, + unread: lastMsg.sender !== "신동현", + }; + newLastMessagesWithUnread[user.id] = updatedMessage; + } + }); + + setLastMessages(newLastMessagesWithUnread); + + // 타임스탬프 기준으로 사용자 정렬: 가장 최근 메시지가 위에 오게 함 + const sortedUsers = usersWithLastMessage.sort( + (a, b) => + b.lastMessageTimestamp.getTime() - a.lastMessageTimestamp.getTime() + ); + setSortedUsers(sortedUsers); + }, []); + + //searchBar에서 검색하면, 필터링된 사용자만 뜨게 하는 부분 + const [searchTerm, setSearchTerm] = useState(""); + function getFilteredUsers(users: User[], searchTerm: string) { + if (!searchTerm) return users; + return users.filter((user) => + user.name.toLowerCase().includes(searchTerm.toLowerCase()) + ); + } + const filteredUsers = getFilteredUsers(sortedUsers, searchTerm); + + return ( +
+ + + + + Chat + <img src={friendsIcon} onClick={() => navigate("/friends")} /> + + setSearchTerm(e.target.value)} + /> + + {filteredUsers.map((user, index) => ( + navigate(`/chat/${user.id}`)} + > + {lastMessages[user.id]?.unread && } + + + + {user.name} + + + + + + + {lastMessages[user.id]?.content || ""} + + + + ))} + + + +
+ ); +} + +const ProfileContainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + position: absolute; + border-radius: 1.5rem; + height: 100vh; + width: 100vw; + border: solid rgba(144, 144, 147, 0.5); +`; + +const Title = styled.span` + font-family: "SF Pro Text"; + font-size: 2.125rem; + font-style: normal; + font-weight: 700; + line-height: 80%; + width: 100%; + padding-left: 1.37rem; + margin-bottom: 1.75rem; + display: flex; + justify-content: space-between; + padding-right: 1.12rem; +`; +const BottomBarIcon = styled.img` + width: 100%; + height: 2.125rem; + margin-bottom: 0; + /* position: relative; */ + position: absolute; + bottom: 0; +`; + +const Name = styled.text` + font-family: "SF Pro Text"; + font-size: 1.125rem; + font-style: normal; + font-weight: 600; + line-height: normal; +`; +const LastMessage = styled.div` + /* width: 17.25rem; */ + height: 2.25rem; + color: var(--gray-1); + font-family: "SF Pro Text"; + font-size: 0.9375rem; + font-style: normal; + font-weight: 400; + line-height: normal; +`; + +const ChatlistContainer = styled.div` + overflow-y: auto; // 세로 스크롤 + width: 100%; + height: 100%; +`; +const ChatlistItem = styled.div` + width: 100%; + height: 5.25rem; + display: flex; + padding: 0.81rem 1.56rem; + position: relative; + + &::after { + content: ""; + position: absolute; + left: 5.1rem; + right: 0; + bottom: 0; + border-bottom: 1px solid var(--gray-1); + } +`; + +const Time = styled.div` + font-family: "SF Pro Text"; + font-size: 0.9375rem; + font-style: normal; + font-weight: 400; + line-height: normal; + color: var(--gray-1); +`; +const ChatlistData = styled.div` + width: 100%; + display: flex; + flex-direction: column; + padding-left: 0.87rem; +`; +const ChatlistDataTitle = styled.div` + width: 100%; + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 0.25rem; +`; + +const TimeContainer = styled.div` + display: flex; +`; +const UnreadIndicator = styled.div` + position: absolute; + top: 50%; + left: 0.8rem; + width: 0.56rem; + height: 0.56rem; + border-radius: 50%; + background-color: var(--blue); +`; +const GroupIcon = styled.img` + margin-left: 0.2rem; +`; From a28b584db3efad6c56f9b5f06b06a919b2133dc2 Mon Sep 17 00:00:00 2001 From: dhsin98 Date: Fri, 3 Nov 2023 17:13:19 +0900 Subject: [PATCH 18/21] =?UTF-8?q?[feat]:=20=EC=B9=9C=EA=B5=AC=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/friends/friends.tsx | 190 ++++++++++++++++++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 src/pages/friends/friends.tsx diff --git a/src/pages/friends/friends.tsx b/src/pages/friends/friends.tsx new file mode 100644 index 0000000..c8380c6 --- /dev/null +++ b/src/pages/friends/friends.tsx @@ -0,0 +1,190 @@ +import styled from "styled-components"; +import bottomBar from "../../assets/images/LightBottomBar.svg"; +import StatusBar from "../../components/StatusBar/statusbar"; +import TopBar from "../../components/TopBar/topbar"; +import searchBar1 from "../../assets/images/Searchbars.svg"; +import SearchBar from "../../components/SearchBar/serachbar"; +import friendsIcon from "../../assets/images/Friends.svg"; +import { useNavigate } from "react-router-dom"; +import userData from "../../assets/datas/userdata.json"; +import bigIcon from "../../assets/images/BigIcon.svg"; +import bubbleImg from "../../assets/images/bubbleStatusImg.svg"; +import { useState } from "react"; +interface User { + id: number; + name: string; + instagram: string; + status: string; +} +//친구 목록에서 status가 존재하는지 여부 +interface FriendsListItemProps { + statusExist: boolean; +} + +export default function Friends() { + const navigate = useNavigate(); + const [searchTerm, setSearchTerm] = useState(""); + const [friends, setFriends] = useState(userData.users); + function getFilteredUsers(users: User[], searchTerm: string) { + if (!searchTerm) return users; + const filteredUsers = users.filter((user) => + user.name.toLowerCase().includes(searchTerm.toLowerCase()) + ); + return filteredUsers; + } + const filteredUsers = getFilteredUsers(friends, searchTerm); + + return ( +
+ + + + Friends + { + setSearchTerm(e.target.value); + }} + /> + + {filteredUsers.map((user, index) => ( + navigate(`/chat/${user.id}`)} + statusExist={!!user.status} // user.status boolean 가져와서 + > + {user.status && ( + //상메가 설정된 경우 버블 안에 보여줌 + + Status bubble + {user.status} + + )} + + + {user.name} + {user.instagram} + + ))} + + + +
+ ); +} + +const ProfileContainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; + position: absolute; + border-radius: 1.5rem; + height: 100%; + width: 100vw; + border: solid var(--gray-1); +`; + +const Title = styled.text` + font-family: "SF Pro Text"; + font-size: 2.125rem; + font-style: normal; + font-weight: 700; + line-height: 80%; + width: 100%; + padding-left: 1.37rem; + margin-bottom: 1.75rem; + display: flex; + justify-content: space-between; + padding-right: 1.12rem; +`; +const BottomBarIcon = styled.img` + width: 100%; + height: 2.125rem; + margin-bottom: 0; + /* position: relative; */ + position: absolute; + bottom: 0; +`; + +const FriendsContainer = styled.div` + display: flex; + flex-flow: wrap; //여러줄에 아이템 표시 + overflow-y: auto; + width: 100%; + padding: 0 1rem; + justify-content: flex-start; +`; + +const FriendsListItem = styled.div` + position: relative; // 상메 올라가는 기준점 + display: flex; + flex-direction: column; + align-items: center; + + padding-top: ${(props) => + props.statusExist + ? "1.1rem" + : "2.1rem"}; // 상메 있는지 여부에 따라 margin-top을 변경 + + //default: 노트북 환경 + width: 100%; + max-width: 10%; // item이 한줄에 10개씩 오도록 + + @media (max-width: 850px) { + // 화면 너비가 850px 이하인 경우 (ipad 환경) + width: 100%; + max-width: 20%; // item이 한줄에 5개씩 오도록 + } + + @media (max-width: 480px) { + // 화면 너비가 480px 이하인 경우 (모바일 환경) + width: 100%; + max-width: 33%; // item이 한줄에 3개씩 오도록 + margin-right: 0; + } +`; + +const StatusBubble = styled.div` + position: relative; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 10px; + + img { + position: absolute; + border-radius: inherit; + } + + span { + position: relative; + z-index: 2; // 이미지 위에 텍스트 + overflow: hidden; + white-space: nowrap; + color: var(--black); + font-family: "SF Pro Text"; + font-size: 0.625rem; + font-style: normal; + font-weight: 400; + line-height: 150%; + } +`; +const Name = styled.div` + margin-top: 0.5rem; + color: var(--black); + text-align: center; + font-family: "SF Pro Text"; + font-size: 1.125rem; + font-style: normal; + font-weight: 600; + line-height: normal; +`; +const Instagram = styled.div` + color: var(--gray-1); + text-align: center; + font-family: "SF Pro Text"; + font-size: 0.75rem; + font-style: normal; + font-weight: 400; + line-height: 125%; +`; From 6359e44753af156488d4a94149b7748c980a6ccd Mon Sep 17 00:00:00 2001 From: dhsin98 Date: Fri, 3 Nov 2023 18:05:05 +0900 Subject: [PATCH 19/21] =?UTF-8?q?[feat]:=20chatRoom=20=EB=A6=AC=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ChatlistItem/chatlistitem.tsx | 151 +++++++++++++++++++ src/pages/chatlist/chatList.tsx | 147 ++---------------- 2 files changed, 166 insertions(+), 132 deletions(-) create mode 100644 src/components/ChatlistItem/chatlistitem.tsx diff --git a/src/components/ChatlistItem/chatlistitem.tsx b/src/components/ChatlistItem/chatlistitem.tsx new file mode 100644 index 0000000..29f4603 --- /dev/null +++ b/src/components/ChatlistItem/chatlistitem.tsx @@ -0,0 +1,151 @@ +// ChatListItem.tsx +import styled from "styled-components"; + +//images +import groupIcon from "../../assets/images/Group.svg"; +import rightarrowIcon from "../../assets/images/rightarrowIcon.svg"; +interface Message { + id: number; + sender: string; + content: string; + showIcon: boolean; + timestamp: string; //메시지 보내는 시간 정보 추가 + unread?: boolean; // 읽지 않은 메시지인지 여부를 나타내는 속성 추가 +} +interface User { + id: number; + name: string; +} + +interface ChatListItemProps { + key: number; + user: User; + lastMessage: Message | null; + onClick: () => void; +} +function formatTimestamp(isoString?: string) { + if (!isoString) return "No last message"; + + const date = new Date(isoString); + date.setHours(0, 0, 0, 0); + + const now = new Date(); + now.setHours(0, 0, 0, 0); + + const diffTime = now.getTime() - date.getTime(); + const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24)); + + // 날짜 차이에 따른 문자열 반환 + //오늘은 시간 정보, 전날은 "어제", 그 전날은 '~일전'으로 반환해줌 + if (diffDays === 0) { + const date = new Date(isoString); + let hours = date.getHours(); + const minutes = date.getMinutes(); + const ampm = hours >= 12 ? "PM" : "AM"; + hours = hours % 12; + hours = hours ? hours : 12; + const strHours = hours < 10 ? `0${hours}` : `${hours}`; + const strMinutes = minutes < 10 ? `0${minutes}` : `${minutes}`; + return `${strHours}:${strMinutes} ${ampm}`; + } else if (diffDays === 1) { + return "어제"; + } else { + return `${diffDays}일 전`; + } +} + +const ChatListItem: React.FC = ({ + user, + lastMessage, + onClick, +}) => { + return ( + + {lastMessage?.unread && } + + + + {user.name} + + + + + + {lastMessage?.content || ""} + + + ); +}; + +const ChatlistItem = styled.div` + width: 100%; + height: 5.25rem; + display: flex; + padding: 0.81rem 1.56rem; + position: relative; + + &::after { + content: ""; + position: absolute; + left: 5.1rem; + right: 0; + bottom: 0; + border-bottom: 1px solid var(--gray-1); + } +`; + +const Time = styled.div` + font-family: "SF Pro Text"; + font-size: 0.9375rem; + font-style: normal; + font-weight: 400; + line-height: normal; + color: var(--gray-1); +`; +const ChatlistData = styled.div` + width: 100%; + display: flex; + flex-direction: column; + padding-left: 0.87rem; +`; +const ChatlistDataTitle = styled.div` + width: 100%; + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 0.25rem; +`; + +const TimeContainer = styled.div` + display: flex; +`; +const UnreadIndicator = styled.div` + position: absolute; + top: 50%; + left: 0.8rem; + width: 0.56rem; + height: 0.56rem; + border-radius: 50%; + background-color: var(--blue); +`; +const GroupIcon = styled.img` + margin-left: 0.2rem; +`; +const Name = styled.span` + font-family: "SF Pro Text"; + font-size: 1.125rem; + font-style: normal; + font-weight: 600; + line-height: normal; +`; +const LastMessage = styled.div` + /* width: 17.25rem; */ + height: 2.25rem; + color: var(--gray-1); + font-family: "SF Pro Text"; + font-size: 0.9375rem; + font-style: normal; + font-weight: 400; + line-height: normal; +`; +export default ChatListItem; diff --git a/src/pages/chatlist/chatList.tsx b/src/pages/chatlist/chatList.tsx index 27f6d7a..2c3643c 100644 --- a/src/pages/chatlist/chatList.tsx +++ b/src/pages/chatlist/chatList.tsx @@ -8,8 +8,7 @@ import friendsIcon from "../../assets/images/Friends.svg"; import { useNavigate } from "react-router-dom"; import userData from "../../assets/datas/userdata.json"; import chatData from "../../assets/datas/chatdata.json"; -import groupIcon from "../../assets/images/Group.svg"; -import rightarrowIcon from "../../assets/images/rightarrowIcon.svg"; +import ChatListItem from "../../components/ChatlistItem/chatlistitem"; interface Message { id: number; sender: string; @@ -26,39 +25,9 @@ interface ChatData { [key: string]: Message[]; } -function formatTimestamp(isoString?: string) { - if (!isoString) return "No last message"; - - const date = new Date(isoString); - date.setHours(0, 0, 0, 0); - - const now = new Date(); - now.setHours(0, 0, 0, 0); - - const diffTime = now.getTime() - date.getTime(); - const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24)); - - // 날짜 차이에 따른 문자열 반환 - //오늘은 시간 정보, 전날은 "어제", 그 전날은 '~일전'으로 반환해줌 - if (diffDays === 0) { - const date = new Date(isoString); - let hours = date.getHours(); - const minutes = date.getMinutes(); - const ampm = hours >= 12 ? "PM" : "AM"; - hours = hours % 12; - hours = hours ? hours : 12; - const strHours = hours < 10 ? `0${hours}` : `${hours}`; - const strMinutes = minutes < 10 ? `0${minutes}` : `${minutes}`; - return `${strHours}:${strMinutes} ${ampm}`; - } else if (diffDays === 1) { - return "어제"; - } else { - return `${diffDays}일 전`; - } -} - export default function ChatList() { const navigate = useNavigate(); + const [lastMessages, setLastMessages] = useState< Record >({}); @@ -72,13 +41,14 @@ export default function ChatList() { ) as ChatData; // 모든 사용자의 마지막 메시지와 시간 가져오기 - const newLastMessages: Record = {}; + const lastMessage: Record = {}; + const usersWithLastMessage = userData.users .filter((user) => savedChats[user.id.toString()]?.length > 0) // chatData가 있는 사용자만 가져옴 .map((user) => { const userChat = savedChats[user.id.toString()]; const lastChat = userChat ? userChat[userChat.length - 1] : null; - newLastMessages[user.id] = lastChat; + lastMessage[user.id] = lastChat; return { ...user, lastMessageTimestamp: lastChat @@ -91,7 +61,7 @@ export default function ChatList() { // 모든 사용자의 마지막 메시지에 대한 unread 상태 업데이트 : // 마지막 sender가 내가 아닌 경우 unread 로 처리 userData.users.forEach((user) => { - const lastMsg = newLastMessages[user.id]; + const lastMsg = lastMessage[user.id]; if (lastMsg) { const updatedMessage = { ...lastMsg, @@ -123,7 +93,7 @@ export default function ChatList() { return (
- + @@ -135,37 +105,22 @@ export default function ChatList() { onChange={(e) => setSearchTerm(e.target.value)} /> <ChatlistContainer> - {filteredUsers.map((user, index) => ( - <ChatlistItem - key={index} + {filteredUsers.map((user) => ( + <ChatListItem + key={user.id} + user={user} + lastMessage={lastMessages[user.id.toString()]} onClick={() => navigate(`/chat/${user.id}`)} - > - {lastMessages[user.id]?.unread && <UnreadIndicator />} - <GroupIcon src={groupIcon} /> - <ChatlistData> - <ChatlistDataTitle> - <Name>{user.name}</Name> - <TimeContainer> - <Time> - {formatTimestamp(lastMessages[user.id]?.timestamp)} - </Time> - <img src={rightarrowIcon} /> - </TimeContainer> - </ChatlistDataTitle> - <LastMessage> - {lastMessages[user.id]?.content || ""} - </LastMessage> - </ChatlistData> - </ChatlistItem> + /> ))} </ChatlistContainer> <BottomBarIcon src={bottomBar} alt="bottom bar Icon" /> - </ProfileContainer> + </ChatRoomContainer> </div> ); } -const ProfileContainer = styled.div` +const ChatRoomContainer = styled.div` display: flex; flex-direction: column; align-items: center; @@ -199,80 +154,8 @@ const BottomBarIcon = styled.img` bottom: 0; `; -const Name = styled.text` - font-family: "SF Pro Text"; - font-size: 1.125rem; - font-style: normal; - font-weight: 600; - line-height: normal; -`; -const LastMessage = styled.div` - /* width: 17.25rem; */ - height: 2.25rem; - color: var(--gray-1); - font-family: "SF Pro Text"; - font-size: 0.9375rem; - font-style: normal; - font-weight: 400; - line-height: normal; -`; - const ChatlistContainer = styled.div` overflow-y: auto; // 세로 스크롤 width: 100%; height: 100%; `; -const ChatlistItem = styled.div` - width: 100%; - height: 5.25rem; - display: flex; - padding: 0.81rem 1.56rem; - position: relative; - - &::after { - content: ""; - position: absolute; - left: 5.1rem; - right: 0; - bottom: 0; - border-bottom: 1px solid var(--gray-1); - } -`; - -const Time = styled.div` - font-family: "SF Pro Text"; - font-size: 0.9375rem; - font-style: normal; - font-weight: 400; - line-height: normal; - color: var(--gray-1); -`; -const ChatlistData = styled.div` - width: 100%; - display: flex; - flex-direction: column; - padding-left: 0.87rem; -`; -const ChatlistDataTitle = styled.div` - width: 100%; - display: flex; - align-items: center; - justify-content: space-between; - margin-bottom: 0.25rem; -`; - -const TimeContainer = styled.div` - display: flex; -`; -const UnreadIndicator = styled.div` - position: absolute; - top: 50%; - left: 0.8rem; - width: 0.56rem; - height: 0.56rem; - border-radius: 50%; - background-color: var(--blue); -`; -const GroupIcon = styled.img` - margin-left: 0.2rem; -`; From a62a1800793175f66c5b91605eec9ad15ac4d063 Mon Sep 17 00:00:00 2001 From: dhsin98 <shinjay417@gmail.com> Date: Fri, 3 Nov 2023 22:54:57 +0900 Subject: [PATCH 20/21] =?UTF-8?q?[chore]:=20=EC=A3=BC=EC=84=9D=EC=A0=95?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ChatInput/chatinput.tsx | 22 +++++++++++--------- src/components/ChatlistItem/chatlistitem.tsx | 2 -- src/pages/chatlist/chatList.tsx | 11 ++++++---- src/pages/friends/friends.tsx | 18 +++++++++------- src/pages/profile/profile.tsx | 3 ++- src/styles/globalStyle.ts | 2 +- 6 files changed, 32 insertions(+), 26 deletions(-) diff --git a/src/components/ChatInput/chatinput.tsx b/src/components/ChatInput/chatinput.tsx index ff90c54..d31b715 100644 --- a/src/components/ChatInput/chatinput.tsx +++ b/src/components/ChatInput/chatinput.tsx @@ -1,5 +1,6 @@ import React, { useState } from "react"; import styled from "styled-components"; +//images import cameraIcon from "../../assets/images/Camera.svg"; import appStoreIcon from "../../assets/images/App-Store.svg"; import dictationIcon from "../../assets/images/Dictation.svg"; @@ -33,17 +34,13 @@ const ChatInput: React.FC<ChatInputProps> = ({ onSend }) => { }; const handleSendClick = () => { + console.log("Send button clicked"); // Add this line for debugging if (inputValue.trim() !== "") { - console.log("inputValue"); onSend(inputValue); } setInputValue(""); }; - const imageClick = () => { - console.log("Click"); - }; - const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => { setInputValue(e.target.value); setIsInputFocused(e.target.value !== ""); // isInputFocused true로 설정 @@ -67,8 +64,8 @@ const ChatInput: React.FC<ChatInputProps> = ({ onSend }) => { required /> {isInputFocused ? ( - //handleSendClick 작동?? - <Send src={sendIcon} alt="send icon" onClick={handleSendClick} /> + //handleSendClick 작동?? click 작동 + <Send onClick={handleSendClick} /> ) : ( <Dictation src={dictationIcon} alt="dictation icon" /> )} @@ -138,13 +135,18 @@ const Dictation = styled.img` right: 1.25rem; `; -const Send = styled.img` +const Send = styled.button` width: 1.6875rem; height: 1.6875rem; + background: url(${sendIcon}) no-repeat center center; + border: none; position: absolute; //input fiield 안에 위치 + z-index: 10; right: 1.25rem; - &:hover { - cursor: pointer; + cursor: pointer; + + &:focus { + outline: none; } `; diff --git a/src/components/ChatlistItem/chatlistitem.tsx b/src/components/ChatlistItem/chatlistitem.tsx index 29f4603..7fdf814 100644 --- a/src/components/ChatlistItem/chatlistitem.tsx +++ b/src/components/ChatlistItem/chatlistitem.tsx @@ -1,6 +1,4 @@ -// ChatListItem.tsx import styled from "styled-components"; - //images import groupIcon from "../../assets/images/Group.svg"; import rightarrowIcon from "../../assets/images/rightarrowIcon.svg"; diff --git a/src/pages/chatlist/chatList.tsx b/src/pages/chatlist/chatList.tsx index 2c3643c..b947fc6 100644 --- a/src/pages/chatlist/chatList.tsx +++ b/src/pages/chatlist/chatList.tsx @@ -1,14 +1,16 @@ import styled from "styled-components"; import { useState, useEffect } from "react"; -import bottomBar from "../../assets/images/LightBottomBar.svg"; +import { useNavigate } from "react-router-dom"; +//components +import ChatListItem from "../../components/ChatlistItem/chatlistitem"; import StatusBar from "../../components/StatusBar/statusbar"; import TopBar from "../../components/TopBar/topbar"; import SearchBar from "../../components/SearchBar/serachbar"; +//images import friendsIcon from "../../assets/images/Friends.svg"; -import { useNavigate } from "react-router-dom"; import userData from "../../assets/datas/userdata.json"; import chatData from "../../assets/datas/chatdata.json"; -import ChatListItem from "../../components/ChatlistItem/chatlistitem"; +import bottomBar from "../../assets/images/LightBottomBar.svg"; interface Message { id: number; sender: string; @@ -51,6 +53,7 @@ export default function ChatList() { lastMessage[user.id] = lastChat; return { ...user, + // 마지막 메시지가 있으면 해당 시간을 가져옴 lastMessageTimestamp: lastChat ? new Date(lastChat.timestamp) : new Date(0), @@ -70,7 +73,7 @@ export default function ChatList() { newLastMessagesWithUnread[user.id] = updatedMessage; } }); - + //unread 업데이트 저장 setLastMessages(newLastMessagesWithUnread); // 타임스탬프 기준으로 사용자 정렬: 가장 최근 메시지가 위에 오게 함 diff --git a/src/pages/friends/friends.tsx b/src/pages/friends/friends.tsx index c8380c6..0433756 100644 --- a/src/pages/friends/friends.tsx +++ b/src/pages/friends/friends.tsx @@ -1,15 +1,16 @@ import styled from "styled-components"; -import bottomBar from "../../assets/images/LightBottomBar.svg"; -import StatusBar from "../../components/StatusBar/statusbar"; -import TopBar from "../../components/TopBar/topbar"; -import searchBar1 from "../../assets/images/Searchbars.svg"; -import SearchBar from "../../components/SearchBar/serachbar"; -import friendsIcon from "../../assets/images/Friends.svg"; import { useNavigate } from "react-router-dom"; +import { useState } from "react"; import userData from "../../assets/datas/userdata.json"; +//component +import SearchBar from "../../components/SearchBar/serachbar"; +//이미지들 import bigIcon from "../../assets/images/BigIcon.svg"; +import bottomBar from "../../assets/images/LightBottomBar.svg"; +import StatusBar from "../../components/StatusBar/statusbar"; +import TopBar from "../../components/TopBar/topbar"; import bubbleImg from "../../assets/images/bubbleStatusImg.svg"; -import { useState } from "react"; + interface User { id: number; name: string; @@ -25,6 +26,7 @@ export default function Friends() { const navigate = useNavigate(); const [searchTerm, setSearchTerm] = useState<string>(""); const [friends, setFriends] = useState(userData.users); + function getFilteredUsers(users: User[], searchTerm: string) { if (!searchTerm) return users; const filteredUsers = users.filter((user) => @@ -84,7 +86,7 @@ const ProfileContainer = styled.div` border: solid var(--gray-1); `; -const Title = styled.text` +const Title = styled.span` font-family: "SF Pro Text"; font-size: 2.125rem; font-style: normal; diff --git a/src/pages/profile/profile.tsx b/src/pages/profile/profile.tsx index 7959f86..f22e25c 100644 --- a/src/pages/profile/profile.tsx +++ b/src/pages/profile/profile.tsx @@ -1,11 +1,12 @@ import styled from "styled-components"; +import { useNavigate } from "react-router-dom"; +//이미지 & datas import bottomBar from "../../assets/images/LightBottomBar.svg"; import StatusBar from "../../components/StatusBar/statusbar"; import BigIconLogo from "../../assets/images/BigIconLogo.svg"; import Github from "../../assets/images/github.svg"; import RightArrow from "../../assets/images/Arrow.svg"; import Instagram from "../../assets/images/instagram.svg"; -import { useNavigate } from "react-router-dom"; import userData from "../../assets/datas/userdata.json"; export default function Profile() { diff --git a/src/styles/globalStyle.ts b/src/styles/globalStyle.ts index 68a3488..175472f 100644 --- a/src/styles/globalStyle.ts +++ b/src/styles/globalStyle.ts @@ -7,7 +7,7 @@ const GlobalStyle = createGlobalStyle` box-sizing:border-box; padding: 0; margin: 0; - font-family: "SF Pro Text", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + font-family: "SF Pro", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; } html,body{ From 7aac63b32faaec5e66455e85ee5338f0c29cc5c6 Mon Sep 17 00:00:00 2001 From: dhsin98 <shinjay417@gmail.com> Date: Sat, 4 Nov 2023 13:10:44 +0900 Subject: [PATCH 21/21] =?UTF-8?q?[chore]:=20chatlist=20=EC=A3=BC=EC=84=9D?= =?UTF-8?q?=20=EC=A0=95=EB=A6=AC2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/chatlist/chatList.tsx | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/pages/chatlist/chatList.tsx b/src/pages/chatlist/chatList.tsx index b947fc6..7035590 100644 --- a/src/pages/chatlist/chatList.tsx +++ b/src/pages/chatlist/chatList.tsx @@ -34,6 +34,7 @@ export default function ChatList() { Record<string, Message | null> >({}); + //마지막 메시지가 최근인 순서로 sorting const [sortedUsers, setSortedUsers] = useState(userData.users); useEffect(() => { @@ -45,7 +46,8 @@ export default function ChatList() { // 모든 사용자의 마지막 메시지와 시간 가져오기 const lastMessage: Record<string, Message | null> = {}; - const usersWithLastMessage = userData.users + //userMessage: Message기록이 있는 유저들 필터링 + 마지막 대화 시간 가져옴 + const userMessage = userData.users .filter((user) => savedChats[user.id.toString()]?.length > 0) // chatData가 있는 사용자만 가져옴 .map((user) => { const userChat = savedChats[user.id.toString()]; @@ -53,35 +55,33 @@ export default function ChatList() { lastMessage[user.id] = lastChat; return { ...user, - // 마지막 메시지가 있으면 해당 시간을 가져옴 lastMessageTimestamp: lastChat - ? new Date(lastChat.timestamp) - : new Date(0), + ? new Date(lastChat.timestamp) // 마지막 메시지가 있으면 해당 시간을 가져옴 + : new Date(0), // 마지막 timestamp 없으면 기본 시간으로 예외처리 }; }); - const newLastMessagesWithUnread: Record<string, Message | null> = {}; + // 타임스탬프 기준으로 사용자 정렬: 가장 최근 메시지가 위에 오게 함 + const sortedUsers = userMessage.sort( + (a, b) => + b.lastMessageTimestamp.getTime() - a.lastMessageTimestamp.getTime() + ); + setSortedUsers(sortedUsers); + + const lastMessageUnread: Record<string, Message | null> = {}; // 모든 사용자의 마지막 메시지에 대한 unread 상태 업데이트 : - // 마지막 sender가 내가 아닌 경우 unread 로 처리 + // 마지막 sender가 내(신동현)가 아닌 경우 unread 로 처리 userData.users.forEach((user) => { const lastMsg = lastMessage[user.id]; if (lastMsg) { - const updatedMessage = { + lastMessageUnread[user.id] = { ...lastMsg, unread: lastMsg.sender !== "신동현", }; - newLastMessagesWithUnread[user.id] = updatedMessage; } }); //unread 업데이트 저장 - setLastMessages(newLastMessagesWithUnread); - - // 타임스탬프 기준으로 사용자 정렬: 가장 최근 메시지가 위에 오게 함 - const sortedUsers = usersWithLastMessage.sort( - (a, b) => - b.lastMessageTimestamp.getTime() - a.lastMessageTimestamp.getTime() - ); - setSortedUsers(sortedUsers); + setLastMessages(lastMessageUnread); }, []); //searchBar에서 검색하면, 필터링된 사용자만 뜨게 하는 부분