From 6006bf3a1c8aac6bcbd5716e7b12f18d63d106db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Tue, 26 Mar 2024 22:17:08 +0900 Subject: [PATCH 01/46] =?UTF-8?q?chore:=20.prettierrc=20=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .prettierrc | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .prettierrc diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..0a72520 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,6 @@ +{ + "trailingComma": "es5", + "tabWidth": 2, + "semi": true, + "singleQuote": true +} From 0ae9c77b9d91747b9757d0824f0131dc5bad8616 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Thu, 28 Mar 2024 14:56:01 +0900 Subject: [PATCH 02/46] =?UTF-8?q?feat:=20status=20bar=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- custom.d.ts | 4 + package-lock.json | 199 ++++++++++++++++++++++++++++++- package.json | 6 +- public/index.html | 6 +- src/App.css | 38 ------ src/App.tsx | 23 +++- src/assets/img/homeIndicator.svg | 3 + src/assets/img/rightSide.svg | 7 ++ src/components/ChattingRoom.tsx | 0 src/components/HomeIndicator.tsx | 21 ++++ src/components/StatusBar.tsx | 63 ++++++++++ src/index.css | 13 -- src/index.tsx | 9 +- src/pages/ChattingPage.tsx | 6 + src/style/GlobalStyles.tsx | 68 +++++++++++ tsconfig.json | 10 +- 16 files changed, 400 insertions(+), 76 deletions(-) create mode 100644 custom.d.ts delete mode 100644 src/App.css create mode 100644 src/assets/img/homeIndicator.svg create mode 100644 src/assets/img/rightSide.svg create mode 100644 src/components/ChattingRoom.tsx create mode 100644 src/components/HomeIndicator.tsx create mode 100644 src/components/StatusBar.tsx delete mode 100644 src/index.css create mode 100644 src/pages/ChattingPage.tsx create mode 100644 src/style/GlobalStyles.tsx diff --git a/custom.d.ts b/custom.d.ts new file mode 100644 index 0000000..091d25e --- /dev/null +++ b/custom.d.ts @@ -0,0 +1,4 @@ +declare module '*.svg' { + const content: any; + export default content; +} diff --git a/package-lock.json b/package-lock.json index c27bbe4..2e57482 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,9 +17,13 @@ "@types/react-dom": "^18.2.22", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-router-dom": "^6.22.3", "react-scripts": "5.0.1", - "typescript": "^4.9.5", + "styled-reset": "^4.5.2", "web-vitals": "^2.1.4" + }, + "devDependencies": { + "typescript": "^5.4.3" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -2288,6 +2292,27 @@ "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==", + "peer": true, + "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==", + "peer": true + }, + "node_modules/@emotion/unitless": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz", + "integrity": "sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw==", + "peer": true + }, "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", @@ -3338,6 +3363,14 @@ } } }, + "node_modules/@remix-run/router": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.3.tgz", + "integrity": "sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w==", + "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", @@ -4290,6 +4323,12 @@ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==" }, + "node_modules/@types/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-n4sx2bqL0mW1tvDf/loQ+aMX7GQD3lc3fkCMC55VFNDu/vBOabO+LTIeXKM14xK0ppk5TUGcWRjiSpIlUpghKw==", + "peer": true + }, "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", @@ -5750,6 +5789,15 @@ "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==", + "peer": true, + "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", @@ -6182,6 +6230,15 @@ "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==", + "peer": true, + "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", @@ -6372,6 +6429,17 @@ "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==", + "peer": true, + "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", @@ -14864,6 +14932,36 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "6.22.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.3.tgz", + "integrity": "sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ==", + "dependencies": { + "@remix-run/router": "1.15.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.22.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.3.tgz", + "integrity": "sha512-7ZILI7HjcE+p31oQvwbokjk6OA/bnFxrhJ19n82Ex9Ph8fNAq+Hm/7KchpMGlTgWhUxRHMMCut+vEtNpWpowKw==", + "dependencies": { + "@remix-run/router": "1.15.3", + "react-router": "6.22.3" + }, + "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", @@ -15719,6 +15817,12 @@ "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==", + "peer": true + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -16260,6 +16364,85 @@ "webpack": "^5.0.0" } }, + "node_modules/styled-components": { + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.8.tgz", + "integrity": "sha512-PQ6Dn+QxlWyEGCKDS71NGsXoVLKfE1c3vApkvDYS5KAK+V8fNWGhbSUEo9Gg2iaID2tjLXegEW3bZDUGpofRWw==", + "peer": true, + "dependencies": { + "@emotion/is-prop-valid": "1.2.1", + "@emotion/unitless": "0.8.0", + "@types/stylis": "4.2.0", + "css-to-react-native": "3.2.0", + "csstype": "3.1.2", + "postcss": "8.4.31", + "shallowequal": "1.1.0", + "stylis": "4.3.1", + "tslib": "2.5.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0" + } + }, + "node_modules/styled-components/node_modules/csstype": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==", + "peer": true + }, + "node_modules/styled-components/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "peer": true, + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/styled-components/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "peer": true + }, + "node_modules/styled-reset": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/styled-reset/-/styled-reset-4.5.2.tgz", + "integrity": "sha512-dbAaaVEhweBs2FGfqGBdW6oMcMK8238C2X5KCxBhUQJX92m/QyUfzRADOXhdXiXNkIPELtMCd72YY9eCdORfIw==", + "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", @@ -16275,6 +16458,12 @@ "postcss": "^8.2.15" } }, + "node_modules/stylis": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.1.tgz", + "integrity": "sha512-EQepAV+wMsIaGVGX1RECzgrcqRRU/0sYOHkeLsZ3fzHaHXZy4DaOOX0vOlGQdlsjkh3mFHAIlVimpwAs4dslyQ==", + "peer": true + }, "node_modules/sucrase": { "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", @@ -16936,15 +17125,15 @@ } }, "node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", + "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==", "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 ea335d3..fbc69e2 100644 --- a/package.json +++ b/package.json @@ -12,8 +12,9 @@ "@types/react-dom": "^18.2.22", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-router-dom": "^6.22.3", "react-scripts": "5.0.1", - "typescript": "^4.9.5", + "styled-reset": "^4.5.2", "web-vitals": "^2.1.4" }, "scripts": { @@ -39,5 +40,8 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "devDependencies": { + "typescript": "^5.4.3" } } diff --git a/public/index.html b/public/index.html index aa069f2..4c9fa54 100644 --- a/public/index.html +++ b/public/index.html @@ -24,7 +24,11 @@ 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 + + react-messenger diff --git a/src/App.css b/src/App.css deleted file mode 100644 index 74b5e05..0000000 --- a/src/App.css +++ /dev/null @@ -1,38 +0,0 @@ -.App { - text-align: center; -} - -.App-logo { - height: 40vmin; - pointer-events: none; -} - -@media (prefers-reduced-motion: no-preference) { - .App-logo { - animation: App-logo-spin infinite 20s linear; - } -} - -.App-header { - background-color: #282c34; - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); - color: white; -} - -.App-link { - color: #61dafb; -} - -@keyframes App-logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} diff --git a/src/App.tsx b/src/App.tsx index bd79c18..14a65a1 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,9 +1,22 @@ +import { BrowserRouter, Routes, Route } from 'react-router-dom'; +import ChattingPage from './pages/ChattingPage'; +import GlobalStyles from './style/GlobalStyles'; +import StatusBar from './components/StatusBar'; +import HomeIndicator from './components/HomeIndicator'; + function App() { - return ( -
-

19기 프론트엔드 파이팅!!! 디자인과 사이좋게 지내요~~~

-
- ); + return ( + + +
+ + + } /> + + +
+
+ ); } export default App; diff --git a/src/assets/img/homeIndicator.svg b/src/assets/img/homeIndicator.svg new file mode 100644 index 0000000..2822346 --- /dev/null +++ b/src/assets/img/homeIndicator.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/img/rightSide.svg b/src/assets/img/rightSide.svg new file mode 100644 index 0000000..1a10768 --- /dev/null +++ b/src/assets/img/rightSide.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/components/ChattingRoom.tsx b/src/components/ChattingRoom.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/components/HomeIndicator.tsx b/src/components/HomeIndicator.tsx new file mode 100644 index 0000000..e9c9caa --- /dev/null +++ b/src/components/HomeIndicator.tsx @@ -0,0 +1,21 @@ +import styled from 'styled-components'; +import HomeBar from '../assets/img/homeIndicator.svg'; + +const HomeBarContainer = styled.div` + width: 23.4375rem; + height: 2.125rem; +`; + +const HomeBarImg = styled.img` + width: 8.375rem; + height: 0.3125rem; + padding: 1.3125rem 7.5rem 0.5rem 7.5625rem; +`; + +export default function HomeIndicator() { + return ( + + + + ); +} diff --git a/src/components/StatusBar.tsx b/src/components/StatusBar.tsx new file mode 100644 index 0000000..48fe26b --- /dev/null +++ b/src/components/StatusBar.tsx @@ -0,0 +1,63 @@ +import { useState, useEffect } from 'react'; +import styled from 'styled-components'; +import rightSide from '../assets/img/rightSide.svg'; + +const StatusBarContainer = styled.div` + width: 23.4375rem; + height: 2.75rem; + flex-shrink: 0; + position: relative; +`; + +const RightSideImg = styled.img` + width: 4.16631rem; + height: 0.7085rem; + position: absolute; + top: 1.08rem; + right: 0.92rem; +`; + +const TextContainer = styled.div` + position: absolute; + top: 1rem; + left: 1.75rem; +`; + +const ClockText = styled.div` + color: #000; + text-align: center; + font-family: 'SF Pro Text'; + font-size: 0.875rem; + font-style: normal; + font-weight: 700; + line-height: 100%; /* 0.875rem */ +`; + +export default function StatusBar() { + const [currentTime, setCurrentTime] = useState(''); + + useEffect(() => { + function getCurrentTime() { + const now = new Date(); + const hours = String(now.getHours()).padStart(2, '0'); + const minutes = String(now.getMinutes()).padStart(2, '0'); + setCurrentTime(`${hours}:${minutes}`); + } + + // 컴포넌트 마운트 시 현재 시간을 설정하고, 매 분마다 시간을 업데이트 + getCurrentTime(); + const intervalId = setInterval(getCurrentTime, 60000); // 60초마다 getCurrentTime을 실행 + + // 컴포넌트가 언마운트될 때 setInterval을 정리 + return () => clearInterval(intervalId); + }, []); // 의존성 배열이 빈 배열이므로, 이 effect는 컴포넌트가 마운트될 때 한 번만 실행 + + return ( + + + {currentTime} + + + + ); +} diff --git a/src/index.css b/src/index.css deleted file mode 100644 index ec2585e..0000000 --- a/src/index.css +++ /dev/null @@ -1,13 +0,0 @@ -body { - margin: 0; - font-family: -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; -} - -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; -} diff --git a/src/index.tsx b/src/index.tsx index d10be77..88e8a31 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,13 +1,12 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; -import './index.css'; import App from './App'; const root = ReactDOM.createRoot( - document.getElementById('root') as HTMLElement + document.getElementById('root') as HTMLElement ); root.render( - - - + + + ); diff --git a/src/pages/ChattingPage.tsx b/src/pages/ChattingPage.tsx new file mode 100644 index 0000000..dcfe3cd --- /dev/null +++ b/src/pages/ChattingPage.tsx @@ -0,0 +1,6 @@ +import React from 'react'; +import styled from 'styled-components'; + +export default function ChattingPage() { + return <>; +} diff --git a/src/style/GlobalStyles.tsx b/src/style/GlobalStyles.tsx new file mode 100644 index 0000000..4e71597 --- /dev/null +++ b/src/style/GlobalStyles.tsx @@ -0,0 +1,68 @@ +import { createGlobalStyle } from 'styled-components'; +import reset from 'styled-reset'; + +const GlobalStyle = createGlobalStyle` + ${reset} + + @font-face { + font-family: "SF Pro Text"; + font-style: normal; + font-weight: 400; + src: url("https://raw.githubusercontent.com/blaisck/sfwin/master/SFPro/TrueType/SFProText-Regular.ttf"); + } + + @font-face { + font-family: "SF Pro Text"; + font-style: normal; + font-weight: 500; + src: url("https://raw.githubusercontent.com/blaisck/sfwin/master/SFPro/TrueType/SFProText-Medium.ttf"); + } + + @font-face { + font-family: "SF Pro Text"; + font-style: normal; + font-weight: 600; + src: url("https://raw.githubusercontent.com/blaisck/sfwin/master/SFPro/TrueType/SFProText-SemiBold.ttf"); + } + + @font-face { + font-family: "SF Pro Text"; + font-style: normal; + font-weight: 700; + src: url("https://raw.githubusercontent.com/blaisck/sfwin/master/SFPro/TrueType/SFProText-Bold.ttf"); + } + + @font-face { + font-family: "SF Pro Display"; + font-style: normal; + font-weight: 500; + src: url("https://raw.githubusercontent.com/blaisck/sfwin/master/SFPro/TrueType/SFProDisplay-Medium.ttf"); + } + + html,body{ + height:100%; + background-color : var(--white); + display:flex; + justify-content: center; + align-items: center; + } + + input{ + outline: none; + } + + .Container{ + width: 23.4375rem; + height: 50.75rem; + border-radius: 1.25rem; + background: var(--Background-Light-Grey, #F6F6F6); + } + + .ChatBackground{ + width: 23.4375rem; + height: 45.75rem; + position : relative; + } +`; + +export default GlobalStyle; diff --git a/tsconfig.json b/tsconfig.json index a273b0c..573e866 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,11 +1,7 @@ { "compilerOptions": { "target": "es5", - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], + "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, @@ -20,7 +16,5 @@ "noEmit": true, "jsx": "react-jsx" }, - "include": [ - "src" - ] + "include": ["src/**/*", "custom.d.ts"] } From 77c7f9b5d8a0dbec840c2d8ab60ab7536cd0e80e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Thu, 28 Mar 2024 23:12:24 +0900 Subject: [PATCH 03/46] =?UTF-8?q?chore:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20public=20=ED=8F=B4=EB=8D=94?= =?UTF-8?q?=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/MyStatus.svg | 9 ++ public/img/DainPark.svg | 9 ++ src/App.tsx | 8 +- src/assets/data/chatData.json | 44 +++++++++ src/assets/data/userData.json | 16 ++++ src/assets/img/attachment.svg | 3 + src/assets/img/audioCall.svg | 3 + src/assets/img/camera.svg | 3 + src/assets/img/left.svg | 3 + src/assets/img/microphone.svg | 3 + src/assets/img/plus.svg | 4 + src/assets/img/videoCall.svg | 3 + src/components/Chat/ChatInput.tsx | 80 ++++++++++++++++ src/components/Chat/ChattingRoom.tsx | 11 +++ src/components/Chat/TitleBar.tsx | 134 +++++++++++++++++++++++++++ src/components/ChattingRoom.tsx | 0 src/components/StatusBar.tsx | 2 +- src/pages/ChattingPage.tsx | 23 ++++- 18 files changed, 351 insertions(+), 7 deletions(-) create mode 100644 public/MyStatus.svg create mode 100644 public/img/DainPark.svg create mode 100644 src/assets/data/chatData.json create mode 100644 src/assets/data/userData.json create mode 100644 src/assets/img/attachment.svg create mode 100644 src/assets/img/audioCall.svg create mode 100644 src/assets/img/camera.svg create mode 100644 src/assets/img/left.svg create mode 100644 src/assets/img/microphone.svg create mode 100644 src/assets/img/plus.svg create mode 100644 src/assets/img/videoCall.svg create mode 100644 src/components/Chat/ChatInput.tsx create mode 100644 src/components/Chat/ChattingRoom.tsx create mode 100644 src/components/Chat/TitleBar.tsx delete mode 100644 src/components/ChattingRoom.tsx diff --git a/public/MyStatus.svg b/public/MyStatus.svg new file mode 100644 index 0000000..2b9c56b --- /dev/null +++ b/public/MyStatus.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/img/DainPark.svg b/public/img/DainPark.svg new file mode 100644 index 0000000..dbf656e --- /dev/null +++ b/public/img/DainPark.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/App.tsx b/src/App.tsx index 14a65a1..d893c4f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -10,9 +10,11 @@ function App() {
- - } /> - +
+ + } /> + +
diff --git a/src/assets/data/chatData.json b/src/assets/data/chatData.json new file mode 100644 index 0000000..e72190d --- /dev/null +++ b/src/assets/data/chatData.json @@ -0,0 +1,44 @@ +[ + { + "id": 0, + "sender": "My Status", + "content": "Lovely you on the way? I had other plans after the meet-up, I just gave ur IG to Charlie just in case, hope you don’t mind.", + "time": "9:41am", + "isRead": true + }, + { + "id": 1, + "sender": "My Status", + "content": "Oooo sorry to hear that. Could you give just for a moment to see me? Cos I’m on the way to see you to bring your clothes that you’d left.", + "time": "9:41am", + "isRead": true + }, + { + "id": 2, + "sender": "Dain Park", + "content": "Gosh I’m a bit under the weather, so I just came back to my flat. Would you pls let him know I’m gonna drop dm? So sorry, and thx for that. I don’t mind at all.", + "time": "9:41am", + "isRead": true + }, + { + "id": 3, + "sender": "Dain Park", + "content": "Aww sweet, tysm! let me know when you arrive the ground floor!", + "time": "9:41am", + "isRead": true + }, + { + "id": 4, + "sender": "My Status", + "content": "Hey lovey don’t forget to tell me when you are okay! Tmrw is the 24th and you leave soon!", + "time": "9:41am", + "isRead": true + }, + { + "id": 5, + "sender": "My status", + "content": "I couldn’t get to see Curtis or Senna so I should see you bfr you go!", + "time": "9:41am", + "isRead": true + } +] diff --git a/src/assets/data/userData.json b/src/assets/data/userData.json new file mode 100644 index 0000000..9b03e83 --- /dev/null +++ b/src/assets/data/userData.json @@ -0,0 +1,16 @@ +{ + "users": [ + { + "id": 0, + "name": "My Status", + "profileImg": "/img/MyStatus.svg'", + "isActive": true + }, + { + "id": 1, + "name": "Dain Park", + "profileImg": "/img/DainPark.svg", + "isActive": true + } + ] +} diff --git a/src/assets/img/attachment.svg b/src/assets/img/attachment.svg new file mode 100644 index 0000000..c7bd49d --- /dev/null +++ b/src/assets/img/attachment.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/img/audioCall.svg b/src/assets/img/audioCall.svg new file mode 100644 index 0000000..be1ea2c --- /dev/null +++ b/src/assets/img/audioCall.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/img/camera.svg b/src/assets/img/camera.svg new file mode 100644 index 0000000..6137ebe --- /dev/null +++ b/src/assets/img/camera.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/img/left.svg b/src/assets/img/left.svg new file mode 100644 index 0000000..fe8eea4 --- /dev/null +++ b/src/assets/img/left.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/img/microphone.svg b/src/assets/img/microphone.svg new file mode 100644 index 0000000..b4356dd --- /dev/null +++ b/src/assets/img/microphone.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/img/plus.svg b/src/assets/img/plus.svg new file mode 100644 index 0000000..c2db4a7 --- /dev/null +++ b/src/assets/img/plus.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/img/videoCall.svg b/src/assets/img/videoCall.svg new file mode 100644 index 0000000..7873299 --- /dev/null +++ b/src/assets/img/videoCall.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/Chat/ChatInput.tsx b/src/components/Chat/ChatInput.tsx new file mode 100644 index 0000000..1533009 --- /dev/null +++ b/src/components/Chat/ChatInput.tsx @@ -0,0 +1,80 @@ +import { useState } from 'react'; +import styled from 'styled-components'; +import Plus from '../../assets/img/plus.svg'; +import Attachment from '../../assets/img/attachment.svg'; +import Camera from '../../assets/img/camera.svg'; +import Microphone from '../../assets/img/microphone.svg'; + +const InputContainer = styled.div` + width: 23.4375rem; + height: 2.87rem; + border-top: 0.03125rem solid #a4a39e; + position: relative; +`; + +const IconImg = styled.img` + width: 1.5rem; + height: 1.5rem; + position: absolute; + bottom: 0.5rem; +`; + +const InputBox = styled.div` + width: 14.375rem; + height: 1.875rem; + border-radius: 0.9375rem; + border: 0.6px solid #dedfe0; + background: #fff; + position: absolute; + top: 0.62rem; + left: 2.88rem; +`; + +const Input = styled.input` + width: 75%; + margin-top: 0.22rem; + margin-bottom: 0.28rem; + margin-left: 0.75rem; + border: none; + outline: none; + font-family: 'SF Pro Text'; + font-size: 1.0625rem; + font-style: normal; + font-weight: 400; + line-height: 1.375rem; + letter-spacing: -0.02563rem; +`; + +export default function ChatInput() { + const [value, setValue] = useState(''); + + const handleSubmit = (event: React.FormEvent) => { + event.preventDefault(); + if (!value.trim()) { + alert('공백 없이 입력해주세요.'); + return; + } + setValue(''); + }; + + return ( + + + +
+ setValue(event.target.value)} + /> +
+
+ + + +
+ ); +} diff --git a/src/components/Chat/ChattingRoom.tsx b/src/components/Chat/ChattingRoom.tsx new file mode 100644 index 0000000..46fe83e --- /dev/null +++ b/src/components/Chat/ChattingRoom.tsx @@ -0,0 +1,11 @@ +import styled from 'styled-components'; + +const ChattingRoomContainer = styled.div` + width: 23.4375rem; + height: 40.37rem; + flex-direction: column; +`; + +export default function ChattingRoom() { + return ; +} diff --git a/src/components/Chat/TitleBar.tsx b/src/components/Chat/TitleBar.tsx new file mode 100644 index 0000000..3e895b3 --- /dev/null +++ b/src/components/Chat/TitleBar.tsx @@ -0,0 +1,134 @@ +import styled from 'styled-components'; +import Left from '../../assets/img/left.svg'; +import VideoCall from '../../assets/img/videoCall.svg'; +import AudioCall from '../../assets/img/audioCall.svg'; + +const TitleBarContainer = styled.div` + width: 23.4375rem; + height: 2.75rem; + border-bottom: 0.03125rem solid #a4a39e; + position: relative; +`; + +const LeftContainer = styled.div` + display: inline-flex; + padding: 0.625rem 0.5625rem; + align-items: flex-start; + gap: 0.3125rem; + position: absolute; + bottom: 0.13rem; + right: 20.1rem; +`; + +const LeftImg = styled.img` + width: 0.75rem; + height: 1.25rem; +`; + +const LeftText = styled.div` + color: var(--Whatsapp-Green, #1bd742); + font-family: 'SF Pro Text'; + font-size: 1.0625rem; + font-style: normal; + font-weight: 400; + line-height: 1.375rem; /* 129.412% */ + letter-spacing: -0.0255rem; +`; + +const ProfileContainer = styled.div` + display: inline-flex; + align-items: flex-start; + gap: 0.5rem; + position: absolute; + top: 0.13rem; + left: 3.81rem; +`; + +const ProfileImg = styled.img` + width: 2.25rem; + height: 2.25rem; + border-radius: 2.25rem; +`; + +const ProfileInnerContainer = styled.div` + display: flex; + flex-direction: column; + align-items: flex-start; +`; + +const ProfileName = styled.div` + display: flex; + align-items: center; + gap: 0.25rem; + + color: #000; + font-family: 'SF Pro Display'; + font-size: 1.125rem; + font-style: normal; + font-weight: 600; + line-height: normal; +`; + +const OnlineText = styled.div` + color: #898989; + font-family: 'SF Pro Text'; + font-size: 0.75rem; + font-style: normal; + font-weight: 400; + line-height: normal; + letter-spacing: -0.0015rem; +`; + +const RightContainer = styled.div` + display: inline-flex; + align-items: flex-start; + gap: 1.375rem; + position: absolute; + top: 0.63rem; + right: 0.94rem; +`; + +const CallImg = styled.img` + width: 1.5rem; + height: 1.5rem; +`; + +interface ChatTitleProps { + userId: number; + userName: string; + profileImg: string; + isActive: boolean; + handleChangeUser?: () => void; +} + +export default function TitleBar(props: ChatTitleProps) { + const { userId, userName, profileImg, isActive, handleChangeUser } = props; + + return ( + + + + 12 + + + + + {userName} + {isActive ? ( + online + ) : ( + offline + )} + + + + + + + + ); +} diff --git a/src/components/ChattingRoom.tsx b/src/components/ChattingRoom.tsx deleted file mode 100644 index e69de29..0000000 diff --git a/src/components/StatusBar.tsx b/src/components/StatusBar.tsx index 48fe26b..9ea2d25 100644 --- a/src/components/StatusBar.tsx +++ b/src/components/StatusBar.tsx @@ -46,7 +46,7 @@ export default function StatusBar() { // 컴포넌트 마운트 시 현재 시간을 설정하고, 매 분마다 시간을 업데이트 getCurrentTime(); - const intervalId = setInterval(getCurrentTime, 60000); // 60초마다 getCurrentTime을 실행 + const intervalId = setInterval(getCurrentTime, 30000); // 30초마다 getCurrentTime을 실행 // 컴포넌트가 언마운트될 때 setInterval을 정리 return () => clearInterval(intervalId); diff --git a/src/pages/ChattingPage.tsx b/src/pages/ChattingPage.tsx index dcfe3cd..27f6929 100644 --- a/src/pages/ChattingPage.tsx +++ b/src/pages/ChattingPage.tsx @@ -1,6 +1,23 @@ -import React from 'react'; -import styled from 'styled-components'; +import { useState } from 'react'; +import TitleBar from '../components/Chat/TitleBar'; +import ChattingRoom from '../components/Chat/ChattingRoom'; +import ChatInput from '../components/Chat/ChatInput'; +import userData from '../assets/data/userData.json'; export default function ChattingPage() { - return <>; + const [partner] = useState(userData.users[1]); + + return ( + <> + + + + + ); } From 87ccdf832edb355b3f883fb506bcf16649a58b84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Fri, 29 Mar 2024 04:43:32 +0900 Subject: [PATCH 04/46] =?UTF-8?q?feat:=20=EC=B4=88=EA=B8=B0=20=EC=B1=84?= =?UTF-8?q?=ED=8C=85=20=EC=B6=9C=EB=A0=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 23 +++- src/assets/data/chatData.json | 97 ++++++++------- src/assets/data/userData.json | 2 +- .../img/{audioCall.svg => audio-call.svg} | 0 src/assets/img/bubble-gray.svg | 10 ++ src/assets/img/bubble-green.svg | 10 ++ src/assets/img/checkmark.svg | 10 ++ .../{homeIndicator.svg => home-indicator.svg} | 0 .../img/{rightSide.svg => right-side.svg} | 0 .../img/{videoCall.svg => video-call.svg} | 0 src/components/Chat/ChatBubble.tsx | 113 ++++++++++++++++++ src/components/Chat/ChattingRoom.tsx | 18 ++- src/components/Chat/TitleBar.tsx | 13 +- src/components/HomeIndicator.tsx | 2 +- src/components/StatusBar.tsx | 2 +- src/index.css | 34 ++++++ src/index.tsx | 1 + src/pages/ChattingPage.tsx | 5 +- src/style/GlobalStyles.tsx | 53 +------- 19 files changed, 280 insertions(+), 113 deletions(-) rename src/assets/img/{audioCall.svg => audio-call.svg} (100%) create mode 100644 src/assets/img/bubble-gray.svg create mode 100644 src/assets/img/bubble-green.svg create mode 100644 src/assets/img/checkmark.svg rename src/assets/img/{homeIndicator.svg => home-indicator.svg} (100%) rename src/assets/img/{rightSide.svg => right-side.svg} (100%) rename src/assets/img/{videoCall.svg => video-call.svg} (100%) create mode 100644 src/components/Chat/ChatBubble.tsx create mode 100644 src/index.css diff --git a/src/App.tsx b/src/App.tsx index d893c4f..50d3dc8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,22 +1,37 @@ import { BrowserRouter, Routes, Route } from 'react-router-dom'; +import styled from 'styled-components'; import ChattingPage from './pages/ChattingPage'; import GlobalStyles from './style/GlobalStyles'; import StatusBar from './components/StatusBar'; import HomeIndicator from './components/HomeIndicator'; +const Container = styled.div` + width: 23.4375rem; + height: 50.75rem; + border-radius: 1.25rem; + background: #f6f6f6; +`; + +// StatusBar와 HomeIndicator 제외 영역 +const InnerContainer = styled.div` + width: 23.4375rem; + height: 45.75rem; + position: relative; +`; + function App() { return ( -
+ -
+ } /> -
+ -
+
); } diff --git a/src/assets/data/chatData.json b/src/assets/data/chatData.json index e72190d..1f0defb 100644 --- a/src/assets/data/chatData.json +++ b/src/assets/data/chatData.json @@ -1,44 +1,53 @@ -[ - { - "id": 0, - "sender": "My Status", - "content": "Lovely you on the way? I had other plans after the meet-up, I just gave ur IG to Charlie just in case, hope you don’t mind.", - "time": "9:41am", - "isRead": true - }, - { - "id": 1, - "sender": "My Status", - "content": "Oooo sorry to hear that. Could you give just for a moment to see me? Cos I’m on the way to see you to bring your clothes that you’d left.", - "time": "9:41am", - "isRead": true - }, - { - "id": 2, - "sender": "Dain Park", - "content": "Gosh I’m a bit under the weather, so I just came back to my flat. Would you pls let him know I’m gonna drop dm? So sorry, and thx for that. I don’t mind at all.", - "time": "9:41am", - "isRead": true - }, - { - "id": 3, - "sender": "Dain Park", - "content": "Aww sweet, tysm! let me know when you arrive the ground floor!", - "time": "9:41am", - "isRead": true - }, - { - "id": 4, - "sender": "My Status", - "content": "Hey lovey don’t forget to tell me when you are okay! Tmrw is the 24th and you leave soon!", - "time": "9:41am", - "isRead": true - }, - { - "id": 5, - "sender": "My status", - "content": "I couldn’t get to see Curtis or Senna so I should see you bfr you go!", - "time": "9:41am", - "isRead": true - } -] +{ + "chattings": [ + { + "chatRoomId": 0, + "userList": [0, 1], + "chatList": [ + { + "id": 0, + "senderId": 0, + "content": "Lovely you on the way? I had other plans after the meet-up, I just gave ur IG to Charlie just in case, hope you don’t mind.", + "time": "9:41am", + "isRead": true + }, + { + "id": 1, + "senderId": 1, + "content": "Gosh I’m a bit under the weather, so I just came back to my flat. Would you pls let him know I’m gonna drop dm? So sorry, and thx for that. I don’t mind at all.", + "time": "9:41am", + "isRead": true + }, + { + "id": 2, + "senderId": 0, + "content": "Oooo sorry to hear that. Could you give just for a moment to see me? Cos I’m on the way to see you to bring your clothes that you’d left.", + "time": "9:41am", + "isRead": true + }, + + { + "id": 3, + "senderId": 1, + "content": "Aww sweet, tysm! let me know when you arrive the ground floor!", + "time": "9:41am", + "isRead": true + }, + { + "id": 4, + "senderId": 0, + "content": "Hey lovey don’t forget to tell me when you are okay! Tmrw is the 24th and you leave soon!", + "time": "9:41am", + "isRead": true + }, + { + "id": 5, + "senderId": 0, + "content": "I couldn’t get to see Curtis or Senna so I should see you bfr you go!", + "time": "9:41am", + "isRead": true + } + ] + } + ] +} diff --git a/src/assets/data/userData.json b/src/assets/data/userData.json index 9b03e83..6486ee7 100644 --- a/src/assets/data/userData.json +++ b/src/assets/data/userData.json @@ -3,7 +3,7 @@ { "id": 0, "name": "My Status", - "profileImg": "/img/MyStatus.svg'", + "profileImg": "/img/MyStatus.svg", "isActive": true }, { diff --git a/src/assets/img/audioCall.svg b/src/assets/img/audio-call.svg similarity index 100% rename from src/assets/img/audioCall.svg rename to src/assets/img/audio-call.svg diff --git a/src/assets/img/bubble-gray.svg b/src/assets/img/bubble-gray.svg new file mode 100644 index 0000000..e030af8 --- /dev/null +++ b/src/assets/img/bubble-gray.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/img/bubble-green.svg b/src/assets/img/bubble-green.svg new file mode 100644 index 0000000..30cae03 --- /dev/null +++ b/src/assets/img/bubble-green.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/img/checkmark.svg b/src/assets/img/checkmark.svg new file mode 100644 index 0000000..817ef99 --- /dev/null +++ b/src/assets/img/checkmark.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/img/homeIndicator.svg b/src/assets/img/home-indicator.svg similarity index 100% rename from src/assets/img/homeIndicator.svg rename to src/assets/img/home-indicator.svg diff --git a/src/assets/img/rightSide.svg b/src/assets/img/right-side.svg similarity index 100% rename from src/assets/img/rightSide.svg rename to src/assets/img/right-side.svg diff --git a/src/assets/img/videoCall.svg b/src/assets/img/video-call.svg similarity index 100% rename from src/assets/img/videoCall.svg rename to src/assets/img/video-call.svg diff --git a/src/components/Chat/ChatBubble.tsx b/src/components/Chat/ChatBubble.tsx new file mode 100644 index 0000000..b399834 --- /dev/null +++ b/src/components/Chat/ChatBubble.tsx @@ -0,0 +1,113 @@ +import styled from 'styled-components'; +import CheckMark from '../../assets/img/checkmark.svg'; +import BubbleGreen from '../../assets/img/bubble-green.svg'; +import BubbleGray from '../../assets/img/bubble-gray.svg'; + +const ChatBubbleContainer = styled.div<{ isSentByMe: boolean }>` + width: 18.6rem; + margin-top: 0.31rem; + margin-left: ${(props) => (props.isSentByMe ? 'auto' : '0.44rem')}; + margin-right: ${(props) => (props.isSentByMe ? '0.44rem' : 'auto')}; + position: relative; +`; + +const BubbleImg = styled.img<{ isSentByMe: boolean }>` + width: 0.5rem; + height: 0.9375rem; + position: absolute; + left: ${(props) => (props.isSentByMe ? '18.125rem' : '0')}; + right: ${(props) => (props.isSentByMe ? '0' : '18.125rem')}; + bottom: 0; +`; + +const BubbleRectangle = styled.div<{ isSentByMe: boolean }>` + display: flex; + width: 17.125rem; + padding: 0.5rem; + flex-direction: column; + gap: 0.625rem; + border-radius: 0.375rem; + background: ${(props) => (props.isSentByMe ? '#E1F3D2' : '#eaeaea')}; + margin-left: ${(props) => (props.isSentByMe ? '0' : 'auto')}; + margin-right: ${(props) => (props.isSentByMe ? 'auto' : '0')}; +`; + +const TextContainer = styled.div` + display: flex; + flex-direction: column; + align-items: flex-end; + position: relative; +`; + +const ChatText = styled.div` + width: 17.125rem; + color: #000; + font-family: 'SF Pro Text'; + font-size: 1.0625rem; + font-style: normal; + font-weight: 400; + line-height: 1.375rem; + letter-spacing: -0.02563rem; + margin-top: 0; + margin-left: 0; +`; + +const MessageInfoContainer = styled.div` + display: flex; + flex-direction: row; + align-items: center; + margin-top: 0.25rem; +`; + +const TimeText = styled.div<{ isSentByMe: boolean }>` + color: rgba(0, 0, 0, 0.15); + font-family: 'SF Pro Display'; + font-size: 0.75rem; + font-style: normal; + font-weight: 500; + margin-bottom: 0; + margin-right: ${(props) => (props.isSentByMe ? '0.25rem' : '0')}; +`; + +const CheckImg = styled.img<{ isSentByMe: boolean }>` + width: 0.9375rem; + height: 0.875rem; + position: relative; + margin-bottom: 0; + margin-right: 0; + display: ${(props) => (props.isSentByMe ? 'inline' : 'none')}; +`; + +interface ChatBubbleProps { + isSentByMe: boolean; + content: string; + time: string; + isRead: boolean; +} + +export default function ChatBubble(props: ChatBubbleProps) { + const { isSentByMe, content, time, isRead } = props; + + return ( + + + + + {content} + + {time} + + + + + + ); +} diff --git a/src/components/Chat/ChattingRoom.tsx b/src/components/Chat/ChattingRoom.tsx index 46fe83e..6f5be7d 100644 --- a/src/components/Chat/ChattingRoom.tsx +++ b/src/components/Chat/ChattingRoom.tsx @@ -1,11 +1,27 @@ import styled from 'styled-components'; +import ChatBubble from './ChatBubble'; +import ChatData from '../../assets/data/chatData.json'; const ChattingRoomContainer = styled.div` width: 23.4375rem; height: 40.37rem; flex-direction: column; + overflow-y: auto; `; export default function ChattingRoom() { - return ; + const chatList = ChatData.chattings[0].chatList; + return ( + + {chatList.map((chat) => ( + + ))} + + ); } diff --git a/src/components/Chat/TitleBar.tsx b/src/components/Chat/TitleBar.tsx index 3e895b3..3642f50 100644 --- a/src/components/Chat/TitleBar.tsx +++ b/src/components/Chat/TitleBar.tsx @@ -1,7 +1,7 @@ import styled from 'styled-components'; import Left from '../../assets/img/left.svg'; -import VideoCall from '../../assets/img/videoCall.svg'; -import AudioCall from '../../assets/img/audioCall.svg'; +import VideoCall from '../../assets/img/video-call.svg'; +import AudioCall from '../../assets/img/audio-call.svg'; const TitleBarContainer = styled.div` width: 23.4375rem; @@ -26,7 +26,7 @@ const LeftImg = styled.img` `; const LeftText = styled.div` - color: var(--Whatsapp-Green, #1bd742); + color: #1bd742; font-family: 'SF Pro Text'; font-size: 1.0625rem; font-style: normal; @@ -93,16 +93,15 @@ const CallImg = styled.img` height: 1.5rem; `; -interface ChatTitleProps { - userId: number; +interface TitleBarProps { userName: string; profileImg: string; isActive: boolean; handleChangeUser?: () => void; } -export default function TitleBar(props: ChatTitleProps) { - const { userId, userName, profileImg, isActive, handleChangeUser } = props; +export default function TitleBar(props: TitleBarProps) { + const { userName, profileImg, isActive, handleChangeUser } = props; return ( diff --git a/src/components/HomeIndicator.tsx b/src/components/HomeIndicator.tsx index e9c9caa..b5ad8d2 100644 --- a/src/components/HomeIndicator.tsx +++ b/src/components/HomeIndicator.tsx @@ -1,5 +1,5 @@ import styled from 'styled-components'; -import HomeBar from '../assets/img/homeIndicator.svg'; +import HomeBar from '../assets/img/home-indicator.svg'; const HomeBarContainer = styled.div` width: 23.4375rem; diff --git a/src/components/StatusBar.tsx b/src/components/StatusBar.tsx index 9ea2d25..d8744ec 100644 --- a/src/components/StatusBar.tsx +++ b/src/components/StatusBar.tsx @@ -1,6 +1,6 @@ import { useState, useEffect } from 'react'; import styled from 'styled-components'; -import rightSide from '../assets/img/rightSide.svg'; +import rightSide from '../assets/img/right-side.svg'; const StatusBarContainer = styled.div` width: 23.4375rem; diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..e7719be --- /dev/null +++ b/src/index.css @@ -0,0 +1,34 @@ +@font-face { + font-family: 'SF Pro Text'; + font-style: normal; + font-weight: 400; + src: url('https://raw.githubusercontent.com/blaisck/sfwin/master/SFPro/TrueType/SFProText-Regular.ttf'); +} + +@font-face { + font-family: 'SF Pro Text'; + font-style: normal; + font-weight: 500; + src: url('https://raw.githubusercontent.com/blaisck/sfwin/master/SFPro/TrueType/SFProText-Medium.ttf'); +} + +@font-face { + font-family: 'SF Pro Text'; + font-style: normal; + font-weight: 600; + src: url('https://raw.githubusercontent.com/blaisck/sfwin/master/SFPro/TrueType/SFProText-SemiBold.ttf'); +} + +@font-face { + font-family: 'SF Pro Text'; + font-style: normal; + font-weight: 700; + src: url('https://raw.githubusercontent.com/blaisck/sfwin/master/SFPro/TrueType/SFProText-Bold.ttf'); +} + +@font-face { + font-family: 'SF Pro Display'; + font-style: normal; + font-weight: 500; + src: url('https://raw.githubusercontent.com/blaisck/sfwin/master/SFPro/TrueType/SFProDisplay-Medium.ttf'); +} diff --git a/src/index.tsx b/src/index.tsx index 88e8a31..7ffc198 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,6 +1,7 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; +import './index.css'; const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement diff --git a/src/pages/ChattingPage.tsx b/src/pages/ChattingPage.tsx index 27f6929..8ebb658 100644 --- a/src/pages/ChattingPage.tsx +++ b/src/pages/ChattingPage.tsx @@ -2,15 +2,14 @@ import { useState } from 'react'; import TitleBar from '../components/Chat/TitleBar'; import ChattingRoom from '../components/Chat/ChattingRoom'; import ChatInput from '../components/Chat/ChatInput'; -import userData from '../assets/data/userData.json'; +import UserData from '../assets/data/userData.json'; export default function ChattingPage() { - const [partner] = useState(userData.users[1]); + const [partner] = useState(UserData.users[1]); return ( <> Date: Fri, 29 Mar 2024 15:17:37 +0900 Subject: [PATCH 05/46] =?UTF-8?q?feat:=20Redux=20Toolkit=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- git | 0 package-lock.json | 101 +++++++++++++++++- package.json | 4 +- .../{chatData.json => initialChatData.json} | 0 src/chatSlice.ts | 45 ++++++++ src/components/Chat/ChatInput.tsx | 33 ++++++ src/components/Chat/ChattingRoom.tsx | 5 +- src/index.tsx | 6 +- src/pages/ChattingPage.tsx | 4 +- src/store.ts | 14 +++ 10 files changed, 201 insertions(+), 11 deletions(-) create mode 100644 git rename src/assets/data/{chatData.json => initialChatData.json} (100%) create mode 100644 src/chatSlice.ts create mode 100644 src/store.ts diff --git a/git b/git new file mode 100644 index 0000000..e69de29 diff --git a/package-lock.json b/package-lock.json index 2e57482..b762c24 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "react-messenger-19th", "version": "0.1.0", "dependencies": { + "@reduxjs/toolkit": "^2.2.2", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", @@ -17,13 +18,14 @@ "@types/react-dom": "^18.2.22", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-redux": "^9.1.0", "react-router-dom": "^6.22.3", "react-scripts": "5.0.1", "styled-reset": "^4.5.2", "web-vitals": "^2.1.4" }, "devDependencies": { - "typescript": "^5.4.3" + "typescript": "^4.9.5" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -3363,6 +3365,38 @@ } } }, + "node_modules/@reduxjs/toolkit": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.2.2.tgz", + "integrity": "sha512-454GZrEx3G6QSYwIx9ROaso1HR6sTH8qyZBe3KEsdWVGU3ayV8jYCwdaEJV3vl9V6+pi3GRl+7Xl7AeDna6qwQ==", + "dependencies": { + "immer": "^10.0.3", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.0.1" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, + "node_modules/@reduxjs/toolkit/node_modules/immer": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.0.4.tgz", + "integrity": "sha512-cuBuGK40P/sk5IzWa9QPUaAdvPHjkk1c+xYsd9oZw+YQQEV+10G0P5uMpGctZZKnyQ+ibRO08bD25nWLmYi2pw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/@remix-run/router": { "version": "1.15.3", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.3.tgz", @@ -4342,6 +4376,11 @@ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==" }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + }, "node_modules/@types/ws": { "version": "8.5.10", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", @@ -14924,6 +14963,32 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, + "node_modules/react-redux": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.1.0.tgz", + "integrity": "sha512-6qoDzIO+gbrza8h3hjMA9aq4nwVFCKFtY2iLxCtVT38Swyy2C/dJCGBXHeHLtx6qlg/8qzc2MrhOeduf5K32wQ==", + "dependencies": { + "@types/use-sync-external-store": "^0.0.3", + "use-sync-external-store": "^1.0.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25", + "react": "^18.0", + "react-native": ">=0.69", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react-native": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, "node_modules/react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", @@ -15089,6 +15154,19 @@ "node": ">=8" } }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "peerDependencies": { + "redux": "^5.0.0" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz", @@ -15236,6 +15314,11 @@ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" }, + "node_modules/reselect": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.0.tgz", + "integrity": "sha512-aw7jcGLDpSgNDyWBQLv2cedml85qd95/iszJjN988zX1t7AVRJi19d9kto5+W7oCfQ94gyo40dVbT6g2k4/kXg==" + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -17125,15 +17208,15 @@ } }, "node_modules/typescript": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", - "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==", + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=14.17" + "node": ">=4.2.0" } }, "node_modules/unbox-primitive": { @@ -17278,6 +17361,14 @@ "requires-port": "^1.0.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index fbc69e2..9b755df 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { + "@reduxjs/toolkit": "^2.2.2", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", @@ -12,6 +13,7 @@ "@types/react-dom": "^18.2.22", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-redux": "^9.1.0", "react-router-dom": "^6.22.3", "react-scripts": "5.0.1", "styled-reset": "^4.5.2", @@ -42,6 +44,6 @@ ] }, "devDependencies": { - "typescript": "^5.4.3" + "typescript": "^4.9.5" } } diff --git a/src/assets/data/chatData.json b/src/assets/data/initialChatData.json similarity index 100% rename from src/assets/data/chatData.json rename to src/assets/data/initialChatData.json diff --git a/src/chatSlice.ts b/src/chatSlice.ts new file mode 100644 index 0000000..df1e479 --- /dev/null +++ b/src/chatSlice.ts @@ -0,0 +1,45 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import initialChatData from './assets/data/initialChatData.json'; + +const initialState = initialChatData; + +export const chatSlice = createSlice({ + name: 'chat', + initialState, + reducers: { + addChat: ( + state, + action: PayloadAction<{ + chatRoomId: number; + senderId: number; + content: string; + time: string; + isRead: boolean; + }> + ) => { + const { chatRoomId, senderId, content, time, isRead } = action.payload; + const chatRoom = state.chattings.find( + (room) => room.chatRoomId === chatRoomId + ); + if (chatRoom) { + const newChatId = + chatRoom.chatList.length > 0 + ? chatRoom.chatList[chatRoom.chatList.length - 1].id + 1 + : 0; + chatRoom.chatList.push({ + id: newChatId, + senderId, + content, + time, + isRead, + }); + } + }, + }, +}); + +// Actions export +export const { addChat } = chatSlice.actions; + +// Reducer export +export default chatSlice.reducer; diff --git a/src/components/Chat/ChatInput.tsx b/src/components/Chat/ChatInput.tsx index 1533009..9025ae0 100644 --- a/src/components/Chat/ChatInput.tsx +++ b/src/components/Chat/ChatInput.tsx @@ -4,6 +4,9 @@ import Plus from '../../assets/img/plus.svg'; import Attachment from '../../assets/img/attachment.svg'; import Camera from '../../assets/img/camera.svg'; import Microphone from '../../assets/img/microphone.svg'; +import initialChatData from '../../assets/data/initialChatData.json'; +import { useDispatch } from 'react-redux'; +import { addChat } from '../../chatSlice'; const InputContainer = styled.div` width: 23.4375rem; @@ -45,8 +48,30 @@ const Input = styled.input` letter-spacing: -0.02563rem; `; +const TodayContainer = styled.div` + display: inline-flex; + padding: 0.1875rem 1rem; + align-items: flex-start; + border-radius: 0.375rem; + background: #dbdfeb; + position: absolute; + top: 0.84rem; + bottom: 0.84rem; +`; + +const TodayText = styled.div` + color: #414350; + text-align: center; + font-family: 'SF Pro Text'; + font-size: 0.75rem; + font-style: normal; + font-weight: 600; + line-height: normal; +`; + export default function ChatInput() { const [value, setValue] = useState(''); + const dispatch = useDispatch(); const handleSubmit = (event: React.FormEvent) => { event.preventDefault(); @@ -54,6 +79,14 @@ export default function ChatInput() { alert('공백 없이 입력해주세요.'); return; } + + // 샘플 데이터 - 실제 사용 시 적절한 값으로 대체 + const chatRoomId = 0; // 예시 + const senderId = 1; // 예시 + const time = '9:45am'; // 현재 시각 등으로 대체 가능 + const isRead = true; // 상황에 따라 설정 + + dispatch(addChat({ chatRoomId, senderId, content: value, time, isRead })); setValue(''); }; diff --git a/src/components/Chat/ChattingRoom.tsx b/src/components/Chat/ChattingRoom.tsx index 6f5be7d..50c0719 100644 --- a/src/components/Chat/ChattingRoom.tsx +++ b/src/components/Chat/ChattingRoom.tsx @@ -1,6 +1,6 @@ import styled from 'styled-components'; import ChatBubble from './ChatBubble'; -import ChatData from '../../assets/data/chatData.json'; +import initialChatData from '../../assets/data/initialChatData.json'; const ChattingRoomContainer = styled.div` width: 23.4375rem; @@ -10,7 +10,8 @@ const ChattingRoomContainer = styled.div` `; export default function ChattingRoom() { - const chatList = ChatData.chattings[0].chatList; + const chatList = initialChatData.chattings[0].chatList; + return ( {chatList.map((chat) => ( diff --git a/src/index.tsx b/src/index.tsx index 7ffc198..3be48dc 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,5 +1,7 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; +import { Provider } from 'react-redux'; +import { store } from './store'; import App from './App'; import './index.css'; @@ -8,6 +10,8 @@ const root = ReactDOM.createRoot( ); root.render( - + + + ); diff --git a/src/pages/ChattingPage.tsx b/src/pages/ChattingPage.tsx index 8ebb658..7b9e6b4 100644 --- a/src/pages/ChattingPage.tsx +++ b/src/pages/ChattingPage.tsx @@ -2,10 +2,10 @@ import { useState } from 'react'; import TitleBar from '../components/Chat/TitleBar'; import ChattingRoom from '../components/Chat/ChattingRoom'; import ChatInput from '../components/Chat/ChatInput'; -import UserData from '../assets/data/userData.json'; +import userData from '../assets/data/userData.json'; export default function ChattingPage() { - const [partner] = useState(UserData.users[1]); + const [partner] = useState(userData.users[1]); return ( <> diff --git a/src/store.ts b/src/store.ts new file mode 100644 index 0000000..f030bd7 --- /dev/null +++ b/src/store.ts @@ -0,0 +1,14 @@ +import { configureStore } from '@reduxjs/toolkit'; +import chatReducer from './chatSlice'; + +export const store = configureStore({ + reducer: { + chat: chatReducer, // chat 기능에 대한 리듀서를 스토어에 추가 + }, +}); + +// 스토어의 상태 타입을 추론하기 위한 타입 +export type RootState = ReturnType; + +// App 디스패치 함수의 타입을 추론하기 위한 타입 +export type AppDispatch = typeof store.dispatch; From b40fe8473c46be0f69870ba0ec635732d815febe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Fri, 29 Mar 2024 15:32:30 +0900 Subject: [PATCH 06/46] =?UTF-8?q?feat:=20=EC=83=88=EB=A1=9C=20=EC=9E=85?= =?UTF-8?q?=EB=A0=A5=EB=90=9C=20=EA=B0=92=20=EC=B6=9C=EB=A0=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/chatSlice.ts | 2 +- src/components/Chat/ChatInput.tsx | 4 ++-- src/components/Chat/ChattingRoom.tsx | 7 ++++++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/chatSlice.ts b/src/chatSlice.ts index df1e479..abd7a8c 100644 --- a/src/chatSlice.ts +++ b/src/chatSlice.ts @@ -24,7 +24,7 @@ export const chatSlice = createSlice({ if (chatRoom) { const newChatId = chatRoom.chatList.length > 0 - ? chatRoom.chatList[chatRoom.chatList.length - 1].id + 1 + ? chatRoom.chatList[chatRoom.chatList.length - 1].id + 1 // 마지막 항목의 id+1로 id 설정 : 0; chatRoom.chatList.push({ id: newChatId, diff --git a/src/components/Chat/ChatInput.tsx b/src/components/Chat/ChatInput.tsx index 9025ae0..9a96689 100644 --- a/src/components/Chat/ChatInput.tsx +++ b/src/components/Chat/ChatInput.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import styled from 'styled-components'; import Plus from '../../assets/img/plus.svg'; import Attachment from '../../assets/img/attachment.svg'; @@ -82,7 +82,7 @@ export default function ChatInput() { // 샘플 데이터 - 실제 사용 시 적절한 값으로 대체 const chatRoomId = 0; // 예시 - const senderId = 1; // 예시 + const senderId = 0; // 예시 const time = '9:45am'; // 현재 시각 등으로 대체 가능 const isRead = true; // 상황에 따라 설정 diff --git a/src/components/Chat/ChattingRoom.tsx b/src/components/Chat/ChattingRoom.tsx index 50c0719..9ccdef1 100644 --- a/src/components/Chat/ChattingRoom.tsx +++ b/src/components/Chat/ChattingRoom.tsx @@ -1,6 +1,8 @@ import styled from 'styled-components'; import ChatBubble from './ChatBubble'; import initialChatData from '../../assets/data/initialChatData.json'; +import { useSelector } from 'react-redux'; +import { RootState } from '../../store'; const ChattingRoomContainer = styled.div` width: 23.4375rem; @@ -10,10 +12,13 @@ const ChattingRoomContainer = styled.div` `; export default function ChattingRoom() { - const chatList = initialChatData.chattings[0].chatList; + const chatList = useSelector( + (state: RootState) => state.chat.chattings[0].chatList + ); // Redux 스토어에서 채팅 데이터 상태를 가져옴 return ( + {/* chatList가 Redux 스토어에서 가져온 데이터를 기반으로 반복 렌더링 */} {chatList.map((chat) => ( Date: Fri, 29 Mar 2024 16:19:48 +0900 Subject: [PATCH 07/46] =?UTF-8?q?feat:=20=EC=83=88=20=EB=A9=94=EC=8B=9C?= =?UTF-8?q?=EC=A7=80=20=EC=A0=84=EC=86=A1=20=EC=8B=9C=EA=B0=84=20=EC=B6=9C?= =?UTF-8?q?=EB=A0=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Chat/ChatInput.tsx | 25 ++----------------------- src/components/Chat/ChattingRoom.tsx | 21 +++++++++++++++++++++ src/components/Chat/FormatTime.ts | 10 ++++++++++ 3 files changed, 33 insertions(+), 23 deletions(-) create mode 100644 src/components/Chat/FormatTime.ts diff --git a/src/components/Chat/ChatInput.tsx b/src/components/Chat/ChatInput.tsx index 9a96689..252c9c8 100644 --- a/src/components/Chat/ChatInput.tsx +++ b/src/components/Chat/ChatInput.tsx @@ -4,7 +4,7 @@ import Plus from '../../assets/img/plus.svg'; import Attachment from '../../assets/img/attachment.svg'; import Camera from '../../assets/img/camera.svg'; import Microphone from '../../assets/img/microphone.svg'; -import initialChatData from '../../assets/data/initialChatData.json'; +import FormatTime from './FormatTime'; import { useDispatch } from 'react-redux'; import { addChat } from '../../chatSlice'; @@ -48,27 +48,6 @@ const Input = styled.input` letter-spacing: -0.02563rem; `; -const TodayContainer = styled.div` - display: inline-flex; - padding: 0.1875rem 1rem; - align-items: flex-start; - border-radius: 0.375rem; - background: #dbdfeb; - position: absolute; - top: 0.84rem; - bottom: 0.84rem; -`; - -const TodayText = styled.div` - color: #414350; - text-align: center; - font-family: 'SF Pro Text'; - font-size: 0.75rem; - font-style: normal; - font-weight: 600; - line-height: normal; -`; - export default function ChatInput() { const [value, setValue] = useState(''); const dispatch = useDispatch(); @@ -83,7 +62,7 @@ export default function ChatInput() { // 샘플 데이터 - 실제 사용 시 적절한 값으로 대체 const chatRoomId = 0; // 예시 const senderId = 0; // 예시 - const time = '9:45am'; // 현재 시각 등으로 대체 가능 + const time = FormatTime(new Date()); // 현재 시각을 포맷팅하여 사용 const isRead = true; // 상황에 따라 설정 dispatch(addChat({ chatRoomId, senderId, content: value, time, isRead })); diff --git a/src/components/Chat/ChattingRoom.tsx b/src/components/Chat/ChattingRoom.tsx index 9ccdef1..dd902a7 100644 --- a/src/components/Chat/ChattingRoom.tsx +++ b/src/components/Chat/ChattingRoom.tsx @@ -11,6 +11,27 @@ const ChattingRoomContainer = styled.div` overflow-y: auto; `; +const TodayContainer = styled.div` + display: inline-flex; + padding: 0.1875rem 1rem; + align-items: flex-start; + border-radius: 0.375rem; + background: #dbdfeb; + position: absolute; + top: 0.84rem; + bottom: 0.84rem; +`; + +const TodayText = styled.div` + color: #414350; + text-align: center; + font-family: 'SF Pro Text'; + font-size: 0.75rem; + font-style: normal; + font-weight: 600; + line-height: normal; +`; + export default function ChattingRoom() { const chatList = useSelector( (state: RootState) => state.chat.chattings[0].chatList diff --git a/src/components/Chat/FormatTime.ts b/src/components/Chat/FormatTime.ts new file mode 100644 index 0000000..6fbf335 --- /dev/null +++ b/src/components/Chat/FormatTime.ts @@ -0,0 +1,10 @@ +export default function FormatTime(date: Date): string { + let hours = date.getHours(); + let minutes = date.getMinutes(); + const ampm = hours >= 12 ? 'pm' : 'am'; + hours = hours % 12 || 12; // 시간이 0이면 12로 변환 + const minutesStr = `${minutes < 10 ? '0' : ''}${minutes}`; + const strTime = `${hours}:${minutesStr}${ampm}`; + + return strTime; +} From 11a210cd817fdfe7d2ae3a40cbcc37ca74ee5dab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Fri, 29 Mar 2024 17:38:56 +0900 Subject: [PATCH 08/46] =?UTF-8?q?chore:=20=ED=98=84=EC=9E=AC=20=EC=8B=9C?= =?UTF-8?q?=EA=B0=81=EC=9D=84=20=EB=AC=B8=EC=9E=90=EC=97=B4=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=ED=99=98=ED=95=98=EC=97=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/data/initialChatData.json | 12 ++++++------ src/components/Chat/ChatInput.tsx | 2 +- src/components/Chat/ChattingRoom.tsx | 11 ++++++++--- src/components/Chat/FormatTime.ts | 4 +++- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/assets/data/initialChatData.json b/src/assets/data/initialChatData.json index 1f0defb..8914d6b 100644 --- a/src/assets/data/initialChatData.json +++ b/src/assets/data/initialChatData.json @@ -8,21 +8,21 @@ "id": 0, "senderId": 0, "content": "Lovely you on the way? I had other plans after the meet-up, I just gave ur IG to Charlie just in case, hope you don’t mind.", - "time": "9:41am", + "time": "2024-03-28T09:41:00", "isRead": true }, { "id": 1, "senderId": 1, "content": "Gosh I’m a bit under the weather, so I just came back to my flat. Would you pls let him know I’m gonna drop dm? So sorry, and thx for that. I don’t mind at all.", - "time": "9:41am", + "time": "2024-03-28T09:41:00", "isRead": true }, { "id": 2, "senderId": 0, "content": "Oooo sorry to hear that. Could you give just for a moment to see me? Cos I’m on the way to see you to bring your clothes that you’d left.", - "time": "9:41am", + "time": "2024-03-28T09:41:00", "isRead": true }, @@ -30,21 +30,21 @@ "id": 3, "senderId": 1, "content": "Aww sweet, tysm! let me know when you arrive the ground floor!", - "time": "9:41am", + "time": "2024-03-28T09:41:00", "isRead": true }, { "id": 4, "senderId": 0, "content": "Hey lovey don’t forget to tell me when you are okay! Tmrw is the 24th and you leave soon!", - "time": "9:41am", + "time": "2024-03-28T09:41:00", "isRead": true }, { "id": 5, "senderId": 0, "content": "I couldn’t get to see Curtis or Senna so I should see you bfr you go!", - "time": "9:41am", + "time": "2024-03-28T09:41:00", "isRead": true } ] diff --git a/src/components/Chat/ChatInput.tsx b/src/components/Chat/ChatInput.tsx index 252c9c8..ee7cefc 100644 --- a/src/components/Chat/ChatInput.tsx +++ b/src/components/Chat/ChatInput.tsx @@ -62,7 +62,7 @@ export default function ChatInput() { // 샘플 데이터 - 실제 사용 시 적절한 값으로 대체 const chatRoomId = 0; // 예시 const senderId = 0; // 예시 - const time = FormatTime(new Date()); // 현재 시각을 포맷팅하여 사용 + const time = new Date().toISOString(); // 현재 시각을 string으로 변환 const isRead = true; // 상황에 따라 설정 dispatch(addChat({ chatRoomId, senderId, content: value, time, isRead })); diff --git a/src/components/Chat/ChattingRoom.tsx b/src/components/Chat/ChattingRoom.tsx index dd902a7..6594cee 100644 --- a/src/components/Chat/ChattingRoom.tsx +++ b/src/components/Chat/ChattingRoom.tsx @@ -1,8 +1,10 @@ +import React from 'react'; import styled from 'styled-components'; import ChatBubble from './ChatBubble'; import initialChatData from '../../assets/data/initialChatData.json'; import { useSelector } from 'react-redux'; import { RootState } from '../../store'; +import FormatTime from './FormatTime'; const ChattingRoomContainer = styled.div` width: 23.4375rem; @@ -12,14 +14,18 @@ const ChattingRoomContainer = styled.div` `; const TodayContainer = styled.div` + width: 2.25rem; + height: 0.875rem; display: inline-flex; padding: 0.1875rem 1rem; - align-items: flex-start; + align-items: center; border-radius: 0.375rem; background: #dbdfeb; position: absolute; top: 0.84rem; bottom: 0.84rem; + position: absolute; + margin: 0.84rem 9.25rem 0.84rem 9.56rem; `; const TodayText = styled.div` @@ -36,7 +42,6 @@ export default function ChattingRoom() { const chatList = useSelector( (state: RootState) => state.chat.chattings[0].chatList ); // Redux 스토어에서 채팅 데이터 상태를 가져옴 - return ( {/* chatList가 Redux 스토어에서 가져온 데이터를 기반으로 반복 렌더링 */} @@ -45,7 +50,7 @@ export default function ChattingRoom() { key={chat.id} isSentByMe={chat.senderId === 0} content={chat.content} - time={chat.time} + time={FormatTime(chat.time)} isRead={chat.isRead} /> ))} diff --git a/src/components/Chat/FormatTime.ts b/src/components/Chat/FormatTime.ts index 6fbf335..ecf4cfb 100644 --- a/src/components/Chat/FormatTime.ts +++ b/src/components/Chat/FormatTime.ts @@ -1,4 +1,6 @@ -export default function FormatTime(date: Date): string { +export default function FormatTime(dateInput: Date | string): string { + const date = typeof dateInput === 'string' ? new Date(dateInput) : dateInput; + let hours = date.getHours(); let minutes = date.getMinutes(); const ampm = hours >= 12 ? 'pm' : 'am'; From e6edc240f6fda668fbd9a31ea13c8c8073e1d766 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Fri, 29 Mar 2024 18:01:51 +0900 Subject: [PATCH 09/46] =?UTF-8?q?feat:=20=EB=82=A0=EC=A7=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=EB=90=98=EB=8A=94=20=EC=A7=80=EC=A0=90=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EB=82=A0=EC=A7=9C=20=EC=B6=9C=EB=A0=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Chat/ChattingRoom.tsx | 68 ++++++++++++++++++++-------- 1 file changed, 50 insertions(+), 18 deletions(-) diff --git a/src/components/Chat/ChattingRoom.tsx b/src/components/Chat/ChattingRoom.tsx index 6594cee..7c79931 100644 --- a/src/components/Chat/ChattingRoom.tsx +++ b/src/components/Chat/ChattingRoom.tsx @@ -13,22 +13,17 @@ const ChattingRoomContainer = styled.div` overflow-y: auto; `; -const TodayContainer = styled.div` - width: 2.25rem; +const DateContainer = styled.div` height: 0.875rem; display: inline-flex; padding: 0.1875rem 1rem; align-items: center; border-radius: 0.375rem; background: #dbdfeb; - position: absolute; - top: 0.84rem; - bottom: 0.84rem; - position: absolute; margin: 0.84rem 9.25rem 0.84rem 9.56rem; `; -const TodayText = styled.div` +const DateText = styled.div` color: #414350; text-align: center; font-family: 'SF Pro Text'; @@ -38,22 +33,59 @@ const TodayText = styled.div` line-height: normal; `; +// 채팅 시간을 "dd/mm/yy" 형식의 문자열로 변환하는 함수 +function formatDateToDMY(dateString: Date | string) { + const date = new Date(dateString); + const day = date.getDate(); + const month = date.getMonth() + 1; // 월은 0부터 시작하므로 1을 더해줍니다. + const year = date.getFullYear() % 100; // 년도의 마지막 두 자리만 가져옵니다. + + return `${day < 10 ? `0${day}` : day}/${ + month < 10 ? `0${month}` : month + }/${year}`; +} + +// 오늘 날짜를 "dd/mm/yy" 형식의 문자열로 반환하는 함수 +function getTodayDateStringDMY() { + return formatDateToDMY(new Date()); +} + export default function ChattingRoom() { const chatList = useSelector( (state: RootState) => state.chat.chattings[0].chatList - ); // Redux 스토어에서 채팅 데이터 상태를 가져옴 + ); + + let lastDate = ''; // 마지막으로 처리한 채팅의 날짜를 저장 + return ( - {/* chatList가 Redux 스토어에서 가져온 데이터를 기반으로 반복 렌더링 */} - {chatList.map((chat) => ( - - ))} + {chatList.map((chat, index) => { + const chatDateDMY = formatDateToDMY(chat.time); // 현재 채팅의 날짜를 "dd/mm/yy" 형식으로 변환 + let showDateText = false; // 날짜 텍스트를 보여줄지 결정하는 플래그 + if (chatDateDMY !== lastDate) { + lastDate = chatDateDMY; // 마지막 날짜 업데이트 + showDateText = true; // 날짜가 변경되었으므로 텍스트 보여주기 + } + + const todayDateStringDMY = getTodayDateStringDMY(); // 오늘 날짜 문자열을 "dd/mm/yy" 형식으로 가져옴 + const isToday = chatDateDMY === todayDateStringDMY; // 현재 채팅 날짜가 오늘인지 확인 + + return ( + + {showDateText && ( + + {isToday ? 'Today' : chatDateDMY} + + )} + + + ); + })} ); } From 9cec1f9ea70f627cc69d8986c5f90471a63660c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Fri, 29 Mar 2024 20:11:48 +0900 Subject: [PATCH 10/46] =?UTF-8?q?feat:=20=EC=83=81=EB=8C=80=EB=B0=A9=20?= =?UTF-8?q?=ED=94=84=EB=A1=9C=ED=95=84=20=ED=81=B4=EB=A6=AD=EC=8B=9C=20?= =?UTF-8?q?=EC=B1=84=ED=8C=85=20=EC=A0=84=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/{ => img}/MyStatus.svg | 0 src/components/Chat/ChatBubble.tsx | 40 ++++++++++++++------------- src/components/Chat/ChatInput.tsx | 15 +++++----- src/components/Chat/ChattingRoom.tsx | 26 ++++++++++++------ src/components/Chat/TitleBar.tsx | 26 +++++++++++++++--- src/{ => features}/chatSlice.ts | 2 +- src/features/userSlice.ts | 26 ++++++++++++++++++ src/pages/ChattingPage.tsx | 41 +++++++++++++++++++++------- src/store.ts | 6 ++-- 9 files changed, 131 insertions(+), 51 deletions(-) rename public/{ => img}/MyStatus.svg (100%) rename src/{ => features}/chatSlice.ts (94%) create mode 100644 src/features/userSlice.ts diff --git a/public/MyStatus.svg b/public/img/MyStatus.svg similarity index 100% rename from public/MyStatus.svg rename to public/img/MyStatus.svg diff --git a/src/components/Chat/ChatBubble.tsx b/src/components/Chat/ChatBubble.tsx index b399834..ca814fa 100644 --- a/src/components/Chat/ChatBubble.tsx +++ b/src/components/Chat/ChatBubble.tsx @@ -3,33 +3,33 @@ import CheckMark from '../../assets/img/checkmark.svg'; import BubbleGreen from '../../assets/img/bubble-green.svg'; import BubbleGray from '../../assets/img/bubble-gray.svg'; -const ChatBubbleContainer = styled.div<{ isSentByMe: boolean }>` +const ChatBubbleContainer = styled.div<{ $isSentByMe: boolean }>` width: 18.6rem; margin-top: 0.31rem; - margin-left: ${(props) => (props.isSentByMe ? 'auto' : '0.44rem')}; - margin-right: ${(props) => (props.isSentByMe ? '0.44rem' : 'auto')}; + margin-left: ${(props) => (props.$isSentByMe ? 'auto' : '0.44rem')}; + margin-right: ${(props) => (props.$isSentByMe ? '0.44rem' : 'auto')}; position: relative; `; -const BubbleImg = styled.img<{ isSentByMe: boolean }>` +const BubbleImg = styled.img<{ $isSentByMe: boolean }>` width: 0.5rem; height: 0.9375rem; position: absolute; - left: ${(props) => (props.isSentByMe ? '18.125rem' : '0')}; - right: ${(props) => (props.isSentByMe ? '0' : '18.125rem')}; + left: ${(props) => (props.$isSentByMe ? '18.125rem' : '0')}; + right: ${(props) => (props.$isSentByMe ? '0' : '18.125rem')}; bottom: 0; `; -const BubbleRectangle = styled.div<{ isSentByMe: boolean }>` +const BubbleRectangle = styled.div<{ $isSentByMe: boolean }>` display: flex; width: 17.125rem; padding: 0.5rem; flex-direction: column; gap: 0.625rem; border-radius: 0.375rem; - background: ${(props) => (props.isSentByMe ? '#E1F3D2' : '#eaeaea')}; - margin-left: ${(props) => (props.isSentByMe ? '0' : 'auto')}; - margin-right: ${(props) => (props.isSentByMe ? 'auto' : '0')}; + background: ${(props) => (props.$isSentByMe ? '#E1F3D2' : '#eaeaea')}; + margin-left: ${(props) => (props.$isSentByMe ? '0' : 'auto')}; + margin-right: ${(props) => (props.$isSentByMe ? 'auto' : '0')}; `; const TextContainer = styled.div` @@ -59,23 +59,24 @@ const MessageInfoContainer = styled.div` margin-top: 0.25rem; `; -const TimeText = styled.div<{ isSentByMe: boolean }>` +const TimeText = styled.div<{ $isSentByMe: boolean }>` color: rgba(0, 0, 0, 0.15); font-family: 'SF Pro Display'; font-size: 0.75rem; font-style: normal; font-weight: 500; margin-bottom: 0; - margin-right: ${(props) => (props.isSentByMe ? '0.25rem' : '0')}; + margin-right: ${(props) => (props.$isSentByMe ? '0.25rem' : '0')}; `; -const CheckImg = styled.img<{ isSentByMe: boolean }>` +const CheckImg = styled.img<{ $isRead: boolean; $isSentByMe: boolean }>` width: 0.9375rem; height: 0.875rem; position: relative; margin-bottom: 0; margin-right: 0; - display: ${(props) => (props.isSentByMe ? 'inline' : 'none')}; + display: ${(props) => + props.$isRead && props.$isSentByMe ? 'inline' : 'none'}; `; interface ChatBubbleProps { @@ -89,21 +90,22 @@ export default function ChatBubble(props: ChatBubbleProps) { const { isSentByMe, content, time, isRead } = props; return ( - + - + {content} - {time} + {time} diff --git a/src/components/Chat/ChatInput.tsx b/src/components/Chat/ChatInput.tsx index ee7cefc..cbba5b9 100644 --- a/src/components/Chat/ChatInput.tsx +++ b/src/components/Chat/ChatInput.tsx @@ -1,12 +1,12 @@ -import { useState, useEffect } from 'react'; +import { useState } from 'react'; import styled from 'styled-components'; import Plus from '../../assets/img/plus.svg'; import Attachment from '../../assets/img/attachment.svg'; import Camera from '../../assets/img/camera.svg'; import Microphone from '../../assets/img/microphone.svg'; -import FormatTime from './FormatTime'; -import { useDispatch } from 'react-redux'; -import { addChat } from '../../chatSlice'; +import { useDispatch, useSelector } from 'react-redux'; +import { RootState } from '../../store'; +import { addChat } from '../../features/chatSlice'; const InputContainer = styled.div` width: 23.4375rem; @@ -49,19 +49,20 @@ const Input = styled.input` `; export default function ChatInput() { + const nowUser = useSelector((state: RootState) => state.user.nowUser); // 현재 사용자 상태 가져오기 const [value, setValue] = useState(''); const dispatch = useDispatch(); const handleSubmit = (event: React.FormEvent) => { event.preventDefault(); + if (!value.trim()) { alert('공백 없이 입력해주세요.'); return; } - // 샘플 데이터 - 실제 사용 시 적절한 값으로 대체 - const chatRoomId = 0; // 예시 - const senderId = 0; // 예시 + const chatRoomId = 0; // 이번 과제 예시 + const senderId = nowUser; const time = new Date().toISOString(); // 현재 시각을 string으로 변환 const isRead = true; // 상황에 따라 설정 diff --git a/src/components/Chat/ChattingRoom.tsx b/src/components/Chat/ChattingRoom.tsx index 7c79931..37e0e6d 100644 --- a/src/components/Chat/ChattingRoom.tsx +++ b/src/components/Chat/ChattingRoom.tsx @@ -1,7 +1,6 @@ import React from 'react'; import styled from 'styled-components'; import ChatBubble from './ChatBubble'; -import initialChatData from '../../assets/data/initialChatData.json'; import { useSelector } from 'react-redux'; import { RootState } from '../../store'; import FormatTime from './FormatTime'; @@ -11,6 +10,15 @@ const ChattingRoomContainer = styled.div` height: 40.37rem; flex-direction: column; overflow-y: auto; + overflow-x: hidden; + + // 웹킷 기반 브라우저를 위한 스타일 + &::-webkit-scrollbar { + width: 5px; + } + + // 파이어폭스 브라우저를 위한 스타일 + scrollbar-width: thin; `; const DateContainer = styled.div` @@ -37,8 +45,8 @@ const DateText = styled.div` function formatDateToDMY(dateString: Date | string) { const date = new Date(dateString); const day = date.getDate(); - const month = date.getMonth() + 1; // 월은 0부터 시작하므로 1을 더해줍니다. - const year = date.getFullYear() % 100; // 년도의 마지막 두 자리만 가져옵니다. + const month = date.getMonth() + 1; // 월은 0부터 시작하므로 1을 더해줌 + const year = date.getFullYear() % 100; // 년도의 마지막 두 자리만 가져옴 return `${day < 10 ? `0${day}` : day}/${ month < 10 ? `0${month}` : month @@ -51,23 +59,25 @@ function getTodayDateStringDMY() { } export default function ChattingRoom() { + const nowUser = useSelector((state: RootState) => state.user.nowUser); // 현재 사용자 상태 가져오기 const chatList = useSelector( (state: RootState) => state.chat.chattings[0].chatList ); - let lastDate = ''; // 마지막으로 처리한 채팅의 날짜를 저장 + let lastDate = ''; return ( {chatList.map((chat, index) => { const chatDateDMY = formatDateToDMY(chat.time); // 현재 채팅의 날짜를 "dd/mm/yy" 형식으로 변환 - let showDateText = false; // 날짜 텍스트를 보여줄지 결정하는 플래그 + let showDateText = false; + if (chatDateDMY !== lastDate) { - lastDate = chatDateDMY; // 마지막 날짜 업데이트 + lastDate = chatDateDMY; showDateText = true; // 날짜가 변경되었으므로 텍스트 보여주기 } - const todayDateStringDMY = getTodayDateStringDMY(); // 오늘 날짜 문자열을 "dd/mm/yy" 형식으로 가져옴 + const todayDateStringDMY = getTodayDateStringDMY(); const isToday = chatDateDMY === todayDateStringDMY; // 현재 채팅 날짜가 오늘인지 확인 return ( @@ -78,7 +88,7 @@ export default function ChattingRoom() { )} void; } export default function TitleBar(props: TitleBarProps) { - const { userName, profileImg, isActive, handleChangeUser } = props; + const { name, profileImg, isActive } = props; + + const nowUser = useSelector((state: RootState) => state.user.nowUser); + const userList = useSelector((state: RootState) => state.user.userList); + const dispatch = useDispatch(); + + useEffect(() => { + console.log(`현재 유저가 바뀌었습니다: ${nowUser}`); + }, [nowUser]); // nowUser가 변경될 때마다 이 effect를 실행합니다. + + const handleChangeUser = () => { + const currentIndex = userList.indexOf(nowUser); // 현재 nowUser의 인덱스 + const nextIndex = currentIndex === 0 ? 1 : 0; + const nextUser = userList[nextIndex]; + dispatch(changeUser(nextUser)); + }; return ( @@ -116,7 +134,7 @@ export default function TitleBar(props: TitleBarProps) { alt="유저 프로필" /> - {userName} + {name} {isActive ? ( online ) : ( diff --git a/src/chatSlice.ts b/src/features/chatSlice.ts similarity index 94% rename from src/chatSlice.ts rename to src/features/chatSlice.ts index abd7a8c..d535961 100644 --- a/src/chatSlice.ts +++ b/src/features/chatSlice.ts @@ -1,5 +1,5 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import initialChatData from './assets/data/initialChatData.json'; +import initialChatData from '../assets/data/initialChatData.json'; const initialState = initialChatData; diff --git a/src/features/userSlice.ts b/src/features/userSlice.ts new file mode 100644 index 0000000..13207c9 --- /dev/null +++ b/src/features/userSlice.ts @@ -0,0 +1,26 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import initialChatData from '../assets/data/initialChatData.json'; + +interface UserState { + nowUser: number; + userList: number[]; +} + +const initialState: UserState = { + nowUser: 0, + userList: initialChatData.chattings[0].userList, // 첫 번째 채팅방의 userList를 초기 상태로 설정 +}; + +export const userSlice = createSlice({ + name: 'user', + initialState, + reducers: { + changeUser: (state, action: PayloadAction) => { + state.nowUser = action.payload; + }, + }, +}); + +export const { changeUser } = userSlice.actions; + +export default userSlice.reducer; diff --git a/src/pages/ChattingPage.tsx b/src/pages/ChattingPage.tsx index 7b9e6b4..b503529 100644 --- a/src/pages/ChattingPage.tsx +++ b/src/pages/ChattingPage.tsx @@ -1,22 +1,43 @@ -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import TitleBar from '../components/Chat/TitleBar'; import ChattingRoom from '../components/Chat/ChattingRoom'; import ChatInput from '../components/Chat/ChatInput'; +import { useSelector } from 'react-redux'; +import { RootState } from '../store'; import userData from '../assets/data/userData.json'; +interface User { + id: number; + name: string; + profileImg: string; + isActive: boolean; +} + export default function ChattingPage() { - const [partner] = useState(userData.users[1]); + const nowUser = useSelector((state: RootState) => state.user.nowUser); + const userList = useSelector((state: RootState) => state.user.userList); + const [partner, setPartner] = useState(null); + + useEffect(() => { + const nextUser = userData.users.find((user) => user.id !== nowUser) ?? null; + setPartner(nextUser); + }, [nowUser, userList]); // nowUser 또는 userList가 변경될 때마다 effect를 실행 return ( <> - - - + {partner !== null ? ( + <> + + + + + ) : ( +

파트너 정보가 없습니다.

+ )} ); } diff --git a/src/store.ts b/src/store.ts index f030bd7..dde7770 100644 --- a/src/store.ts +++ b/src/store.ts @@ -1,9 +1,11 @@ import { configureStore } from '@reduxjs/toolkit'; -import chatReducer from './chatSlice'; +import chatReducer from './features/chatSlice'; +import userReducer from './features/userSlice'; export const store = configureStore({ reducer: { - chat: chatReducer, // chat 기능에 대한 리듀서를 스토어에 추가 + chat: chatReducer, + user: userReducer, }, }); From 7169eae854a60cce3d788186c430a653052cf981 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Fri, 29 Mar 2024 20:40:59 +0900 Subject: [PATCH 11/46] =?UTF-8?q?feat:=20chatList=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=EB=90=A0=20=EB=95=8C=EB=A7=88=EB=8B=A4=20=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=EB=A1=A4=20=ED=95=98=EB=8B=A8=EC=9C=BC=EB=A1=9C=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Chat/ChattingRoom.tsx | 23 +++++++++++++++-------- src/components/Chat/TitleBar.tsx | 4 ---- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/components/Chat/ChattingRoom.tsx b/src/components/Chat/ChattingRoom.tsx index 37e0e6d..5a4f607 100644 --- a/src/components/Chat/ChattingRoom.tsx +++ b/src/components/Chat/ChattingRoom.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useRef, useEffect } from 'react'; import styled from 'styled-components'; import ChatBubble from './ChatBubble'; import { useSelector } from 'react-redux'; @@ -12,13 +12,11 @@ const ChattingRoomContainer = styled.div` overflow-y: auto; overflow-x: hidden; - // 웹킷 기반 브라우저를 위한 스타일 + scrollbar-width: none; // Firefox + -ms-overflow-style: none; // Internet Explorer/Edge &::-webkit-scrollbar { - width: 5px; + display: none; // Chrome, Safari } - - // 파이어폭스 브라우저를 위한 스타일 - scrollbar-width: thin; `; const DateContainer = styled.div` @@ -63,12 +61,21 @@ export default function ChattingRoom() { const chatList = useSelector( (state: RootState) => state.chat.chattings[0].chatList ); + const ChattingRoomContainerRef = useRef(null); + + useEffect(() => { + // chatList가 변경될 때마다 실행됩니다. + if (ChattingRoomContainerRef.current) { + ChattingRoomContainerRef.current.scrollTop = + ChattingRoomContainerRef.current.scrollHeight; + } + }, [chatList]); // chatList가 변경될 때마다 useEffect를 다시 실행 let lastDate = ''; return ( - - {chatList.map((chat, index) => { + + {chatList.map((chat) => { const chatDateDMY = formatDateToDMY(chat.time); // 현재 채팅의 날짜를 "dd/mm/yy" 형식으로 변환 let showDateText = false; diff --git a/src/components/Chat/TitleBar.tsx b/src/components/Chat/TitleBar.tsx index 51f387f..6651ea4 100644 --- a/src/components/Chat/TitleBar.tsx +++ b/src/components/Chat/TitleBar.tsx @@ -110,10 +110,6 @@ export default function TitleBar(props: TitleBarProps) { const userList = useSelector((state: RootState) => state.user.userList); const dispatch = useDispatch(); - useEffect(() => { - console.log(`현재 유저가 바뀌었습니다: ${nowUser}`); - }, [nowUser]); // nowUser가 변경될 때마다 이 effect를 실행합니다. - const handleChangeUser = () => { const currentIndex = userList.indexOf(nowUser); // 현재 nowUser의 인덱스 const nextIndex = currentIndex === 0 ? 1 : 0; From 093caed54f4d4f150ac75cd00629d1776aa3c907 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Fri, 29 Mar 2024 21:31:26 +0900 Subject: [PATCH 12/46] =?UTF-8?q?docs:=20readme=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 52 ++++++++-------------------- src/components/Chat/ChatInput.tsx | 6 ++-- src/components/Chat/ChattingRoom.tsx | 12 +++---- src/components/Chat/TitleBar.tsx | 15 +++----- src/components/StatusBar.tsx | 2 +- src/pages/ChattingPage.tsx | 6 ++-- 6 files changed, 32 insertions(+), 61 deletions(-) diff --git a/README.md b/README.md index 82b92ad..d699d62 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,27 @@ -# 서론 - -안녕하세요 🙌🏻 19기 프론트 운영진 배성준입니다. 이번 미션에서는 드디어 투두리스트에서 벗어나 새로운 프로젝트인 **messenger** 만들기를 진행합니다. +# 미션 -이번주는 특별히 **디자이너와의 협업**으로 진행되는 미션입니다. 디자이너분께서 열심히 리디자인 한 메신저 프로젝트를 여러분들께서 구현해주시면 됩니다. +## 배포링크 -동시에, 이번주부터는 새로 **TypeScript**를 적용해보려고 합니다. +[배포링크]!(https://react-messenger-19th-tawny.vercel.app/) -프로젝트의 규모가 커지게 될 수록, 컴포넌트가 가지는 props의 종류 또한 다양해지게 됩니다. 무지성 코딩을 하다보면 가끔 이 props의 구성과 이름, 어떤 타입이 들어가야 하는지 헷갈리기 마련이죠. 보통 그럴 때 다시 컴포넌트 정의 부분으로 돌아가 props의 구성을 보고 오곤 합니다. +## Key Questions -하지만 이럴 때, typescript를 적용한다면 컴포넌트의 구성과 그 이름, 심지어 타입까지 한번에 자동완성으로 편리하게 관리할 수 있고, 생산성이 증대되겠죠. +1. JavaScript를 사용할때에 비해 TypeScript를 사용할 때의 장점은 무엇인가요? + 타입스크립트는 개발자가 변수, 함수 매개변수, 반환 값 등에 대해 명시적으로 타입을 선언할 수 있어 코드의 의도를 명확하게 전달할 수 있습니다. 타입스크립트를 처음 써봐서 아직 미숙한 부분이 많았는데 익숙해지면 자바스크립트보다 개발의 효율성과 유지 보수 측면에서 더 나을 것 같습니다. 특히 타입스크립트는 다른 파일에서 작업할 때 자동 완성 기능을 통해 props 종류를 보여줘서 해당 props의 이름과 타입을 확인할 수 있습니다. 또한 타입이 맞지 않는 경우에 오류로 확인할 수 있어 문제를 해결하는 데 편리하다고 느껴졌습니다. -또한, **React Hooks**에 조금 더 익숙해지는 것을 목표로 합니다. 여러 Hook들이 있지만 그 중에서도 `useState`, `useEffect`, `useRef`를 중점적으로 사용해 보는 미션인데요, React를 사용하면서 굉장히 자주 쓰이는 Hook들이기 때문에 이 부분을 집중적으로 공부해 보아요! +2. 디자이너로부터 전달받은 피그마 링크 혹은, 피그마 캡처본 + [피그마 링크]!(https://www.figma.com/file/W7XzAbakNIOQxwiza0wLIo/CEOS-Messenger-Redesign?type=design&node-id=5-3617&mode=design&t=ic7kPkFW1UIT5FiD-11) -그럼 이번 미션도 파이팅입니다!!🎉 +[피그마 이미지]!(image-1.png) -# 미션 +3. 컴포넌트를 분리한 기준은 무엇인가요? + src 폴더 안에 크게 assets, components, features, pages, style로 나눠 assets 폴더 안에는 data와 img 폴더로 data 폴더 안에는 userData.json과 chatData.json을 포함시켰습니다. img 폴더 안에는 svg 이미지들을 저장했습니다. 4주차 과제 때 채팅방 이외에 추가로 구현할 것을 염두에 두고 components 폴더 안에 Chat 폴더를 생성해 작업했습니다. features 폴더 안에 Redux Toolkit 관련하여 chatSlice.ts 파일과 userSlice.ts 파일을 저장했습니다. pages 폴더 안에 ChattingPage.tsx 파일을 생성하여 채팅방 페이지를 구현했습니다. -## Key Questions +4. 디자인 시스템을 적용하면서 느낀 점은 무엇인가요? + 디자이너 분께서 그룹화를 꼼꼼하게 해주셔서 이를 바탕으로 고민 없이 스타일을 지정해줄 수 있어 편리하다고 느꼈습니다. 코드를 작성하고 즉각적으로 시각적 결과물을 확인하며 피그마 디자인과 맞춰가는 재미도 있었습니다. -- JavaScript를 사용할때에 비해 TypeScript를 사용할 때의 장점은 무엇인가요? -- 디자이너로부터 전달받은 피그마 링크 혹은, 피그마 캡처본 -- 컴포넌트를 분리한 기준은 무엇인가요? -- 디자인 시스템을 적용하면서 느낀 점은 무엇인가요? -- 디자이너와 소통하며 느낀점은 무엇인가요? +5. 디자이너와 소통하며 느낀점은 무엇인가요? + 디자이너 분께 질문이 있어 피그마에 코멘트를 남기거나 연락을 드렸을 때 항상 빠르게 답장을 주셔서 편하게 작업할 수 있었습니다. 추가로 요구를 드리더라도 이에 대해 바로바로 피드백을 주셔서 빠른 소통의 중요성을 느낄 수 있었습니다. ## 미션 목표 @@ -31,27 +30,6 @@ - useEffect와 useRef의 사용법을 이해합니다. - styled-components를 통한 CSS-in-JS 및 CSS Preprocessor의 사용법에 익숙해집니다. -## 기한 - -2024년 3월 29일 금요일 - -## 필수 구현 기능 - -- 피그마를 보고 [결과화면](https://3th-fb-messenger.netlify.app)과 같이 구현합니다. -- 디자인 시스템을 구축합니다. -- 채팅방 상단의 프로필을 클릭하면 사용자를 변경할 수 있습니다. -- 메세지를 보내면 채팅방 하단으로 스크롤을 이동시킵니다. -- 메세지에 유저 정보(프로필 사진, 이름)를 표시합니다. -- user와 message 데이터를 json 파일에 저장합니다. -- UI는 **반응형을 제외**하고 피그마파일을 따라서 진행합니다. - -### 추가 구현 기능 - -- 더블 클릭 하면 감정표현을 추가합니다. -- 그 외 추가하고 싶은 기능이 있다면 마음껏 추가해 주세요! - -참고로 이번 과제는 다음주까지 이어지는 과제이므로 **확장성**을 충분히 고려해 주세요. 참고로 **4주차 과제에서는 유저 및 기능 추가와 Routing을** 진행합니다. 이를 위해 [recoil](https://recoiljs.org/ko/)이나 [redux](https://ko.redux.js.org/introduction/getting-started/)를 이용한 상태관리를 미리 해보시는 것을 추천합니다!! 모두 공식문서 많이 읽어보시고 자신만의 상태관리 조합도 찾아보면 재밌을 거에요 XD - ## 링크 및 참고자료 - [React docs - Hook](https://ko.reactjs.org/docs/hooks-intro.html) diff --git a/src/components/Chat/ChatInput.tsx b/src/components/Chat/ChatInput.tsx index cbba5b9..7c2bf06 100644 --- a/src/components/Chat/ChatInput.tsx +++ b/src/components/Chat/ChatInput.tsx @@ -1,12 +1,12 @@ import { useState } from 'react'; import styled from 'styled-components'; +import { useDispatch, useSelector } from 'react-redux'; +import { RootState } from '../../store'; +import { addChat } from '../../features/chatSlice'; import Plus from '../../assets/img/plus.svg'; import Attachment from '../../assets/img/attachment.svg'; import Camera from '../../assets/img/camera.svg'; import Microphone from '../../assets/img/microphone.svg'; -import { useDispatch, useSelector } from 'react-redux'; -import { RootState } from '../../store'; -import { addChat } from '../../features/chatSlice'; const InputContainer = styled.div` width: 23.4375rem; diff --git a/src/components/Chat/ChattingRoom.tsx b/src/components/Chat/ChattingRoom.tsx index 5a4f607..c739c1e 100644 --- a/src/components/Chat/ChattingRoom.tsx +++ b/src/components/Chat/ChattingRoom.tsx @@ -1,8 +1,8 @@ import React, { useRef, useEffect } from 'react'; import styled from 'styled-components'; -import ChatBubble from './ChatBubble'; import { useSelector } from 'react-redux'; import { RootState } from '../../store'; +import ChatBubble from './ChatBubble'; import FormatTime from './FormatTime'; const ChattingRoomContainer = styled.div` @@ -61,20 +61,18 @@ export default function ChattingRoom() { const chatList = useSelector( (state: RootState) => state.chat.chattings[0].chatList ); - const ChattingRoomContainerRef = useRef(null); + const ChattingRoomRef = useRef(null); useEffect(() => { - // chatList가 변경될 때마다 실행됩니다. - if (ChattingRoomContainerRef.current) { - ChattingRoomContainerRef.current.scrollTop = - ChattingRoomContainerRef.current.scrollHeight; + if (ChattingRoomRef.current) { + ChattingRoomRef.current.scrollTop = ChattingRoomRef.current.scrollHeight; } }, [chatList]); // chatList가 변경될 때마다 useEffect를 다시 실행 let lastDate = ''; return ( - + {chatList.map((chat) => { const chatDateDMY = formatDateToDMY(chat.time); // 현재 채팅의 날짜를 "dd/mm/yy" 형식으로 변환 let showDateText = false; diff --git a/src/components/Chat/TitleBar.tsx b/src/components/Chat/TitleBar.tsx index 6651ea4..1bcec9d 100644 --- a/src/components/Chat/TitleBar.tsx +++ b/src/components/Chat/TitleBar.tsx @@ -1,11 +1,10 @@ -import { useEffect } from 'react'; import styled from 'styled-components'; -import Left from '../../assets/img/left.svg'; -import VideoCall from '../../assets/img/video-call.svg'; -import AudioCall from '../../assets/img/audio-call.svg'; import { useSelector, useDispatch } from 'react-redux'; import { changeUser } from '../../features/userSlice'; import { RootState } from '../../store'; +import Left from '../../assets/img/left.svg'; +import VideoCall from '../../assets/img/video-call.svg'; +import AudioCall from '../../assets/img/audio-call.svg'; const TitleBarContainer = styled.div` width: 23.4375rem; @@ -123,12 +122,8 @@ export default function TitleBar(props: TitleBarProps) { 12 - - + + {name} {isActive ? ( diff --git a/src/components/StatusBar.tsx b/src/components/StatusBar.tsx index d8744ec..ec7e873 100644 --- a/src/components/StatusBar.tsx +++ b/src/components/StatusBar.tsx @@ -44,7 +44,7 @@ export default function StatusBar() { setCurrentTime(`${hours}:${minutes}`); } - // 컴포넌트 마운트 시 현재 시간을 설정하고, 매 분마다 시간을 업데이트 + // 컴포넌트 마운트 시 현재 시간을 설정하고, 30초마다 시간을 업데이트 getCurrentTime(); const intervalId = setInterval(getCurrentTime, 30000); // 30초마다 getCurrentTime을 실행 diff --git a/src/pages/ChattingPage.tsx b/src/pages/ChattingPage.tsx index b503529..a948554 100644 --- a/src/pages/ChattingPage.tsx +++ b/src/pages/ChattingPage.tsx @@ -1,10 +1,10 @@ import { useState, useEffect } from 'react'; -import TitleBar from '../components/Chat/TitleBar'; -import ChattingRoom from '../components/Chat/ChattingRoom'; -import ChatInput from '../components/Chat/ChatInput'; import { useSelector } from 'react-redux'; import { RootState } from '../store'; import userData from '../assets/data/userData.json'; +import TitleBar from '../components/Chat/TitleBar'; +import ChattingRoom from '../components/Chat/ChattingRoom'; +import ChatInput from '../components/Chat/ChatInput'; interface User { id: number; From 365cfe8c61f4a5291fa936b0d47b27f7591e8131 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Fri, 29 Mar 2024 21:46:02 +0900 Subject: [PATCH 13/46] =?UTF-8?q?chore:=20StatusBar=20=EC=8B=9C=EA=B0=84?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/StatusBar.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/StatusBar.tsx b/src/components/StatusBar.tsx index ec7e873..d0c9428 100644 --- a/src/components/StatusBar.tsx +++ b/src/components/StatusBar.tsx @@ -44,9 +44,9 @@ export default function StatusBar() { setCurrentTime(`${hours}:${minutes}`); } - // 컴포넌트 마운트 시 현재 시간을 설정하고, 30초마다 시간을 업데이트 + // 컴포넌트 마운트 시 현재 시간을 설정하고, 1초마다 시간을 업데이트 getCurrentTime(); - const intervalId = setInterval(getCurrentTime, 30000); // 30초마다 getCurrentTime을 실행 + const intervalId = setInterval(getCurrentTime, 1000); // 1초마다 getCurrentTime을 실행 // 컴포넌트가 언마운트될 때 setInterval을 정리 return () => clearInterval(intervalId); From 5240f51a8dcae52965f1d660d1d6120bbad6b0dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Fri, 29 Mar 2024 22:19:05 +0900 Subject: [PATCH 14/46] =?UTF-8?q?docs:=20readme=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 45 ++++++++++++++++++++---------------- src/components/StatusBar.tsx | 4 +--- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index d699d62..2949d7b 100644 --- a/README.md +++ b/README.md @@ -2,26 +2,38 @@ ## 배포링크 -[배포링크]!(https://react-messenger-19th-tawny.vercel.app/) +- [배포링크](https://react-messenger-19th-tawny.vercel.app/) + +## 느낀 점 + +이번 과제를 하면서 타입스크립트를 처음 다뤄 어렵게 느껴졌습니다. 필수 기능 위주로 구현하고 Redux Toolkit으로 상태 관리를 해보았습니다. 4주차 과제 시작하기에 앞서 리팩토링을 해서 더 깔끔한 코드를 작성하고 싶습니다. ## Key Questions -1. JavaScript를 사용할때에 비해 TypeScript를 사용할 때의 장점은 무엇인가요? - 타입스크립트는 개발자가 변수, 함수 매개변수, 반환 값 등에 대해 명시적으로 타입을 선언할 수 있어 코드의 의도를 명확하게 전달할 수 있습니다. 타입스크립트를 처음 써봐서 아직 미숙한 부분이 많았는데 익숙해지면 자바스크립트보다 개발의 효율성과 유지 보수 측면에서 더 나을 것 같습니다. 특히 타입스크립트는 다른 파일에서 작업할 때 자동 완성 기능을 통해 props 종류를 보여줘서 해당 props의 이름과 타입을 확인할 수 있습니다. 또한 타입이 맞지 않는 경우에 오류로 확인할 수 있어 문제를 해결하는 데 편리하다고 느껴졌습니다. +**1. JavaScript를 사용할때에 비해 TypeScript를 사용할 때의 장점은 무엇인가요?** + +타입스크립트는 개발자가 변수, 함수 매개변수, 반환 값 등에 대해 명시적으로 타입을 선언할 수 있어 코드의 의도를 명확하게 전달할 수 있습니다. 타입스크립트를 처음 써봐서 아직 미숙한 부분이 많았는데 익숙해지면 자바스크립트보다 개발의 효율성과 유지 보수 측면에서 더 나을 것 같습니다. 특히 타입스크립트는 다른 파일에서 작업할 때 자동 완성 기능을 통해 props 종류를 보여줘서 해당 props의 이름과 타입을 확인할 수 있습니다. 또한 타입이 맞지 않는 경우에 오류로 확인할 수 있어 문제를 해결하는 데 편리하다고 느껴졌습니다. + +**2. 디자이너로부터 전달받은 피그마 링크 혹은, 피그마 캡처본** + +- [피그마 링크](https://www.figma.com/file/W7XzAbakNIOQxwiza0wLIo/CEOS-Messenger-Redesign?type=design&node-id=5-3617&mode=design&t=ic7kPkFW1UIT5FiD-11) + +![image](https://github.com/CEOS-Developers/react-messenger-19th/assets/126255206/c1bbe729-7ada-4da0-84fb-4aced07614da) + +**3. 컴포넌트를 분리한 기준은 무엇인가요?** -2. 디자이너로부터 전달받은 피그마 링크 혹은, 피그마 캡처본 - [피그마 링크]!(https://www.figma.com/file/W7XzAbakNIOQxwiza0wLIo/CEOS-Messenger-Redesign?type=design&node-id=5-3617&mode=design&t=ic7kPkFW1UIT5FiD-11) +src 폴더 안에 크게 assets, components, features, pages, style로 나눠 assets 폴더 안에는 data와 img 폴더로 data 폴더 안에는 userData.json과 chatData.json을 포함시켰습니다. img 폴더 안에는 svg 이미지들을 저장했습니다. 4주차 과제 때 채팅방 이외에 추가로 구현할 것을 염두에 두고 components 폴더 안에 Chat 폴더를 생성해 작업했습니다. features 폴더 안에 Redux Toolkit 관련하여 chatSlice.ts 파일과 userSlice.ts 파일을 저장했습니다. pages 폴더 안에 ChattingPage.tsx 파일을 생성하여 채팅방 페이지를 구현했습니다. -[피그마 이미지]!(image-1.png) +**4. 디자인 시스템을 적용하면서 느낀 점은 무엇인가요?** -3. 컴포넌트를 분리한 기준은 무엇인가요? - src 폴더 안에 크게 assets, components, features, pages, style로 나눠 assets 폴더 안에는 data와 img 폴더로 data 폴더 안에는 userData.json과 chatData.json을 포함시켰습니다. img 폴더 안에는 svg 이미지들을 저장했습니다. 4주차 과제 때 채팅방 이외에 추가로 구현할 것을 염두에 두고 components 폴더 안에 Chat 폴더를 생성해 작업했습니다. features 폴더 안에 Redux Toolkit 관련하여 chatSlice.ts 파일과 userSlice.ts 파일을 저장했습니다. pages 폴더 안에 ChattingPage.tsx 파일을 생성하여 채팅방 페이지를 구현했습니다. +디자이너 분께서 그룹화를 꼼꼼하게 해주셔서 이를 바탕으로 고민 없이 스타일을 지정해줄 수 있어 편리하다고 느꼈습니다. +지난 1,2주차 과제에서는 스스로 디자인까지 해야했는데 이번주 프론트-디자인 협동 과제를 수행하며 디자인을 신경쓰지 않고 피그마 그대로 구현하니 편리했습니다. 기능을 구현하며 깔끔한 디자인의 중요성도 깨닫게 되었습니다. +또한 코드를 작성하고 즉각적으로 시각적 결과물을 확인하며 피그마 디자인과 맞춰가는 재미도 느낄 수 있었습니다. -4. 디자인 시스템을 적용하면서 느낀 점은 무엇인가요? - 디자이너 분께서 그룹화를 꼼꼼하게 해주셔서 이를 바탕으로 고민 없이 스타일을 지정해줄 수 있어 편리하다고 느꼈습니다. 코드를 작성하고 즉각적으로 시각적 결과물을 확인하며 피그마 디자인과 맞춰가는 재미도 있었습니다. +**5. 디자이너와 소통하며 느낀점은 무엇인가요?** -5. 디자이너와 소통하며 느낀점은 무엇인가요? - 디자이너 분께 질문이 있어 피그마에 코멘트를 남기거나 연락을 드렸을 때 항상 빠르게 답장을 주셔서 편하게 작업할 수 있었습니다. 추가로 요구를 드리더라도 이에 대해 바로바로 피드백을 주셔서 빠른 소통의 중요성을 느낄 수 있었습니다. +디자이너 분께 질문이 있어 피그마에 코멘트를 남기거나 연락을 드렸을 때 항상 빠르게 답장을 주셔서 편하게 작업할 수 있었습니다. 추가로 요구를 드리더라도 이에 대해 바로바로 피드백을 주셔서 빠른 소통의 중요성을 느낄 수 있었습니다. +저도 협업을 진행할 때 팀원들과 빠르고 정확한 소통을 하려고 노력할 것입니다.! ## 미션 목표 @@ -32,13 +44,6 @@ ## 링크 및 참고자료 -- [React docs - Hook](https://ko.reactjs.org/docs/hooks-intro.html) +- [이미지 경로 설정](https://whales.tistory.com/95) - [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) diff --git a/src/components/StatusBar.tsx b/src/components/StatusBar.tsx index d0c9428..4535fc5 100644 --- a/src/components/StatusBar.tsx +++ b/src/components/StatusBar.tsx @@ -44,13 +44,11 @@ export default function StatusBar() { setCurrentTime(`${hours}:${minutes}`); } - // 컴포넌트 마운트 시 현재 시간을 설정하고, 1초마다 시간을 업데이트 getCurrentTime(); const intervalId = setInterval(getCurrentTime, 1000); // 1초마다 getCurrentTime을 실행 - // 컴포넌트가 언마운트될 때 setInterval을 정리 return () => clearInterval(intervalId); - }, []); // 의존성 배열이 빈 배열이므로, 이 effect는 컴포넌트가 마운트될 때 한 번만 실행 + }, []); return ( From 522c40cbb17d56ad429c5e89921cf776f5a313a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Sun, 31 Mar 2024 02:43:08 +0900 Subject: [PATCH 15/46] =?UTF-8?q?chore:=20=EB=AA=A8=EB=B0=94=EC=9D=BC=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Chat/ChatInput.tsx | 2 ++ src/style/GlobalStyles.tsx | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/src/components/Chat/ChatInput.tsx b/src/components/Chat/ChatInput.tsx index 7c2bf06..ab108f0 100644 --- a/src/components/Chat/ChatInput.tsx +++ b/src/components/Chat/ChatInput.tsx @@ -34,6 +34,8 @@ const InputBox = styled.div` `; const Input = styled.input` + display: flex; + align-items: center; width: 75%; margin-top: 0.22rem; margin-bottom: 0.28rem; diff --git a/src/style/GlobalStyles.tsx b/src/style/GlobalStyles.tsx index 1509612..8066497 100644 --- a/src/style/GlobalStyles.tsx +++ b/src/style/GlobalStyles.tsx @@ -11,6 +11,11 @@ const GlobalStyle = createGlobalStyle` align-items: center; } + #root { + height: 100dvh; + width: 100%; + } + input{ outline: none; } From 6031ad2972f36a453bafc550d91255100c79506a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Sun, 31 Mar 2024 03:18:18 +0900 Subject: [PATCH 16/46] =?UTF-8?q?chore:=20=EB=AA=A8=EB=B0=94=EC=9D=BC=20?= =?UTF-8?q?=EB=8C=80=EB=B9=84=20input=20=EC=9C=84=EC=B9=98=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Chat/ChatInput.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/components/Chat/ChatInput.tsx b/src/components/Chat/ChatInput.tsx index ab108f0..d55c5bd 100644 --- a/src/components/Chat/ChatInput.tsx +++ b/src/components/Chat/ChatInput.tsx @@ -34,12 +34,10 @@ const InputBox = styled.div` `; const Input = styled.input` - display: flex; - align-items: center; - width: 75%; - margin-top: 0.22rem; - margin-bottom: 0.28rem; - margin-left: 0.75rem; + width: 78%; + position: relative; + top: 0.22rem; + left: 0.75rem; border: none; outline: none; font-family: 'SF Pro Text'; @@ -48,6 +46,7 @@ const Input = styled.input` font-weight: 400; line-height: 1.375rem; letter-spacing: -0.02563rem; + background: transparent; `; export default function ChatInput() { From 5d47ab2bd692f48a05d949e198902f3bbd75bfed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Sun, 31 Mar 2024 03:53:40 +0900 Subject: [PATCH 17/46] =?UTF-8?q?chore:=20GlobalStyle=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 4 +--- src/components/Chat/ChatInput.tsx | 9 +++++---- src/style/GlobalStyles.tsx | 15 +++++++++------ 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 50d3dc8..2748acc 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,10 +6,8 @@ import StatusBar from './components/StatusBar'; import HomeIndicator from './components/HomeIndicator'; const Container = styled.div` - width: 23.4375rem; - height: 50.75rem; - border-radius: 1.25rem; background: #f6f6f6; + border-radius: 1.25rem; `; // StatusBar와 HomeIndicator 제외 영역 diff --git a/src/components/Chat/ChatInput.tsx b/src/components/Chat/ChatInput.tsx index d55c5bd..0a2f97e 100644 --- a/src/components/Chat/ChatInput.tsx +++ b/src/components/Chat/ChatInput.tsx @@ -23,6 +23,9 @@ const IconImg = styled.img` `; const InputBox = styled.div` + display: flex; + align-items: center; + justify-content: center; width: 14.375rem; height: 1.875rem; border-radius: 0.9375rem; @@ -34,10 +37,8 @@ const InputBox = styled.div` `; const Input = styled.input` - width: 78%; - position: relative; - top: 0.22rem; - left: 0.75rem; + width: 88%; + height: 100%; border: none; outline: none; font-family: 'SF Pro Text'; diff --git a/src/style/GlobalStyles.tsx b/src/style/GlobalStyles.tsx index 8066497..c6133c6 100644 --- a/src/style/GlobalStyles.tsx +++ b/src/style/GlobalStyles.tsx @@ -3,8 +3,15 @@ import reset from 'styled-reset'; const GlobalStyle = createGlobalStyle` ${reset} + html { + height: 100dvh; + display: flex; + justify-content: center; + align-items: center; + } - html,body{ + body{ + width: 23.4375rem; height: 100%; display: flex; justify-content: center; @@ -14,11 +21,7 @@ const GlobalStyle = createGlobalStyle` #root { height: 100dvh; width: 100%; - } - - input{ - outline: none; - } + } `; export default GlobalStyle; From f408895d336b0049cb71fc21e244fbb32e7103ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Thu, 4 Apr 2024 23:07:07 +0900 Subject: [PATCH 18/46] =?UTF-8?q?feat:=20=EB=9D=BC=EC=9A=B0=ED=8C=85=20?= =?UTF-8?q?=EA=B2=BD=EB=A1=9C=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 10 +++++++++- src/components/Chat/ChatInput.tsx | 2 +- src/components/Chat/ChattingRoom.tsx | 4 ++-- src/pages/ChatsPage.tsx | 5 +++++ src/pages/ContactInfoPage.tsx | 5 +++++ src/pages/EditContactPage.tsx | 5 +++++ src/pages/StatusPage.tsx | 5 +++++ 7 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 src/pages/ChatsPage.tsx create mode 100644 src/pages/ContactInfoPage.tsx create mode 100644 src/pages/EditContactPage.tsx create mode 100644 src/pages/StatusPage.tsx diff --git a/src/App.tsx b/src/App.tsx index 2748acc..e8bf1d8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,11 @@ import { BrowserRouter, Routes, Route } from 'react-router-dom'; import styled from 'styled-components'; -import ChattingPage from './pages/ChattingPage'; import GlobalStyles from './style/GlobalStyles'; +import ChattingPage from './pages/ChattingPage'; +import ContactInfoPage from './pages/ChattingPage'; +import ChatsPage from './pages/ChattingPage'; +import StatusPage from './pages/ChattingPage'; +import EditContactPage from './pages/ChattingPage'; import StatusBar from './components/StatusBar'; import HomeIndicator from './components/HomeIndicator'; @@ -26,6 +30,10 @@ function App() { } /> + } /> + } /> + } /> + } /> diff --git a/src/components/Chat/ChatInput.tsx b/src/components/Chat/ChatInput.tsx index 0a2f97e..1401fdf 100644 --- a/src/components/Chat/ChatInput.tsx +++ b/src/components/Chat/ChatInput.tsx @@ -37,7 +37,7 @@ const InputBox = styled.div` `; const Input = styled.input` - width: 88%; + width: 85%; height: 100%; border: none; outline: none; diff --git a/src/components/Chat/ChattingRoom.tsx b/src/components/Chat/ChattingRoom.tsx index c739c1e..839226b 100644 --- a/src/components/Chat/ChattingRoom.tsx +++ b/src/components/Chat/ChattingRoom.tsx @@ -86,7 +86,7 @@ export default function ChattingRoom() { const isToday = chatDateDMY === todayDateStringDMY; // 현재 채팅 날짜가 오늘인지 확인 return ( - + <> {showDateText && ( {isToday ? 'Today' : chatDateDMY} @@ -98,7 +98,7 @@ export default function ChattingRoom() { time={FormatTime(chat.time)} isRead={chat.isRead} /> - + ); })} diff --git a/src/pages/ChatsPage.tsx b/src/pages/ChatsPage.tsx new file mode 100644 index 0000000..60de08a --- /dev/null +++ b/src/pages/ChatsPage.tsx @@ -0,0 +1,5 @@ +import React from 'react'; + +export default function ChatsPage() { + return
ChatsPage
; +} diff --git a/src/pages/ContactInfoPage.tsx b/src/pages/ContactInfoPage.tsx new file mode 100644 index 0000000..733e78d --- /dev/null +++ b/src/pages/ContactInfoPage.tsx @@ -0,0 +1,5 @@ +import React from 'react'; + +export default function ContactInfoPage() { + return
ContactInfoPage
; +} diff --git a/src/pages/EditContactPage.tsx b/src/pages/EditContactPage.tsx new file mode 100644 index 0000000..cc16f72 --- /dev/null +++ b/src/pages/EditContactPage.tsx @@ -0,0 +1,5 @@ +import React from 'react'; + +export default function EditContactPage() { + return
EditContactPage
; +} diff --git a/src/pages/StatusPage.tsx b/src/pages/StatusPage.tsx new file mode 100644 index 0000000..67d2c10 --- /dev/null +++ b/src/pages/StatusPage.tsx @@ -0,0 +1,5 @@ +import React from 'react'; + +export default function StatusPage() { + return
StatusPage
; +} From 244ee1d6ff2df77bcb3276b1d7fd65b11ec68bcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Mon, 8 Apr 2024 23:17:19 +0900 Subject: [PATCH 19/46] =?UTF-8?q?feat:=20user=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/img/CharileMoore.svg | 9 +++++ public/img/ColinBearman.svg | 9 +++++ public/img/DeclanWalker.svg | 9 +++++ public/img/HaileyStephenson.svg | 9 +++++ public/img/LeahStimptson.svg | 9 +++++ public/img/SenaAdams.svg | 9 +++++ public/img/SiennaHwang.svg | 9 +++++ {src/assets => public}/img/checkmark.svg | 0 src/assets/data/userData.json | 42 ++++++++++++++++++++++++ 9 files changed, 105 insertions(+) create mode 100644 public/img/CharileMoore.svg create mode 100644 public/img/ColinBearman.svg create mode 100644 public/img/DeclanWalker.svg create mode 100644 public/img/HaileyStephenson.svg create mode 100644 public/img/LeahStimptson.svg create mode 100644 public/img/SenaAdams.svg create mode 100644 public/img/SiennaHwang.svg rename {src/assets => public}/img/checkmark.svg (100%) diff --git a/public/img/CharileMoore.svg b/public/img/CharileMoore.svg new file mode 100644 index 0000000..da0e510 --- /dev/null +++ b/public/img/CharileMoore.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/img/ColinBearman.svg b/public/img/ColinBearman.svg new file mode 100644 index 0000000..65b076e --- /dev/null +++ b/public/img/ColinBearman.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/img/DeclanWalker.svg b/public/img/DeclanWalker.svg new file mode 100644 index 0000000..3a3e224 --- /dev/null +++ b/public/img/DeclanWalker.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/img/HaileyStephenson.svg b/public/img/HaileyStephenson.svg new file mode 100644 index 0000000..bd3d4a6 --- /dev/null +++ b/public/img/HaileyStephenson.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/img/LeahStimptson.svg b/public/img/LeahStimptson.svg new file mode 100644 index 0000000..2268d16 --- /dev/null +++ b/public/img/LeahStimptson.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/img/SenaAdams.svg b/public/img/SenaAdams.svg new file mode 100644 index 0000000..ef9512b --- /dev/null +++ b/public/img/SenaAdams.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/img/SiennaHwang.svg b/public/img/SiennaHwang.svg new file mode 100644 index 0000000..6b50d67 --- /dev/null +++ b/public/img/SiennaHwang.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/img/checkmark.svg b/public/img/checkmark.svg similarity index 100% rename from src/assets/img/checkmark.svg rename to public/img/checkmark.svg diff --git a/src/assets/data/userData.json b/src/assets/data/userData.json index 6486ee7..3cc4339 100644 --- a/src/assets/data/userData.json +++ b/src/assets/data/userData.json @@ -11,6 +11,48 @@ "name": "Dain Park", "profileImg": "/img/DainPark.svg", "isActive": true + }, + { + "id": 2, + "name": "Leah Stimptson", + "profileImg": "/img/LeahStimptson.svg", + "isActive": true + }, + { + "id": 3, + "name": "Colin Bearman", + "profileImg": "/img/ColinBearman.svg", + "isActive": false + }, + { + "id": 4, + "name": "Charile Moore", + "profileImg": "/img/CharileMoore.svg", + "isActive": true + }, + { + "id": 5, + "name": "Hailey Stephenson", + "profileImg": "/img/HaileyStephenson.svg", + "isActive": false + }, + { + "id": 6, + "name": "Sena Adams", + "profileImg": "/img/SenaAdams.svg", + "isActive": false + }, + { + "id": 7, + "name": "Sienna Hwang", + "profileImg": "/img/SiennaHwang.svg", + "isActive": true + }, + { + "id": 8, + "name": "Declan Walker", + "profileImg": "/img/DeclanWalker.svg", + "isActive": false } ] } From 49a81cdd957fa2a9c3a89335bb35bf0ca327169e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Mon, 8 Apr 2024 23:45:13 +0900 Subject: [PATCH 20/46] =?UTF-8?q?feat:=20chat=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/data/initialChatData.json | 91 ++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/src/assets/data/initialChatData.json b/src/assets/data/initialChatData.json index 8914d6b..30a531f 100644 --- a/src/assets/data/initialChatData.json +++ b/src/assets/data/initialChatData.json @@ -48,6 +48,97 @@ "isRead": true } ] + }, + { + "chatRoomId": 1, + "userList": [0, 2], + "chatList": [ + { + "id": 0, + "senderId": 2, + "content": "Is that yours?", + "time": "2024-03-05T09:41:00", + "isRead": true + } + ] + }, + { + "chatRoomId": 2, + "userList": [0, 3], + "chatList": [ + { + "id": 0, + "senderId": 3, + "content": "I just want you to know that if it is another choice I could change, yes I ...", + "time": "2024-02-27T09:41:00", + "isRead": false + } + ] + }, + { + "chatRoomId": 3, + "userList": [0, 4], + "chatList": [ + { + "id": 0, + "senderId": 4, + "content": "Mate where???", + "time": "2024-02-15T09:41:00", + "isRead": true + } + ] + }, + { + "chatRoomId": 4, + "userList": [0, 5], + "chatList": [ + { + "id": 0, + "senderId": 5, + "content": "Photo", + "time": "2024-02-10T09:41:00", + "isRead": false + } + ] + }, + { + "chatRoomId": 5, + "userList": [0, 6], + "chatList": [ + { + "id": 0, + "senderId": 6, + "content": "You reacted 🥹 to “Now I arrived in Seoul! It’s insane. You have to see...", + "time": "2024-01-25T09:41:00", + "isRead": false + } + ] + }, + { + "chatRoomId": 6, + "userList": [0, 7], + "chatList": [ + { + "id": 0, + "senderId": 7, + "content": "언니 이거 봄?", + "time": "2024-01-15T09:41:00", + "isRead": true + } + ] + }, + { + "chatRoomId": 7, + "userList": [0, 8], + "chatList": [ + { + "id": 0, + "senderId": 0, + "content": "You reacted ❤️ to “Really honoured to be there with you, thank you for..", + "time": "2024-01-09T09:41:00", + "isRead": false + } + ] } ] } From 3292c1d9a586f8631034b6e308a9c7da1573ea2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Tue, 9 Apr 2024 00:14:32 +0900 Subject: [PATCH 21/46] =?UTF-8?q?style:=20=EB=82=A0=EC=A7=9C=EC=99=80=20?= =?UTF-8?q?=EC=B1=84=ED=8C=85=20=EC=82=AC=EC=9D=B4=20=EA=B0=84=EA=B2=A9=20?= =?UTF-8?q?=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- {public => src/assets}/img/checkmark.svg | 0 src/components/Chat/ChatBubble.tsx | 2 +- src/components/Chat/ChattingRoom.tsx | 16 +++++++++++----- 3 files changed, 12 insertions(+), 6 deletions(-) rename {public => src/assets}/img/checkmark.svg (100%) diff --git a/public/img/checkmark.svg b/src/assets/img/checkmark.svg similarity index 100% rename from public/img/checkmark.svg rename to src/assets/img/checkmark.svg diff --git a/src/components/Chat/ChatBubble.tsx b/src/components/Chat/ChatBubble.tsx index ca814fa..498993b 100644 --- a/src/components/Chat/ChatBubble.tsx +++ b/src/components/Chat/ChatBubble.tsx @@ -5,7 +5,7 @@ import BubbleGray from '../../assets/img/bubble-gray.svg'; const ChatBubbleContainer = styled.div<{ $isSentByMe: boolean }>` width: 18.6rem; - margin-top: 0.31rem; + margin-bottom: 0.31rem; margin-left: ${(props) => (props.$isSentByMe ? 'auto' : '0.44rem')}; margin-right: ${(props) => (props.$isSentByMe ? '0.44rem' : 'auto')}; position: relative; diff --git a/src/components/Chat/ChattingRoom.tsx b/src/components/Chat/ChattingRoom.tsx index 839226b..2e8a187 100644 --- a/src/components/Chat/ChattingRoom.tsx +++ b/src/components/Chat/ChattingRoom.tsx @@ -19,14 +19,20 @@ const ChattingRoomContainer = styled.div` } `; -const DateContainer = styled.div` +// DateContainer에 전달되는 props의 타입을 정의 +interface DateContainerProps { + isToday: boolean; +} + +const DateContainer = styled.div` height: 0.875rem; display: inline-flex; padding: 0.1875rem 1rem; align-items: center; border-radius: 0.375rem; background: #dbdfeb; - margin: 0.84rem 9.25rem 0.84rem 9.56rem; + margin: ${(props) => (props.isToday ? '0.73rem' : '0.84rem')} 9.25rem 0.84rem + 9.56rem; `; const DateText = styled.div` @@ -86,9 +92,9 @@ export default function ChattingRoom() { const isToday = chatDateDMY === todayDateStringDMY; // 현재 채팅 날짜가 오늘인지 확인 return ( - <> + {showDateText && ( - + {isToday ? 'Today' : chatDateDMY} )} @@ -98,7 +104,7 @@ export default function ChattingRoom() { time={FormatTime(chat.time)} isRead={chat.isRead} /> - + ); })}
From f8f230dd7445937130f3b4de9961d837486988c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Tue, 9 Apr 2024 03:38:34 +0900 Subject: [PATCH 22/46] =?UTF-8?q?feat:=20TopNavBar=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EB=B6=84=EB=A6=AC=ED=95=98=EC=97=AC=20?= =?UTF-8?q?=EC=9E=AC=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/img/call.svg | 4 + src/components/Chat/TitleBar.tsx | 62 +--------------- src/components/ContactInfo/ContactNavBar.tsx | 7 ++ src/components/TopNavBar/TopNavBar.tsx | 77 ++++++++++++++++++++ 4 files changed, 92 insertions(+), 58 deletions(-) create mode 100644 src/assets/img/call.svg create mode 100644 src/components/ContactInfo/ContactNavBar.tsx create mode 100644 src/components/TopNavBar/TopNavBar.tsx diff --git a/src/assets/img/call.svg b/src/assets/img/call.svg new file mode 100644 index 0000000..bc7ee06 --- /dev/null +++ b/src/assets/img/call.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/components/Chat/TitleBar.tsx b/src/components/Chat/TitleBar.tsx index 1bcec9d..d0df68c 100644 --- a/src/components/Chat/TitleBar.tsx +++ b/src/components/Chat/TitleBar.tsx @@ -2,41 +2,9 @@ import styled from 'styled-components'; import { useSelector, useDispatch } from 'react-redux'; import { changeUser } from '../../features/userSlice'; import { RootState } from '../../store'; +import TopNavBar from '../TopNavBar/TopNavBar'; import Left from '../../assets/img/left.svg'; -import VideoCall from '../../assets/img/video-call.svg'; -import AudioCall from '../../assets/img/audio-call.svg'; - -const TitleBarContainer = styled.div` - width: 23.4375rem; - height: 2.75rem; - border-bottom: 0.03125rem solid #a4a39e; - position: relative; -`; - -const LeftContainer = styled.div` - display: inline-flex; - padding: 0.625rem 0.5625rem; - align-items: flex-start; - gap: 0.3125rem; - position: absolute; - bottom: 0.13rem; - right: 20.1rem; -`; - -const LeftImg = styled.img` - width: 0.75rem; - height: 1.25rem; -`; - -const LeftText = styled.div` - color: #1bd742; - font-family: 'SF Pro Text'; - font-size: 1.0625rem; - font-style: normal; - font-weight: 400; - line-height: 1.375rem; /* 129.412% */ - letter-spacing: -0.0255rem; -`; +import Call from '../../assets/img/call.svg'; const ProfileContainer = styled.div` display: inline-flex; @@ -82,20 +50,6 @@ const OnlineText = styled.div` letter-spacing: -0.0015rem; `; -const RightContainer = styled.div` - display: inline-flex; - align-items: flex-start; - gap: 1.375rem; - position: absolute; - top: 0.63rem; - right: 0.94rem; -`; - -const CallImg = styled.img` - width: 1.5rem; - height: 1.5rem; -`; - interface TitleBarProps { name: string; profileImg: string; @@ -117,11 +71,7 @@ export default function TitleBar(props: TitleBarProps) { }; return ( - - - - 12 - + @@ -133,10 +83,6 @@ export default function TitleBar(props: TitleBarProps) { )} - - - - - + ); } diff --git a/src/components/ContactInfo/ContactNavBar.tsx b/src/components/ContactInfo/ContactNavBar.tsx new file mode 100644 index 0000000..fa2b9ec --- /dev/null +++ b/src/components/ContactInfo/ContactNavBar.tsx @@ -0,0 +1,7 @@ +import styled from 'styled-components'; +import TopNavBar from '../TopNavBar/TopNavBar'; +import Left from '../../assets/img/left.svg'; + +export default function ContactNavBar() { + return
ContactNavBar
; +} diff --git a/src/components/TopNavBar/TopNavBar.tsx b/src/components/TopNavBar/TopNavBar.tsx new file mode 100644 index 0000000..2a1a604 --- /dev/null +++ b/src/components/TopNavBar/TopNavBar.tsx @@ -0,0 +1,77 @@ +import styled from 'styled-components'; + +const NavBarContainer = styled.div` + width: 23.4375rem; + height: 2.75rem; + border-bottom: 0.03125rem solid #a4a39e; + position: relative; +`; + +const LeftContainer = styled.div` + display: inline-flex; + padding: 0.625rem 0.5625rem; + align-items: flex-start; + gap: 0.3125rem; + position: absolute; + bottom: 0.13rem; + right: 20.1rem; +`; + +const LeftImg = styled.img` + width: 0.75rem; + height: 1.25rem; +`; + +const Text = styled.div` + color: #1bd742; + font-family: 'SF Pro Text'; + font-size: 1.0625rem; + font-style: normal; + font-weight: 400; + line-height: 1.375rem; + letter-spacing: -0.0255rem; +`; + +const RightContainer = styled.div` + display: inline-flex; + align-items: flex-start; + gap: 1.375rem; + position: absolute; + top: 0.63rem; + right: 0.94rem; +`; + +const IconImg = styled.img` + height: 1.5rem; +`; + +interface TopNavBarProps { + leftImgSrc?: string; + leftText?: string; + rightImgSrc?: string; + rightText?: string; + children?: React.ReactNode; +} + +// props 추가 (leftImgSrc, leftText, rightImgSrcs, rightText) +export default function TopNavBar({ + leftImgSrc, + leftText, + rightImgSrc, + rightText, + children, +}: TopNavBarProps) { + return ( + + + {leftImgSrc && } + {leftText} + + {children} {/* 여기에 children을 렌더링 */} + + {rightImgSrc && } + {rightText && {rightText}} + + + ); +} From 63e89628c58a834c404db90a4ac57383415fbead Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Tue, 9 Apr 2024 04:23:24 +0900 Subject: [PATCH 23/46] =?UTF-8?q?feat:=20=EA=B0=81=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20TopNavBar=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 8 ++++---- src/assets/img/edit.svg | 3 +++ src/components/Chats/ChatsNavBar.tsx | 7 +++++++ src/components/{Chat => Chatting}/ChatBubble.tsx | 0 src/components/{Chat => Chatting}/ChatInput.tsx | 0 src/components/{Chat => Chatting}/ChattingRoom.tsx | 0 src/components/{Chat => Chatting}/FormatTime.ts | 0 src/components/{Chat => Chatting}/TitleBar.tsx | 10 ++++++++-- src/components/ContactInfo/ContactNavBar.tsx | 2 +- src/components/EditContact/EditNavBar.tsx | 6 ++++++ src/components/TopNavBar/TopNavBar.tsx | 2 +- src/pages/ChatsPage.tsx | 3 ++- src/pages/ChattingPage.tsx | 6 +++--- src/pages/ContactInfoPage.tsx | 4 ++-- src/pages/EditContactPage.tsx | 4 ++-- 15 files changed, 39 insertions(+), 16 deletions(-) create mode 100644 src/assets/img/edit.svg create mode 100644 src/components/Chats/ChatsNavBar.tsx rename src/components/{Chat => Chatting}/ChatBubble.tsx (100%) rename src/components/{Chat => Chatting}/ChatInput.tsx (100%) rename src/components/{Chat => Chatting}/ChattingRoom.tsx (100%) rename src/components/{Chat => Chatting}/FormatTime.ts (100%) rename src/components/{Chat => Chatting}/TitleBar.tsx (88%) create mode 100644 src/components/EditContact/EditNavBar.tsx diff --git a/src/App.tsx b/src/App.tsx index e8bf1d8..1d177ab 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,10 +2,10 @@ import { BrowserRouter, Routes, Route } from 'react-router-dom'; import styled from 'styled-components'; import GlobalStyles from './style/GlobalStyles'; import ChattingPage from './pages/ChattingPage'; -import ContactInfoPage from './pages/ChattingPage'; -import ChatsPage from './pages/ChattingPage'; -import StatusPage from './pages/ChattingPage'; -import EditContactPage from './pages/ChattingPage'; +import ContactInfoPage from './pages/ContactInfoPage'; +import ChatsPage from './pages/ChatsPage'; +import StatusPage from './pages/StatusPage'; +import EditContactPage from './pages/EditContactPage'; import StatusBar from './components/StatusBar'; import HomeIndicator from './components/HomeIndicator'; diff --git a/src/assets/img/edit.svg b/src/assets/img/edit.svg new file mode 100644 index 0000000..6a0c561 --- /dev/null +++ b/src/assets/img/edit.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/Chats/ChatsNavBar.tsx b/src/components/Chats/ChatsNavBar.tsx new file mode 100644 index 0000000..6d5e241 --- /dev/null +++ b/src/components/Chats/ChatsNavBar.tsx @@ -0,0 +1,7 @@ +import styled from 'styled-components'; +import TopNavBar from '../TopNavBar/TopNavBar'; +import Edit from '../../assets/img/edit.svg'; + +export default function ContactNavBar() { + return ; +} diff --git a/src/components/Chat/ChatBubble.tsx b/src/components/Chatting/ChatBubble.tsx similarity index 100% rename from src/components/Chat/ChatBubble.tsx rename to src/components/Chatting/ChatBubble.tsx diff --git a/src/components/Chat/ChatInput.tsx b/src/components/Chatting/ChatInput.tsx similarity index 100% rename from src/components/Chat/ChatInput.tsx rename to src/components/Chatting/ChatInput.tsx diff --git a/src/components/Chat/ChattingRoom.tsx b/src/components/Chatting/ChattingRoom.tsx similarity index 100% rename from src/components/Chat/ChattingRoom.tsx rename to src/components/Chatting/ChattingRoom.tsx diff --git a/src/components/Chat/FormatTime.ts b/src/components/Chatting/FormatTime.ts similarity index 100% rename from src/components/Chat/FormatTime.ts rename to src/components/Chatting/FormatTime.ts diff --git a/src/components/Chat/TitleBar.tsx b/src/components/Chatting/TitleBar.tsx similarity index 88% rename from src/components/Chat/TitleBar.tsx rename to src/components/Chatting/TitleBar.tsx index d0df68c..e66840c 100644 --- a/src/components/Chat/TitleBar.tsx +++ b/src/components/Chatting/TitleBar.tsx @@ -1,4 +1,5 @@ import styled from 'styled-components'; +import { useNavigate } from 'react-router-dom'; import { useSelector, useDispatch } from 'react-redux'; import { changeUser } from '../../features/userSlice'; import { RootState } from '../../store'; @@ -62,6 +63,11 @@ export default function TitleBar(props: TitleBarProps) { const nowUser = useSelector((state: RootState) => state.user.nowUser); const userList = useSelector((state: RootState) => state.user.userList); const dispatch = useDispatch(); + const navigate = useNavigate(); + + const goToContactInfoPage = () => { + navigate('/contact-info'); + }; const handleChangeUser = () => { const currentIndex = userList.indexOf(nowUser); // 현재 nowUser의 인덱스 @@ -72,9 +78,9 @@ export default function TitleBar(props: TitleBarProps) { return ( - + - + {name} {isActive ? ( online diff --git a/src/components/ContactInfo/ContactNavBar.tsx b/src/components/ContactInfo/ContactNavBar.tsx index fa2b9ec..eed2991 100644 --- a/src/components/ContactInfo/ContactNavBar.tsx +++ b/src/components/ContactInfo/ContactNavBar.tsx @@ -3,5 +3,5 @@ import TopNavBar from '../TopNavBar/TopNavBar'; import Left from '../../assets/img/left.svg'; export default function ContactNavBar() { - return
ContactNavBar
; + return ; } diff --git a/src/components/EditContact/EditNavBar.tsx b/src/components/EditContact/EditNavBar.tsx new file mode 100644 index 0000000..bd6546a --- /dev/null +++ b/src/components/EditContact/EditNavBar.tsx @@ -0,0 +1,6 @@ +import styled from 'styled-components'; +import TopNavBar from '../TopNavBar/TopNavBar'; + +export default function EditNavBar() { + return ; +} diff --git a/src/components/TopNavBar/TopNavBar.tsx b/src/components/TopNavBar/TopNavBar.tsx index 2a1a604..1cc693d 100644 --- a/src/components/TopNavBar/TopNavBar.tsx +++ b/src/components/TopNavBar/TopNavBar.tsx @@ -14,7 +14,6 @@ const LeftContainer = styled.div` gap: 0.3125rem; position: absolute; bottom: 0.13rem; - right: 20.1rem; `; const LeftImg = styled.img` @@ -23,6 +22,7 @@ const LeftImg = styled.img` `; const Text = styled.div` + white-space: nowrap; color: #1bd742; font-family: 'SF Pro Text'; font-size: 1.0625rem; diff --git a/src/pages/ChatsPage.tsx b/src/pages/ChatsPage.tsx index 60de08a..792c388 100644 --- a/src/pages/ChatsPage.tsx +++ b/src/pages/ChatsPage.tsx @@ -1,5 +1,6 @@ import React from 'react'; +import ChatsNavBar from '../components/Chats/ChatsNavBar'; export default function ChatsPage() { - return
ChatsPage
; + return ; } diff --git a/src/pages/ChattingPage.tsx b/src/pages/ChattingPage.tsx index a948554..9a03586 100644 --- a/src/pages/ChattingPage.tsx +++ b/src/pages/ChattingPage.tsx @@ -2,9 +2,9 @@ import { useState, useEffect } from 'react'; import { useSelector } from 'react-redux'; import { RootState } from '../store'; import userData from '../assets/data/userData.json'; -import TitleBar from '../components/Chat/TitleBar'; -import ChattingRoom from '../components/Chat/ChattingRoom'; -import ChatInput from '../components/Chat/ChatInput'; +import TitleBar from '../components/Chatting/TitleBar'; +import ChattingRoom from '../components/Chatting/ChattingRoom'; +import ChatInput from '../components/Chatting/ChatInput'; interface User { id: number; diff --git a/src/pages/ContactInfoPage.tsx b/src/pages/ContactInfoPage.tsx index 733e78d..2182070 100644 --- a/src/pages/ContactInfoPage.tsx +++ b/src/pages/ContactInfoPage.tsx @@ -1,5 +1,5 @@ -import React from 'react'; +import ContactNavBar from '../components/ContactInfo/ContactNavBar'; export default function ContactInfoPage() { - return
ContactInfoPage
; + return ; } diff --git a/src/pages/EditContactPage.tsx b/src/pages/EditContactPage.tsx index cc16f72..a7b6f01 100644 --- a/src/pages/EditContactPage.tsx +++ b/src/pages/EditContactPage.tsx @@ -1,5 +1,5 @@ -import React from 'react'; +import EditNavBar from '../components/EditContact/EditNavBar'; export default function EditContactPage() { - return
EditContactPage
; + return ; } From ff60771ec765ba523af0706da57aa89e5e19f74a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Wed, 10 Apr 2024 03:44:45 +0900 Subject: [PATCH 24/46] =?UTF-8?q?feat:=20=ED=95=98=EB=8B=A8=20=ED=83=AD=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 4 +- src/assets/img/calls-gray.svg | 3 + src/assets/img/camera-gray.svg | 3 + src/assets/img/chats-gray.svg | 3 + src/assets/img/chats-green.svg | 3 + src/assets/img/settings-gray.svg | 3 + src/assets/img/status-gray.svg | 3 + src/assets/img/status-green.svg | 3 + src/components/BottomTabBar/BottomTabBar.tsx | 105 ++++++++++++++++++ src/components/Chatting/TitleBar.tsx | 11 +- src/components/ContactInfo/ContactNavBar.tsx | 18 ++- src/components/Status/StatusNavBar.tsx | 5 + src/components/TopNavBar/TopNavBar.tsx | 23 +++- src/components/{ => iphone}/HomeIndicator.tsx | 2 +- src/components/{ => iphone}/StatusBar.tsx | 2 +- src/pages/ChatsPage.tsx | 8 +- src/pages/StatusPage.tsx | 10 +- 17 files changed, 194 insertions(+), 15 deletions(-) create mode 100644 src/assets/img/calls-gray.svg create mode 100644 src/assets/img/camera-gray.svg create mode 100644 src/assets/img/chats-gray.svg create mode 100644 src/assets/img/chats-green.svg create mode 100644 src/assets/img/settings-gray.svg create mode 100644 src/assets/img/status-gray.svg create mode 100644 src/assets/img/status-green.svg create mode 100644 src/components/BottomTabBar/BottomTabBar.tsx create mode 100644 src/components/Status/StatusNavBar.tsx rename src/components/{ => iphone}/HomeIndicator.tsx (87%) rename src/components/{ => iphone}/StatusBar.tsx (96%) diff --git a/src/App.tsx b/src/App.tsx index 1d177ab..a8e57fc 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,8 +6,8 @@ import ContactInfoPage from './pages/ContactInfoPage'; import ChatsPage from './pages/ChatsPage'; import StatusPage from './pages/StatusPage'; import EditContactPage from './pages/EditContactPage'; -import StatusBar from './components/StatusBar'; -import HomeIndicator from './components/HomeIndicator'; +import StatusBar from './components/iphone/StatusBar'; +import HomeIndicator from './components/iphone/HomeIndicator'; const Container = styled.div` background: #f6f6f6; diff --git a/src/assets/img/calls-gray.svg b/src/assets/img/calls-gray.svg new file mode 100644 index 0000000..b094369 --- /dev/null +++ b/src/assets/img/calls-gray.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/img/camera-gray.svg b/src/assets/img/camera-gray.svg new file mode 100644 index 0000000..d51b58e --- /dev/null +++ b/src/assets/img/camera-gray.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/img/chats-gray.svg b/src/assets/img/chats-gray.svg new file mode 100644 index 0000000..ca3c898 --- /dev/null +++ b/src/assets/img/chats-gray.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/img/chats-green.svg b/src/assets/img/chats-green.svg new file mode 100644 index 0000000..a0688c4 --- /dev/null +++ b/src/assets/img/chats-green.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/img/settings-gray.svg b/src/assets/img/settings-gray.svg new file mode 100644 index 0000000..c047ce4 --- /dev/null +++ b/src/assets/img/settings-gray.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/img/status-gray.svg b/src/assets/img/status-gray.svg new file mode 100644 index 0000000..ca58b51 --- /dev/null +++ b/src/assets/img/status-gray.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/img/status-green.svg b/src/assets/img/status-green.svg new file mode 100644 index 0000000..bf91df6 --- /dev/null +++ b/src/assets/img/status-green.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/BottomTabBar/BottomTabBar.tsx b/src/components/BottomTabBar/BottomTabBar.tsx new file mode 100644 index 0000000..45f6cd5 --- /dev/null +++ b/src/components/BottomTabBar/BottomTabBar.tsx @@ -0,0 +1,105 @@ +import { useState } from 'react'; +import styled from 'styled-components'; +import { useNavigate } from 'react-router-dom'; +import StatusGray from '../../assets/img/status-gray.svg'; +import StatusGreen from '../../assets/img/status-green.svg'; +import Calls from '../../assets/img/calls-gray.svg'; +import Camera from '../../assets/img/camera-gray.svg'; +import ChatsGray from '../../assets/img/chats-gray.svg'; +import ChatsGreen from '../../assets/img/chats-green.svg'; +import Settings from '../../assets/img/settings-gray.svg'; + +const TabBarContainer = styled.div` + width: 23.4375rem; + height: 3.06rem; + border-top: 0.03125rem #a4a39e; + position: relative; + background: #f6f6f6; + display: flex; + align-items: center; + justify-content: space-around; +`; + +const IconContainer = styled.div` + width: 4.6875rem; + height: 3.0625rem; + display: flex; + flex-direction: column; + align-items: center; + position: relative; +`; + +const IconImg = styled.img` + width: auto; + height: auto; + position: absolute; +`; + +const Text = styled.div` + color: ${(props) => (props.$active ? '#1BD742' : 'rgba(84, 84, 88, 0.65)')}; + text-align: center; + font-family: 'SF Pro Text'; + font-size: 0.625rem; + font-style: normal; + font-weight: 500; + line-height: normal; + letter-spacing: 0.00625rem; + + position: absolute; + top: 2.19rem; +`; + +interface TextProps { + $active?: boolean; +} + +export default function BotttomTabBar() { + const navigate = useNavigate(); + const [activeTab, setActiveTab] = useState(null); + + const handleTabClick = (tabName: string) => { + setActiveTab(tabName); // 클릭한 탭으로 activeTab 상태 업데이트 + if (tabName === 'Status') { + navigate('/status'); + } else if (tabName === 'Chats') { + navigate('/chats'); + } + }; + + return ( + + handleTabClick('Status')}> + + Status + + + + Calls + + + + Camera + + handleTabClick('Chats')}> + + Chats + + + + Settings + + + ); +} diff --git a/src/components/Chatting/TitleBar.tsx b/src/components/Chatting/TitleBar.tsx index e66840c..bb898b5 100644 --- a/src/components/Chatting/TitleBar.tsx +++ b/src/components/Chatting/TitleBar.tsx @@ -69,6 +69,10 @@ export default function TitleBar(props: TitleBarProps) { navigate('/contact-info'); }; + const goToChatsPage = () => { + navigate('/chats'); + }; + const handleChangeUser = () => { const currentIndex = userList.indexOf(nowUser); // 현재 nowUser의 인덱스 const nextIndex = currentIndex === 0 ? 1 : 0; @@ -77,7 +81,12 @@ export default function TitleBar(props: TitleBarProps) { }; return ( - + diff --git a/src/components/ContactInfo/ContactNavBar.tsx b/src/components/ContactInfo/ContactNavBar.tsx index eed2991..8e4d876 100644 --- a/src/components/ContactInfo/ContactNavBar.tsx +++ b/src/components/ContactInfo/ContactNavBar.tsx @@ -1,7 +1,21 @@ -import styled from 'styled-components'; +import { useNavigate } from 'react-router-dom'; import TopNavBar from '../TopNavBar/TopNavBar'; import Left from '../../assets/img/left.svg'; export default function ContactNavBar() { - return ; + const navigate = useNavigate(); + + // 이전 페이지로 돌아가는 함수 + const goBack = () => { + navigate(-1); + }; + + return ( + + ); } diff --git a/src/components/Status/StatusNavBar.tsx b/src/components/Status/StatusNavBar.tsx new file mode 100644 index 0000000..5416690 --- /dev/null +++ b/src/components/Status/StatusNavBar.tsx @@ -0,0 +1,5 @@ +import TopNavBar from '../TopNavBar/TopNavBar'; + +export default function StatusNavBar() { + return ; +} diff --git a/src/components/TopNavBar/TopNavBar.tsx b/src/components/TopNavBar/TopNavBar.tsx index 1cc693d..4b2dc5f 100644 --- a/src/components/TopNavBar/TopNavBar.tsx +++ b/src/components/TopNavBar/TopNavBar.tsx @@ -14,6 +14,7 @@ const LeftContainer = styled.div` gap: 0.3125rem; position: absolute; bottom: 0.13rem; + cursor: pointer; `; const LeftImg = styled.img` @@ -21,7 +22,7 @@ const LeftImg = styled.img` height: 1.25rem; `; -const Text = styled.div` +const Text = styled.div` white-space: nowrap; color: #1bd742; font-family: 'SF Pro Text'; @@ -30,6 +31,11 @@ const Text = styled.div` font-weight: 400; line-height: 1.375rem; letter-spacing: -0.0255rem; + ${(props) => + !props.$hasLeftImg && + ` + margin-left: 0.44rem; // leftImgSrc가 없을 때 적용될 스타일 + `} `; const RightContainer = styled.div` @@ -45,12 +51,18 @@ const IconImg = styled.img` height: 1.5rem; `; +// Text 컴포넌트의 prop 타입 정의 +interface TextProps { + $hasLeftImg: boolean; +} + interface TopNavBarProps { leftImgSrc?: string; leftText?: string; rightImgSrc?: string; rightText?: string; children?: React.ReactNode; + leftTextOnClick?: () => void; } // props 추가 (leftImgSrc, leftText, rightImgSrcs, rightText) @@ -60,17 +72,18 @@ export default function TopNavBar({ rightImgSrc, rightText, children, + leftTextOnClick, }: TopNavBarProps) { return ( - - {leftImgSrc && } - {leftText} + + {leftImgSrc && } + {leftText} {children} {/* 여기에 children을 렌더링 */} {rightImgSrc && } - {rightText && {rightText}} + {rightText && {rightText}} ); diff --git a/src/components/HomeIndicator.tsx b/src/components/iphone/HomeIndicator.tsx similarity index 87% rename from src/components/HomeIndicator.tsx rename to src/components/iphone/HomeIndicator.tsx index b5ad8d2..8815cfa 100644 --- a/src/components/HomeIndicator.tsx +++ b/src/components/iphone/HomeIndicator.tsx @@ -1,5 +1,5 @@ import styled from 'styled-components'; -import HomeBar from '../assets/img/home-indicator.svg'; +import HomeBar from '../../assets/img/home-indicator.svg'; const HomeBarContainer = styled.div` width: 23.4375rem; diff --git a/src/components/StatusBar.tsx b/src/components/iphone/StatusBar.tsx similarity index 96% rename from src/components/StatusBar.tsx rename to src/components/iphone/StatusBar.tsx index 4535fc5..8a8b58f 100644 --- a/src/components/StatusBar.tsx +++ b/src/components/iphone/StatusBar.tsx @@ -1,6 +1,6 @@ import { useState, useEffect } from 'react'; import styled from 'styled-components'; -import rightSide from '../assets/img/right-side.svg'; +import rightSide from '../../assets/img/right-side.svg'; const StatusBarContainer = styled.div` width: 23.4375rem; diff --git a/src/pages/ChatsPage.tsx b/src/pages/ChatsPage.tsx index 792c388..cb97a19 100644 --- a/src/pages/ChatsPage.tsx +++ b/src/pages/ChatsPage.tsx @@ -1,6 +1,12 @@ import React from 'react'; import ChatsNavBar from '../components/Chats/ChatsNavBar'; +import BottomTabBar from '../components/BottomTabBar/BottomTabBar'; export default function ChatsPage() { - return ; + return ( + <> + + + + ); } diff --git a/src/pages/StatusPage.tsx b/src/pages/StatusPage.tsx index 67d2c10..7f81be7 100644 --- a/src/pages/StatusPage.tsx +++ b/src/pages/StatusPage.tsx @@ -1,5 +1,11 @@ -import React from 'react'; +import StatusNavBar from '../components/Status/StatusNavBar'; +import BottomTabBar from '../components/BottomTabBar/BottomTabBar'; export default function StatusPage() { - return
StatusPage
; + return ( + <> + + + + ); } From ba98285e6902355dcf17311ca5c2e4d3d7c40cef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Wed, 10 Apr 2024 13:49:09 +0900 Subject: [PATCH 25/46] =?UTF-8?q?style:=20=ED=95=98=EB=8B=A8=20=ED=83=AD?= =?UTF-8?q?=20=EA=B2=BD=EB=A1=9C=20=EC=9D=B4=EB=8F=99=EC=97=90=20=EB=94=B0?= =?UTF-8?q?=EB=A5=B8=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/BottomTabBar/BottomTabBar.tsx | 31 +++++++---------- src/components/Chats/ChatsList.tsx | 11 ++++++ src/components/Chatting/ChatBubble.tsx | 35 +++++++++++--------- src/components/Chatting/ChattingRoom.tsx | 18 +++++----- src/components/Status/MyStatus.tsx | 11 ++++++ src/pages/ChatsPage.tsx | 3 +- src/pages/StatusPage.tsx | 2 ++ 7 files changed, 66 insertions(+), 45 deletions(-) create mode 100644 src/components/Chats/ChatsList.tsx create mode 100644 src/components/Status/MyStatus.tsx diff --git a/src/components/BottomTabBar/BottomTabBar.tsx b/src/components/BottomTabBar/BottomTabBar.tsx index 45f6cd5..127bee0 100644 --- a/src/components/BottomTabBar/BottomTabBar.tsx +++ b/src/components/BottomTabBar/BottomTabBar.tsx @@ -1,6 +1,6 @@ -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import styled from 'styled-components'; -import { useNavigate } from 'react-router-dom'; +import { useNavigate, useLocation } from 'react-router-dom'; import StatusGray from '../../assets/img/status-gray.svg'; import StatusGreen from '../../assets/img/status-green.svg'; import Calls from '../../assets/img/calls-gray.svg'; @@ -12,7 +12,7 @@ import Settings from '../../assets/img/settings-gray.svg'; const TabBarContainer = styled.div` width: 23.4375rem; height: 3.06rem; - border-top: 0.03125rem #a4a39e; + border-top: 0.03125rem solid #a4a39e; position: relative; background: #f6f6f6; display: flex; @@ -27,6 +27,7 @@ const IconContainer = styled.div` flex-direction: column; align-items: center; position: relative; + cursor: pointer; `; const IconImg = styled.img` @@ -55,26 +56,18 @@ interface TextProps { export default function BotttomTabBar() { const navigate = useNavigate(); - const [activeTab, setActiveTab] = useState(null); - - const handleTabClick = (tabName: string) => { - setActiveTab(tabName); // 클릭한 탭으로 activeTab 상태 업데이트 - if (tabName === 'Status') { - navigate('/status'); - } else if (tabName === 'Chats') { - navigate('/chats'); - } - }; + const location = useLocation(); + const isActive = (path: string) => location.pathname === path; return ( - handleTabClick('Status')}> + navigate('/status')}> - Status + Status @@ -84,13 +77,13 @@ export default function BotttomTabBar() { Camera - handleTabClick('Chats')}> + navigate('/chats')}> - Chats + Chats ; +} diff --git a/src/components/Chatting/ChatBubble.tsx b/src/components/Chatting/ChatBubble.tsx index 498993b..6d0570d 100644 --- a/src/components/Chatting/ChatBubble.tsx +++ b/src/components/Chatting/ChatBubble.tsx @@ -3,7 +3,7 @@ import CheckMark from '../../assets/img/checkmark.svg'; import BubbleGreen from '../../assets/img/bubble-green.svg'; import BubbleGray from '../../assets/img/bubble-gray.svg'; -const ChatBubbleContainer = styled.div<{ $isSentByMe: boolean }>` +const ChatBubbleContainer = styled.div` width: 18.6rem; margin-bottom: 0.31rem; margin-left: ${(props) => (props.$isSentByMe ? 'auto' : '0.44rem')}; @@ -11,7 +11,7 @@ const ChatBubbleContainer = styled.div<{ $isSentByMe: boolean }>` position: relative; `; -const BubbleImg = styled.img<{ $isSentByMe: boolean }>` +const BubbleImg = styled.img` width: 0.5rem; height: 0.9375rem; position: absolute; @@ -20,7 +20,7 @@ const BubbleImg = styled.img<{ $isSentByMe: boolean }>` bottom: 0; `; -const BubbleRectangle = styled.div<{ $isSentByMe: boolean }>` +const BubbleRectangle = styled.div` display: flex; width: 17.125rem; padding: 0.5rem; @@ -59,7 +59,7 @@ const MessageInfoContainer = styled.div` margin-top: 0.25rem; `; -const TimeText = styled.div<{ $isSentByMe: boolean }>` +const TimeText = styled.div` color: rgba(0, 0, 0, 0.15); font-family: 'SF Pro Display'; font-size: 0.75rem; @@ -69,7 +69,7 @@ const TimeText = styled.div<{ $isSentByMe: boolean }>` margin-right: ${(props) => (props.$isSentByMe ? '0.25rem' : '0')}; `; -const CheckImg = styled.img<{ $isRead: boolean; $isSentByMe: boolean }>` +const CheckImg = styled.img` width: 0.9375rem; height: 0.875rem; position: relative; @@ -79,33 +79,36 @@ const CheckImg = styled.img<{ $isRead: boolean; $isSentByMe: boolean }>` props.$isRead && props.$isSentByMe ? 'inline' : 'none'}; `; -interface ChatBubbleProps { - isSentByMe: boolean; +interface ChatBubbleBaseProps { + $isSentByMe?: boolean; + $isRead?: boolean; +} + +interface ChatBubbleProps extends ChatBubbleBaseProps { content: string; time: string; - isRead: boolean; } export default function ChatBubble(props: ChatBubbleProps) { - const { isSentByMe, content, time, isRead } = props; + const { $isSentByMe, content, time, $isRead } = props; return ( - + - + {content} - {time} + {time} diff --git a/src/components/Chatting/ChattingRoom.tsx b/src/components/Chatting/ChattingRoom.tsx index 2e8a187..4f3c398 100644 --- a/src/components/Chatting/ChattingRoom.tsx +++ b/src/components/Chatting/ChattingRoom.tsx @@ -19,11 +19,6 @@ const ChattingRoomContainer = styled.div` } `; -// DateContainer에 전달되는 props의 타입을 정의 -interface DateContainerProps { - isToday: boolean; -} - const DateContainer = styled.div` height: 0.875rem; display: inline-flex; @@ -31,7 +26,7 @@ const DateContainer = styled.div` align-items: center; border-radius: 0.375rem; background: #dbdfeb; - margin: ${(props) => (props.isToday ? '0.73rem' : '0.84rem')} 9.25rem 0.84rem + margin: ${(props) => (props.$isToday ? '0.73rem' : '0.84rem')} 9.25rem 0.84rem 9.56rem; `; @@ -62,6 +57,11 @@ function getTodayDateStringDMY() { return formatDateToDMY(new Date()); } +// DateContainer에 전달되는 props의 타입을 정의 +interface DateContainerProps { + $isToday: boolean; +} + export default function ChattingRoom() { const nowUser = useSelector((state: RootState) => state.user.nowUser); // 현재 사용자 상태 가져오기 const chatList = useSelector( @@ -94,15 +94,15 @@ export default function ChattingRoom() { return ( {showDateText && ( - + {isToday ? 'Today' : chatDateDMY} )} ); diff --git a/src/components/Status/MyStatus.tsx b/src/components/Status/MyStatus.tsx new file mode 100644 index 0000000..4553aa9 --- /dev/null +++ b/src/components/Status/MyStatus.tsx @@ -0,0 +1,11 @@ +import styled from 'styled-components'; + +const MyStatusContainer = styled.div` + width: 23.4375rem; + height: 40.06rem; + background: #efeff4; +`; + +export default function MyStatus() { + return ; +} diff --git a/src/pages/ChatsPage.tsx b/src/pages/ChatsPage.tsx index cb97a19..6a843fd 100644 --- a/src/pages/ChatsPage.tsx +++ b/src/pages/ChatsPage.tsx @@ -1,11 +1,12 @@ -import React from 'react'; import ChatsNavBar from '../components/Chats/ChatsNavBar'; import BottomTabBar from '../components/BottomTabBar/BottomTabBar'; +import ChatsList from '../components/Chats/ChatsList'; export default function ChatsPage() { return ( <> + ); diff --git a/src/pages/StatusPage.tsx b/src/pages/StatusPage.tsx index 7f81be7..f0ae3a2 100644 --- a/src/pages/StatusPage.tsx +++ b/src/pages/StatusPage.tsx @@ -1,10 +1,12 @@ import StatusNavBar from '../components/Status/StatusNavBar'; import BottomTabBar from '../components/BottomTabBar/BottomTabBar'; +import MyStatus from '../components/Status/MyStatus'; export default function StatusPage() { return ( <> + ); From 0d6c8600d25dc8b287faa02abb00e1beedab18c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Wed, 10 Apr 2024 15:11:51 +0900 Subject: [PATCH 26/46] =?UTF-8?q?feat:=20status=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EC=99=84=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/img/my-profile.svg | 11 +++ src/assets/img/status-camera.svg | 4 ++ src/assets/img/status-edit.svg | 4 ++ src/components/Status/MyStatus.tsx | 105 ++++++++++++++++++++++++++++- 4 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 src/assets/img/my-profile.svg create mode 100644 src/assets/img/status-camera.svg create mode 100644 src/assets/img/status-edit.svg diff --git a/src/assets/img/my-profile.svg b/src/assets/img/my-profile.svg new file mode 100644 index 0000000..25ac3b0 --- /dev/null +++ b/src/assets/img/my-profile.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/assets/img/status-camera.svg b/src/assets/img/status-camera.svg new file mode 100644 index 0000000..b046af5 --- /dev/null +++ b/src/assets/img/status-camera.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/img/status-edit.svg b/src/assets/img/status-edit.svg new file mode 100644 index 0000000..c32c615 --- /dev/null +++ b/src/assets/img/status-edit.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/components/Status/MyStatus.tsx b/src/components/Status/MyStatus.tsx index 4553aa9..5764258 100644 --- a/src/components/Status/MyStatus.tsx +++ b/src/components/Status/MyStatus.tsx @@ -1,11 +1,112 @@ import styled from 'styled-components'; +import { useNavigate } from 'react-router-dom'; +import MyProfile from '../../assets/img/my-profile.svg'; +import camera from '../../assets/img/status-camera.svg'; +import edit from '../../assets/img/status-edit.svg'; -const MyStatusContainer = styled.div` +const Container = styled.div` width: 23.4375rem; height: 40.06rem; background: #efeff4; `; +const MyStatusContainer = styled.div` + width: 23.4375rem; + height: 4.75rem; + background: #fff; + box-shadow: 0px 0.33px 0px 0px rgba(60, 60, 67, 0.29), + 0px -0.33px 0px 0px rgba(60, 60, 67, 0.29); + position: relative; + top: 2.19rem; +`; + +const ProfileImg = styled.img` + width: 3.625rem; + height: 3.625rem; + position: absolute; + left: 0.81rem; + top: 0.56rem; +`; + +const MyStatusText = styled.div` + color: #000; + font-family: 'SF Pro Text'; + font-size: 1rem; + font-style: normal; + font-weight: 600; + line-height: 1.3125rem; /* 131.25% */ + letter-spacing: -0.02063rem; + + position: absolute; + top: 1.06rem; + left: 5rem; +`; + +const SubText = styled.div` + color: #8e8e93; + font-family: 'SF Pro Text'; + font-size: 0.875rem; + font-style: normal; + font-weight: 400; + line-height: 1rem; /* 114.286% */ + letter-spacing: -0.0125rem; + + position: absolute; +`; + +const IconContainer = styled.div` + display: flex; + width: 5.5rem; + height: 2.25rem; + justify-content: center; + align-items: flex-start; + gap: 1rem; + position: absolute; + top: 1.25rem; + left: 16.94rem; +`; + +const IconImg = styled.img` + width: auto; + height: auto; +`; + +const TipContainer = styled.div` + width: 23.4375rem; + height: 2.6875rem; + background: #fff; + box-shadow: 0px 0.33px 0px 0px rgba(60, 60, 67, 0.29), + 0px -0.33px 0px 0px rgba(60, 60, 67, 0.29); + position: relative; + top: 4.75rem; +`; + export default function MyStatus() { - return ; + const navigate = useNavigate(); + + return ( + + + + My Status + + Give me magarita + + + + navigate('/edit-contact')} + src={edit} + alt="수정 버튼 이미지" + /> + + + + + No recent updates to show right now. + + + + ); } From 53f04b6943724ea4c32a15cc1d997748b74d660c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Wed, 10 Apr 2024 17:48:31 +0900 Subject: [PATCH 27/46] =?UTF-8?q?feat:=20edit-contact=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EC=99=84=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 27 +++++-- src/assets/img/edit-arrow.svg | 3 + src/components/EditContact/EditContact.tsx | 86 ++++++++++++++++++++++ src/components/EditContact/EditNavBar.tsx | 18 ++++- src/components/TopNavBar/TopNavBar.tsx | 13 ++-- src/components/iphone/StatusBar.tsx | 1 - src/pages/EditContactPage.tsx | 8 +- src/style/GlobalStyles.tsx | 1 + 8 files changed, 142 insertions(+), 15 deletions(-) create mode 100644 src/assets/img/edit-arrow.svg create mode 100644 src/components/EditContact/EditContact.tsx diff --git a/src/App.tsx b/src/App.tsx index a8e57fc..7270b0a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,5 @@ -import { BrowserRouter, Routes, Route } from 'react-router-dom'; +import { useEffect, useState, ReactNode } from 'react'; +import { BrowserRouter, Routes, Route, useLocation } from 'react-router-dom'; import styled from 'styled-components'; import GlobalStyles from './style/GlobalStyles'; import ChattingPage from './pages/ChattingPage'; @@ -9,8 +10,8 @@ import EditContactPage from './pages/EditContactPage'; import StatusBar from './components/iphone/StatusBar'; import HomeIndicator from './components/iphone/HomeIndicator'; -const Container = styled.div` - background: #f6f6f6; +const Container = styled.div<{ $bgColor: string }>` + background: ${({ $bgColor }) => $bgColor}; border-radius: 1.25rem; `; @@ -21,11 +22,27 @@ const InnerContainer = styled.div` position: relative; `; +// useLocation을 사용하기 위한 Wrapper 컴포넌트 +function AppContainer({ children }: { children: ReactNode }) { + const location = useLocation(); + const [bgColor, setBgColor] = useState('#f6f6f6'); + + useEffect(() => { + if (location.pathname === '/edit-contact') { + setBgColor('#fff'); // edit-contact 경로일 때만 배경색을 #fff로 설정 + } else { + setBgColor('#f6f6f6'); + } + }, [location]); + + return {children}; +} + function App() { return ( - + @@ -37,7 +54,7 @@ function App() { - + ); } diff --git a/src/assets/img/edit-arrow.svg b/src/assets/img/edit-arrow.svg new file mode 100644 index 0000000..d74e65f --- /dev/null +++ b/src/assets/img/edit-arrow.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/EditContact/EditContact.tsx b/src/components/EditContact/EditContact.tsx new file mode 100644 index 0000000..29b17f4 --- /dev/null +++ b/src/components/EditContact/EditContact.tsx @@ -0,0 +1,86 @@ +import styled from 'styled-components'; +import RightArrow from '../../assets/img/edit-arrow.svg'; + +const Container = styled.div` + width: 22.4375rem; + height: 40.93rem; + padding: 2.19rem 0rem 0rem 1rem; +`; + +const TextContainer = styled.div` + width: 22.4375rem; + min-height: 3.125rem; + display: flex; + justify-content: space-between; +`; + +const SubTextContainer = styled.div` + display: flex; + flex-direction: column; + justify-content: center; +`; + +const SubTextBox = styled.div` + width: 15.4275rem; + height: 1.3125rem; + padding: 0.88rem 1.01rem 0.94rem 0rem; + border-bottom: 0.0625rem solid rgba(60, 60, 67, 0.29); + display: flex; + align-items: center; +`; + +const EditText = styled.div` + font-family: 'SF Pro Text'; + font-size: 1rem; + font-style: normal; + line-height: 1.3125rem; + letter-spacing: -0.02063rem; + margin-top: 0.94rem; +`; + +const IconImg = styled.img` + width: 0.4985rem; + height: 0.82025rem; +`; + +export default function EditContact() { + return ( + + + Name + + Timothée + Chalamet + + + + Phone + United Kingdom + + + Link + Instagram + + + + mobile + + + +44 1234 567890 + + + + + more fields + + + + + Delete Contact + + + + ); +} diff --git a/src/components/EditContact/EditNavBar.tsx b/src/components/EditContact/EditNavBar.tsx index bd6546a..810875b 100644 --- a/src/components/EditContact/EditNavBar.tsx +++ b/src/components/EditContact/EditNavBar.tsx @@ -1,6 +1,20 @@ -import styled from 'styled-components'; +import { useNavigate } from 'react-router-dom'; import TopNavBar from '../TopNavBar/TopNavBar'; export default function EditNavBar() { - return ; + const navigate = useNavigate(); + + // 이전 페이지로 돌아가는 함수 + const goBack = () => { + navigate(-1); + }; + + return ( + + ); } diff --git a/src/components/TopNavBar/TopNavBar.tsx b/src/components/TopNavBar/TopNavBar.tsx index 4b2dc5f..ebe53e9 100644 --- a/src/components/TopNavBar/TopNavBar.tsx +++ b/src/components/TopNavBar/TopNavBar.tsx @@ -1,9 +1,10 @@ import styled from 'styled-components'; -const NavBarContainer = styled.div` +const NavBarContainer = styled.div<{ $noBorderBottom?: boolean }>` width: 23.4375rem; height: 2.75rem; - border-bottom: 0.03125rem solid #a4a39e; + border-bottom: ${(props) => + props.$noBorderBottom ? 'none' : '0.03125rem solid #a4a39e'}; position: relative; `; @@ -51,7 +52,6 @@ const IconImg = styled.img` height: 1.5rem; `; -// Text 컴포넌트의 prop 타입 정의 interface TextProps { $hasLeftImg: boolean; } @@ -62,10 +62,10 @@ interface TopNavBarProps { rightImgSrc?: string; rightText?: string; children?: React.ReactNode; - leftTextOnClick?: () => void; + leftTextOnClick?: () => void; // 클릭을 통해 이전 페이지로 돌아가기 위함 + $noBorderBottom?: boolean; // edit-contact page에서는 테두리 없애기 위함 } -// props 추가 (leftImgSrc, leftText, rightImgSrcs, rightText) export default function TopNavBar({ leftImgSrc, leftText, @@ -73,9 +73,10 @@ export default function TopNavBar({ rightText, children, leftTextOnClick, + $noBorderBottom, }: TopNavBarProps) { return ( - + {leftImgSrc && } {leftText} diff --git a/src/components/iphone/StatusBar.tsx b/src/components/iphone/StatusBar.tsx index 8a8b58f..5eaac01 100644 --- a/src/components/iphone/StatusBar.tsx +++ b/src/components/iphone/StatusBar.tsx @@ -5,7 +5,6 @@ import rightSide from '../../assets/img/right-side.svg'; const StatusBarContainer = styled.div` width: 23.4375rem; height: 2.75rem; - flex-shrink: 0; position: relative; `; diff --git a/src/pages/EditContactPage.tsx b/src/pages/EditContactPage.tsx index a7b6f01..6cad994 100644 --- a/src/pages/EditContactPage.tsx +++ b/src/pages/EditContactPage.tsx @@ -1,5 +1,11 @@ import EditNavBar from '../components/EditContact/EditNavBar'; +import EditContact from '../components/EditContact/EditContact'; export default function EditContactPage() { - return ; + return ( + <> + + + + ); } diff --git a/src/style/GlobalStyles.tsx b/src/style/GlobalStyles.tsx index c6133c6..b13b908 100644 --- a/src/style/GlobalStyles.tsx +++ b/src/style/GlobalStyles.tsx @@ -8,6 +8,7 @@ const GlobalStyle = createGlobalStyle` display: flex; justify-content: center; align-items: center; + background: #A7A7A7; } body{ From 24794b8eea37c0c574de8ce465069293dc6dc5ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Thu, 11 Apr 2024 02:49:05 +0900 Subject: [PATCH 28/46] =?UTF-8?q?chore:=20edit-contact=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=ED=99=94=EC=82=B4=ED=91=9C=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=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/components/Chats/ChatsNavBar.tsx | 2 +- src/components/Chatting/TitleBar.tsx | 8 ++- src/components/ContactInfo/ContactNavBar.tsx | 1 + src/components/EditContact/EditContact.tsx | 67 +++++++++++++++----- src/components/EditContact/EditNavBar.tsx | 2 + src/components/Status/StatusNavBar.tsx | 2 +- src/components/TopNavBar/TopNavBar.tsx | 29 ++++++++- 7 files changed, 89 insertions(+), 22 deletions(-) diff --git a/src/components/Chats/ChatsNavBar.tsx b/src/components/Chats/ChatsNavBar.tsx index 6d5e241..23d8826 100644 --- a/src/components/Chats/ChatsNavBar.tsx +++ b/src/components/Chats/ChatsNavBar.tsx @@ -3,5 +3,5 @@ import TopNavBar from '../TopNavBar/TopNavBar'; import Edit from '../../assets/img/edit.svg'; export default function ContactNavBar() { - return ; + return ; } diff --git a/src/components/Chatting/TitleBar.tsx b/src/components/Chatting/TitleBar.tsx index bb898b5..3ae2a96 100644 --- a/src/components/Chatting/TitleBar.tsx +++ b/src/components/Chatting/TitleBar.tsx @@ -87,8 +87,12 @@ export default function TitleBar(props: TitleBarProps) { rightImgSrc={Call} leftTextOnClick={goToChatsPage} > - - + + {name} {isActive ? ( diff --git a/src/components/ContactInfo/ContactNavBar.tsx b/src/components/ContactInfo/ContactNavBar.tsx index 8e4d876..abd60a9 100644 --- a/src/components/ContactInfo/ContactNavBar.tsx +++ b/src/components/ContactInfo/ContactNavBar.tsx @@ -16,6 +16,7 @@ export default function ContactNavBar() { leftText="Dain Park" rightText="Edit" leftTextOnClick={goBack} + title="Contact Info" /> ); } diff --git a/src/components/EditContact/EditContact.tsx b/src/components/EditContact/EditContact.tsx index 29b17f4..e2862d8 100644 --- a/src/components/EditContact/EditContact.tsx +++ b/src/components/EditContact/EditContact.tsx @@ -27,6 +27,7 @@ const SubTextBox = styled.div` border-bottom: 0.0625rem solid rgba(60, 60, 67, 0.29); display: flex; align-items: center; + justify-content: space-between; `; const EditText = styled.div` @@ -35,10 +36,18 @@ const EditText = styled.div` font-style: normal; line-height: 1.3125rem; letter-spacing: -0.02063rem; - margin-top: 0.94rem; `; -const IconImg = styled.img` +const MobileContainer = styled.div` + width: 4.92rem; + height: 1.305rem; + gap: 0.36rem; + display: flex; + align-items: center; + padding: 0.88rem 1.08rem 0.94rem 0rem; +`; + +const RightArrowImg = styled.img` width: 0.4985rem; height: 0.82025rem; `; @@ -47,37 +56,65 @@ export default function EditContact() { return ( - Name + + Name + - Timothée - Chalamet + + Timothée + + + Chalamet + - Phone - United Kingdom + + Phone + + + United Kingdom + + - Link - Instagram + + Link + + + Instagram + + - - mobile - + + + mobile + + + - +44 1234 567890 + +44 1234 567890 - + more fields - + Delete Contact diff --git a/src/components/EditContact/EditNavBar.tsx b/src/components/EditContact/EditNavBar.tsx index 810875b..dd023d1 100644 --- a/src/components/EditContact/EditNavBar.tsx +++ b/src/components/EditContact/EditNavBar.tsx @@ -15,6 +15,8 @@ export default function EditNavBar() { rightText="Save" leftTextOnClick={goBack} $noBorderBottom={true} + $isEditPage={true} + title="Edit Contact" /> ); } diff --git a/src/components/Status/StatusNavBar.tsx b/src/components/Status/StatusNavBar.tsx index 5416690..620fe33 100644 --- a/src/components/Status/StatusNavBar.tsx +++ b/src/components/Status/StatusNavBar.tsx @@ -1,5 +1,5 @@ import TopNavBar from '../TopNavBar/TopNavBar'; export default function StatusNavBar() { - return ; + return ; } diff --git a/src/components/TopNavBar/TopNavBar.tsx b/src/components/TopNavBar/TopNavBar.tsx index ebe53e9..7f2ee00 100644 --- a/src/components/TopNavBar/TopNavBar.tsx +++ b/src/components/TopNavBar/TopNavBar.tsx @@ -2,10 +2,11 @@ import styled from 'styled-components'; const NavBarContainer = styled.div<{ $noBorderBottom?: boolean }>` width: 23.4375rem; - height: 2.75rem; + height: 2.12rem; border-bottom: ${(props) => props.$noBorderBottom ? 'none' : '0.03125rem solid #a4a39e'}; position: relative; + padding-top: 0.63rem; `; const LeftContainer = styled.div` @@ -25,7 +26,7 @@ const LeftImg = styled.img` const Text = styled.div` white-space: nowrap; - color: #1bd742; + color: ${(props) => (props.$isEditPage ? '#D1D1D6' : '#1bd742')}; font-family: 'SF Pro Text'; font-size: 1.0625rem; font-style: normal; @@ -39,6 +40,17 @@ const Text = styled.div` `} `; +const TitleText = styled.div` + color: #000; + text-align: center; + font-family: 'SF Pro Text'; + font-size: 1.0625rem; + font-style: normal; + font-weight: 600; + line-height: 1.375rem; + letter-spacing: -0.025rem; +`; + const RightContainer = styled.div` display: inline-flex; align-items: flex-start; @@ -54,6 +66,7 @@ const IconImg = styled.img` interface TextProps { $hasLeftImg: boolean; + $isEditPage?: boolean; } interface TopNavBarProps { @@ -62,8 +75,10 @@ interface TopNavBarProps { rightImgSrc?: string; rightText?: string; children?: React.ReactNode; + title?: string; leftTextOnClick?: () => void; // 클릭을 통해 이전 페이지로 돌아가기 위함 $noBorderBottom?: boolean; // edit-contact page에서는 테두리 없애기 위함 + $isEditPage?: boolean; // edit-contact page에서 오른쪽 텍스트가 회색 } export default function TopNavBar({ @@ -72,8 +87,10 @@ export default function TopNavBar({ rightImgSrc, rightText, children, + title, leftTextOnClick, $noBorderBottom, + $isEditPage = false, // 기본값 false 설정 }: TopNavBarProps) { return ( @@ -82,9 +99,15 @@ export default function TopNavBar({ {leftText} {children} {/* 여기에 children을 렌더링 */} + {title && {title}}{' '} + {/* 여기에 TitleText를 렌더링 */} {rightImgSrc && } - {rightText && {rightText}} + {rightText && ( + + {rightText} + + )} ); From 5be222d0765a3810275c8377b5196264aa2a6995 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Fri, 12 Apr 2024 04:41:29 +0900 Subject: [PATCH 29/46] =?UTF-8?q?feat:=20contact-info=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EC=99=84=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/img/contact-call.svg | 4 + src/assets/img/contact-github.svg | 3 + src/assets/img/contact-instagram.svg | 5 + src/assets/img/contact-message.svg | 4 + src/assets/img/media.svg | 4 + src/assets/img/mute.svg | 4 + src/assets/img/search.svg | 4 + src/assets/img/star.svg | 4 + src/assets/img/tone.svg | 4 + src/components/Chats/ChatsList.tsx | 4 +- src/components/Chatting/ChattingRoom.tsx | 6 +- src/components/Chatting/TitleBar.tsx | 19 +- src/components/ContactInfo/ContactInfo.tsx | 233 +++++++++++++++++++++ src/components/EditContact/EditContact.tsx | 6 +- src/components/Status/MyStatus.tsx | 6 +- src/pages/ChattingPage.tsx | 8 +- src/pages/ContactInfoPage.tsx | 28 ++- src/types/type.ts | 12 ++ 18 files changed, 323 insertions(+), 35 deletions(-) create mode 100644 src/assets/img/contact-call.svg create mode 100644 src/assets/img/contact-github.svg create mode 100644 src/assets/img/contact-instagram.svg create mode 100644 src/assets/img/contact-message.svg create mode 100644 src/assets/img/media.svg create mode 100644 src/assets/img/mute.svg create mode 100644 src/assets/img/search.svg create mode 100644 src/assets/img/star.svg create mode 100644 src/assets/img/tone.svg create mode 100644 src/components/ContactInfo/ContactInfo.tsx create mode 100644 src/types/type.ts diff --git a/src/assets/img/contact-call.svg b/src/assets/img/contact-call.svg new file mode 100644 index 0000000..6d5bbfe --- /dev/null +++ b/src/assets/img/contact-call.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/img/contact-github.svg b/src/assets/img/contact-github.svg new file mode 100644 index 0000000..7d3c750 --- /dev/null +++ b/src/assets/img/contact-github.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/img/contact-instagram.svg b/src/assets/img/contact-instagram.svg new file mode 100644 index 0000000..3810213 --- /dev/null +++ b/src/assets/img/contact-instagram.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/img/contact-message.svg b/src/assets/img/contact-message.svg new file mode 100644 index 0000000..f28623a --- /dev/null +++ b/src/assets/img/contact-message.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/img/media.svg b/src/assets/img/media.svg new file mode 100644 index 0000000..225ffe8 --- /dev/null +++ b/src/assets/img/media.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/img/mute.svg b/src/assets/img/mute.svg new file mode 100644 index 0000000..34eaaf8 --- /dev/null +++ b/src/assets/img/mute.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/img/search.svg b/src/assets/img/search.svg new file mode 100644 index 0000000..58dafe3 --- /dev/null +++ b/src/assets/img/search.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/img/star.svg b/src/assets/img/star.svg new file mode 100644 index 0000000..6f64956 --- /dev/null +++ b/src/assets/img/star.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/img/tone.svg b/src/assets/img/tone.svg new file mode 100644 index 0000000..c187d78 --- /dev/null +++ b/src/assets/img/tone.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/components/Chats/ChatsList.tsx b/src/components/Chats/ChatsList.tsx index 4d43079..7039ec2 100644 --- a/src/components/Chats/ChatsList.tsx +++ b/src/components/Chats/ChatsList.tsx @@ -1,11 +1,11 @@ import styled from 'styled-components'; -const ChatsListContainer = styled.div` +const ChatsListPageContainer = styled.div` width: 23.4375rem; height: 40.06rem; background: #fff; `; export default function ChatsList() { - return ; + return ; } diff --git a/src/components/Chatting/ChattingRoom.tsx b/src/components/Chatting/ChattingRoom.tsx index 4f3c398..73ad996 100644 --- a/src/components/Chatting/ChattingRoom.tsx +++ b/src/components/Chatting/ChattingRoom.tsx @@ -5,7 +5,7 @@ import { RootState } from '../../store'; import ChatBubble from './ChatBubble'; import FormatTime from './FormatTime'; -const ChattingRoomContainer = styled.div` +const ChattingPageContainer = styled.div` width: 23.4375rem; height: 40.37rem; flex-direction: column; @@ -78,7 +78,7 @@ export default function ChattingRoom() { let lastDate = ''; return ( - + {chatList.map((chat) => { const chatDateDMY = formatDateToDMY(chat.time); // 현재 채팅의 날짜를 "dd/mm/yy" 형식으로 변환 let showDateText = false; @@ -107,6 +107,6 @@ export default function ChattingRoom() { ); })} - + ); } diff --git a/src/components/Chatting/TitleBar.tsx b/src/components/Chatting/TitleBar.tsx index 3ae2a96..22f9e00 100644 --- a/src/components/Chatting/TitleBar.tsx +++ b/src/components/Chatting/TitleBar.tsx @@ -3,6 +3,7 @@ import { useNavigate } from 'react-router-dom'; import { useSelector, useDispatch } from 'react-redux'; import { changeUser } from '../../features/userSlice'; import { RootState } from '../../store'; +import { TitleBarProps } from '../../types/type'; import TopNavBar from '../TopNavBar/TopNavBar'; import Left from '../../assets/img/left.svg'; import Call from '../../assets/img/call.svg'; @@ -51,12 +52,6 @@ const OnlineText = styled.div` letter-spacing: -0.0015rem; `; -interface TitleBarProps { - name: string; - profileImg: string; - isActive: boolean; -} - export default function TitleBar(props: TitleBarProps) { const { name, profileImg, isActive } = props; @@ -65,14 +60,6 @@ export default function TitleBar(props: TitleBarProps) { const dispatch = useDispatch(); const navigate = useNavigate(); - const goToContactInfoPage = () => { - navigate('/contact-info'); - }; - - const goToChatsPage = () => { - navigate('/chats'); - }; - const handleChangeUser = () => { const currentIndex = userList.indexOf(nowUser); // 현재 nowUser의 인덱스 const nextIndex = currentIndex === 0 ? 1 : 0; @@ -85,13 +72,13 @@ export default function TitleBar(props: TitleBarProps) { leftImgSrc={Left} leftText="12" rightImgSrc={Call} - leftTextOnClick={goToChatsPage} + leftTextOnClick={() => navigate('/chats')} > navigate('/contact-info')} /> {name} diff --git a/src/components/ContactInfo/ContactInfo.tsx b/src/components/ContactInfo/ContactInfo.tsx new file mode 100644 index 0000000..dda3ff6 --- /dev/null +++ b/src/components/ContactInfo/ContactInfo.tsx @@ -0,0 +1,233 @@ +import styled from 'styled-components'; +import { TitleBarProps } from '../../types/type'; +import Instagram from '../../assets/img/contact-instagram.svg'; +import Github from '../../assets/img/contact-github.svg'; +import Message from '../../assets/img/contact-message.svg'; +import Call from '../../assets/img/contact-call.svg'; +import Media from '../../assets/img/media.svg'; +import Star from '../../assets/img/star.svg'; +import Search from '../../assets/img/search.svg'; +import Mute from '../../assets/img/mute.svg'; +import Tone from '../../assets/img/tone.svg'; +import RightArrow from '../../assets/img/edit-arrow.svg'; + +const ContactPageContainer = styled.div` + width: 23.4375rem; + height: 43.12rem; + background: #efeff4; + position: relative; +`; + +const ProfileImg = styled.img` + width: 23.4375rem; + height: 23.4375rem; +`; + +const InfoContainer = styled.div` + width: 22.4975rem; + height: 7.875rem; + box-shadow: 0px 0.33px 0px 0px rgba(60, 60, 67, 0.29); + padding-left: 0.94rem; + background: #fff; +`; + +const InfoBox = styled.div` + width: 21.5575rem; + padding: 0.69rem 0.94rem 0rem 0rem; +`; + +const NameText = styled.div` + color: #000; + font-family: 'SF Pro Text'; + font-size: 1.125rem; + font-style: normal; + font-weight: 500; + line-height: 1.4375rem; + letter-spacing: -0.0265rem; +`; + +const SubText = styled.div` + color: #000; + font-family: 'SF Pro Text'; + font-size: 0.875rem; + font-style: normal; + font-weight: 400; + line-height: 1rem; + letter-spacing: -0.0125rem; +`; + +const InfoText = styled.div` + color: #8e8e93; + font-family: 'SF Pro Text'; + font-size: 0.75rem; + font-style: normal; + font-weight: 400; + line-height: normal; + letter-spacing: -0.0015rem; +`; + +const Seperator = styled.div` + height: 0.0625rem; + background: rgba(60, 60, 67, 0.29); + margin-left: auto; +`; + +const IconContainer = styled.div` + display: flex; + align-items: flex-start; + gap: 0.625rem; + position: absolute; + right: 0.9375rem; + top: 24.38rem; +`; + +const ContactIconImg = styled.img` + display: flex; + width: 2.25rem; + height: 2.25rem; + justify-content: center; + align-items: center; +`; + +const ExtraContainer = styled.div` + width: 23.4375rem; + background: #fff; + box-shadow: 0px 0.33px 0px 0px rgba(60, 60, 67, 0.29), + 0px -0.33px 0px 0px rgba(60, 60, 67, 0.29); + margin-top: 1.22rem; +`; + +const DetailContainer = styled.div` + width: 21.4375rem; + height: 1.8075rem; + padding: 0.5rem 1.06rem 0.63rem 0.94rem; + display: flex; + align-items: center; +`; + +const ExtraIconImg = styled.img` + width: 1.8125rem; + height: 1.8125rem; + margin-right: 0.94rem; +`; + +const ExtraText = styled.div` + color: #000; + font-family: 'SF Pro Text'; + font-size: 1rem; + font-style: normal; + font-weight: 400; + line-height: 1.375rem; + letter-spacing: -0.02063rem; +`; + +const RightArrowImg = styled.img` + width: 0.4375rem; + height: 0.75rem; + margin-left: 0.75rem; +`; + +export default function ContactInfo(props: TitleBarProps) { + const { name, profileImg } = props; + + return ( + + + + + {name} + +44 7496 0000000 + + + + + + + + + + I’m in Seoul Now! + Mar 20, 2024 + + + + + + + Media, Links, and Docs + + 12 + + + + + + + Starred Messages + + None + + + + + + + Chat Search + + + + + + + + Mute + + No + + + + + + + Custom Tone + + Default (Note) + + + + + + + ); +} diff --git a/src/components/EditContact/EditContact.tsx b/src/components/EditContact/EditContact.tsx index e2862d8..ddb6145 100644 --- a/src/components/EditContact/EditContact.tsx +++ b/src/components/EditContact/EditContact.tsx @@ -1,7 +1,7 @@ import styled from 'styled-components'; import RightArrow from '../../assets/img/edit-arrow.svg'; -const Container = styled.div` +const EditPageContainer = styled.div` width: 22.4375rem; height: 40.93rem; padding: 2.19rem 0rem 0rem 1rem; @@ -54,7 +54,7 @@ const RightArrowImg = styled.img` export default function EditContact() { return ( - + Name @@ -118,6 +118,6 @@ export default function EditContact() { Delete Contact - + ); } diff --git a/src/components/Status/MyStatus.tsx b/src/components/Status/MyStatus.tsx index 5764258..6b1bcc4 100644 --- a/src/components/Status/MyStatus.tsx +++ b/src/components/Status/MyStatus.tsx @@ -4,7 +4,7 @@ import MyProfile from '../../assets/img/my-profile.svg'; import camera from '../../assets/img/status-camera.svg'; import edit from '../../assets/img/status-edit.svg'; -const Container = styled.div` +const StatusPageContainer = styled.div` width: 23.4375rem; height: 40.06rem; background: #efeff4; @@ -85,7 +85,7 @@ export default function MyStatus() { const navigate = useNavigate(); return ( - + My Status @@ -107,6 +107,6 @@ export default function MyStatus() { No recent updates to show right now. - + ); } diff --git a/src/pages/ChattingPage.tsx b/src/pages/ChattingPage.tsx index 9a03586..877e187 100644 --- a/src/pages/ChattingPage.tsx +++ b/src/pages/ChattingPage.tsx @@ -1,18 +1,12 @@ import { useState, useEffect } from 'react'; import { useSelector } from 'react-redux'; import { RootState } from '../store'; +import { User } from '../types/type'; import userData from '../assets/data/userData.json'; import TitleBar from '../components/Chatting/TitleBar'; import ChattingRoom from '../components/Chatting/ChattingRoom'; import ChatInput from '../components/Chatting/ChatInput'; -interface User { - id: number; - name: string; - profileImg: string; - isActive: boolean; -} - export default function ChattingPage() { const nowUser = useSelector((state: RootState) => state.user.nowUser); const userList = useSelector((state: RootState) => state.user.userList); diff --git a/src/pages/ContactInfoPage.tsx b/src/pages/ContactInfoPage.tsx index 2182070..8163b8c 100644 --- a/src/pages/ContactInfoPage.tsx +++ b/src/pages/ContactInfoPage.tsx @@ -1,5 +1,31 @@ +import { useState, useEffect } from 'react'; +import { useSelector } from 'react-redux'; +import { RootState } from '../store'; +import { User } from '../types/type'; +import userData from '../assets/data/userData.json'; import ContactNavBar from '../components/ContactInfo/ContactNavBar'; +import ContactInfo from '../components/ContactInfo/ContactInfo'; export default function ContactInfoPage() { - return ; + const nowUser = useSelector((state: RootState) => state.user.nowUser); + const userList = useSelector((state: RootState) => state.user.userList); + const [partner, setPartner] = useState(null); + + useEffect(() => { + const nextUser = userData.users.find((user) => user.id !== nowUser) ?? null; + setPartner(nextUser); + }, [nowUser, userList]); // nowUser 또는 userList가 변경될 때마다 effect를 실행 + + return ( + <> + {partner !== null ? ( + <> + + + + ) : ( +

파트너 정보가 없습니다.

+ )} + + ); } diff --git a/src/types/type.ts b/src/types/type.ts new file mode 100644 index 0000000..399e29a --- /dev/null +++ b/src/types/type.ts @@ -0,0 +1,12 @@ +export interface TitleBarProps { + name: string; + profileImg: string; + isActive?: boolean; +} + +export interface User { + id: number; + name: string; + profileImg: string; + isActive: boolean; +} From a0436f6773ad65c04085f36e0f2943357e530d60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Sat, 13 Apr 2024 03:51:46 +0900 Subject: [PATCH 30/46] =?UTF-8?q?chore:=20=EB=B3=84=EB=8F=84=EC=9D=98=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=97=86=EC=9D=B4=20Top?= =?UTF-8?q?NavBar=20=EC=82=AC=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 12 ++++- src/components/Chats/ChatsNavBar.tsx | 7 --- src/components/ContactInfo/ContactInfo.tsx | 56 ++++++-------------- src/components/ContactInfo/ContactNavBar.tsx | 22 -------- src/components/EditContact/EditNavBar.tsx | 22 -------- src/components/Status/StatusNavBar.tsx | 5 -- src/components/iphone/HomeIndicator.tsx | 4 +- src/pages/ChatsPage.tsx | 5 +- src/pages/ContactInfoPage.tsx | 18 ++++++- src/pages/EditContactPage.tsx | 19 ++++++- src/pages/StatusPage.tsx | 4 +- 11 files changed, 66 insertions(+), 108 deletions(-) delete mode 100644 src/components/Chats/ChatsNavBar.tsx delete mode 100644 src/components/ContactInfo/ContactNavBar.tsx delete mode 100644 src/components/EditContact/EditNavBar.tsx delete mode 100644 src/components/Status/StatusNavBar.tsx diff --git a/src/App.tsx b/src/App.tsx index 7270b0a..aaa8736 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -12,14 +12,22 @@ import HomeIndicator from './components/iphone/HomeIndicator'; const Container = styled.div<{ $bgColor: string }>` background: ${({ $bgColor }) => $bgColor}; - border-radius: 1.25rem; `; // StatusBar와 HomeIndicator 제외 영역 const InnerContainer = styled.div` width: 23.4375rem; - height: 45.75rem; + height: 45.88rem; position: relative; + + overflow-y: auto; + overflow-x: hidden; + + scrollbar-width: none; // Firefox + -ms-overflow-style: none; // Internet Explorer/Edge + &::-webkit-scrollbar { + display: none; // Chrome, Safari + } `; // useLocation을 사용하기 위한 Wrapper 컴포넌트 diff --git a/src/components/Chats/ChatsNavBar.tsx b/src/components/Chats/ChatsNavBar.tsx deleted file mode 100644 index 23d8826..0000000 --- a/src/components/Chats/ChatsNavBar.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import styled from 'styled-components'; -import TopNavBar from '../TopNavBar/TopNavBar'; -import Edit from '../../assets/img/edit.svg'; - -export default function ContactNavBar() { - return ; -} diff --git a/src/components/ContactInfo/ContactInfo.tsx b/src/components/ContactInfo/ContactInfo.tsx index dda3ff6..6adee3b 100644 --- a/src/components/ContactInfo/ContactInfo.tsx +++ b/src/components/ContactInfo/ContactInfo.tsx @@ -121,6 +121,18 @@ const ExtraText = styled.div` letter-spacing: -0.02063rem; `; +const ExtraDetailText = styled.div` + color: rgba(60, 60, 67, 0.6); + text-align: right; + font-family: 'SF Pro Text'; + font-size: 1rem; + font-style: normal; + font-weight: 400; + line-height: 1.375rem; + letter-spacing: -0.0235rem; + margin-left: auto; +`; + const RightArrowImg = styled.img` width: 0.4375rem; height: 0.75rem; @@ -158,32 +170,14 @@ export default function ContactInfo(props: TitleBarProps) { alt="Media, Links, and Docs 아이콘 이미지" /> Media, Links, and Docs - - 12 - + 12 Starred Messages - - None - + None @@ -198,32 +192,14 @@ export default function ContactInfo(props: TitleBarProps) { Mute - - No - + No Custom Tone - - Default (Note) - + Default (Note) diff --git a/src/components/ContactInfo/ContactNavBar.tsx b/src/components/ContactInfo/ContactNavBar.tsx deleted file mode 100644 index abd60a9..0000000 --- a/src/components/ContactInfo/ContactNavBar.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { useNavigate } from 'react-router-dom'; -import TopNavBar from '../TopNavBar/TopNavBar'; -import Left from '../../assets/img/left.svg'; - -export default function ContactNavBar() { - const navigate = useNavigate(); - - // 이전 페이지로 돌아가는 함수 - const goBack = () => { - navigate(-1); - }; - - return ( - - ); -} diff --git a/src/components/EditContact/EditNavBar.tsx b/src/components/EditContact/EditNavBar.tsx deleted file mode 100644 index dd023d1..0000000 --- a/src/components/EditContact/EditNavBar.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { useNavigate } from 'react-router-dom'; -import TopNavBar from '../TopNavBar/TopNavBar'; - -export default function EditNavBar() { - const navigate = useNavigate(); - - // 이전 페이지로 돌아가는 함수 - const goBack = () => { - navigate(-1); - }; - - return ( - - ); -} diff --git a/src/components/Status/StatusNavBar.tsx b/src/components/Status/StatusNavBar.tsx deleted file mode 100644 index 620fe33..0000000 --- a/src/components/Status/StatusNavBar.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import TopNavBar from '../TopNavBar/TopNavBar'; - -export default function StatusNavBar() { - return ; -} diff --git a/src/components/iphone/HomeIndicator.tsx b/src/components/iphone/HomeIndicator.tsx index 8815cfa..2733367 100644 --- a/src/components/iphone/HomeIndicator.tsx +++ b/src/components/iphone/HomeIndicator.tsx @@ -4,12 +4,12 @@ import HomeBar from '../../assets/img/home-indicator.svg'; const HomeBarContainer = styled.div` width: 23.4375rem; height: 2.125rem; + background: none; `; - const HomeBarImg = styled.img` width: 8.375rem; height: 0.3125rem; - padding: 1.3125rem 7.5rem 0.5rem 7.5625rem; + margin: 1.25rem 7.5rem 0.56rem 7.56rem; `; export default function HomeIndicator() { diff --git a/src/pages/ChatsPage.tsx b/src/pages/ChatsPage.tsx index 6a843fd..9fbfc44 100644 --- a/src/pages/ChatsPage.tsx +++ b/src/pages/ChatsPage.tsx @@ -1,11 +1,12 @@ -import ChatsNavBar from '../components/Chats/ChatsNavBar'; import BottomTabBar from '../components/BottomTabBar/BottomTabBar'; import ChatsList from '../components/Chats/ChatsList'; +import TopNavBar from '../components/TopNavBar/TopNavBar'; +import Edit from '../assets/img/edit.svg'; export default function ChatsPage() { return ( <> - + diff --git a/src/pages/ContactInfoPage.tsx b/src/pages/ContactInfoPage.tsx index 8163b8c..b7d3e12 100644 --- a/src/pages/ContactInfoPage.tsx +++ b/src/pages/ContactInfoPage.tsx @@ -1,15 +1,23 @@ import { useState, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; import { useSelector } from 'react-redux'; import { RootState } from '../store'; import { User } from '../types/type'; import userData from '../assets/data/userData.json'; -import ContactNavBar from '../components/ContactInfo/ContactNavBar'; +import TopNavBar from '../components/TopNavBar/TopNavBar'; import ContactInfo from '../components/ContactInfo/ContactInfo'; +import Left from '../assets/img/left.svg'; export default function ContactInfoPage() { const nowUser = useSelector((state: RootState) => state.user.nowUser); const userList = useSelector((state: RootState) => state.user.userList); const [partner, setPartner] = useState(null); + const navigate = useNavigate(); + + // 이전 페이지로 돌아가는 함수 + const goBack = () => { + navigate(-1); + }; useEffect(() => { const nextUser = userData.users.find((user) => user.id !== nowUser) ?? null; @@ -20,7 +28,13 @@ export default function ContactInfoPage() { <> {partner !== null ? ( <> - + ) : ( diff --git a/src/pages/EditContactPage.tsx b/src/pages/EditContactPage.tsx index 6cad994..c6b8a85 100644 --- a/src/pages/EditContactPage.tsx +++ b/src/pages/EditContactPage.tsx @@ -1,10 +1,25 @@ -import EditNavBar from '../components/EditContact/EditNavBar'; +import { useNavigate } from 'react-router-dom'; +import TopNavBar from '../components/TopNavBar/TopNavBar'; import EditContact from '../components/EditContact/EditContact'; export default function EditContactPage() { + const navigate = useNavigate(); + + // 이전 페이지로 돌아가는 함수 + const goBack = () => { + navigate(-1); + }; + return ( <> - + ); diff --git a/src/pages/StatusPage.tsx b/src/pages/StatusPage.tsx index f0ae3a2..687afb8 100644 --- a/src/pages/StatusPage.tsx +++ b/src/pages/StatusPage.tsx @@ -1,11 +1,11 @@ -import StatusNavBar from '../components/Status/StatusNavBar'; +import TopNavBar from '../components/TopNavBar/TopNavBar'; import BottomTabBar from '../components/BottomTabBar/BottomTabBar'; import MyStatus from '../components/Status/MyStatus'; export default function StatusPage() { return ( <> - + From 78d7c181ad65bd0f8bd92bf504911369f3bfe7ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Sat, 13 Apr 2024 04:10:53 +0900 Subject: [PATCH 31/46] =?UTF-8?q?style:=20HomeIndicator=EC=99=80=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A1=A4=20=EB=B2=94=EC=9C=84=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 33 ++++++-------------- src/components/BottomTabBar/BottomTabBar.tsx | 2 +- src/components/Chatting/ChatInput.tsx | 1 + src/components/Chatting/ChattingRoom.tsx | 2 +- src/components/ContactInfo/ContactInfo.tsx | 11 ++++++- src/components/iphone/HomeIndicator.tsx | 15 +++------ 6 files changed, 26 insertions(+), 38 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index aaa8736..9766004 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -11,23 +11,10 @@ import StatusBar from './components/iphone/StatusBar'; import HomeIndicator from './components/iphone/HomeIndicator'; const Container = styled.div<{ $bgColor: string }>` - background: ${({ $bgColor }) => $bgColor}; -`; - -// StatusBar와 HomeIndicator 제외 영역 -const InnerContainer = styled.div` width: 23.4375rem; - height: 45.88rem; + height: 50.75rem; + background: ${({ $bgColor }) => $bgColor}; position: relative; - - overflow-y: auto; - overflow-x: hidden; - - scrollbar-width: none; // Firefox - -ms-overflow-style: none; // Internet Explorer/Edge - &::-webkit-scrollbar { - display: none; // Chrome, Safari - } `; // useLocation을 사용하기 위한 Wrapper 컴포넌트 @@ -52,15 +39,13 @@ function App() { - - - } /> - } /> - } /> - } /> - } /> - - + + } /> + } /> + } /> + } /> + } /> + diff --git a/src/components/BottomTabBar/BottomTabBar.tsx b/src/components/BottomTabBar/BottomTabBar.tsx index 127bee0..7decfe2 100644 --- a/src/components/BottomTabBar/BottomTabBar.tsx +++ b/src/components/BottomTabBar/BottomTabBar.tsx @@ -1,4 +1,3 @@ -import { useEffect, useState } from 'react'; import styled from 'styled-components'; import { useNavigate, useLocation } from 'react-router-dom'; import StatusGray from '../../assets/img/status-gray.svg'; @@ -18,6 +17,7 @@ const TabBarContainer = styled.div` display: flex; align-items: center; justify-content: space-around; + margin-bottom: 2.125rem; `; const IconContainer = styled.div` diff --git a/src/components/Chatting/ChatInput.tsx b/src/components/Chatting/ChatInput.tsx index 1401fdf..121a25f 100644 --- a/src/components/Chatting/ChatInput.tsx +++ b/src/components/Chatting/ChatInput.tsx @@ -13,6 +13,7 @@ const InputContainer = styled.div` height: 2.87rem; border-top: 0.03125rem solid #a4a39e; position: relative; + margin-bottom: 2.125rem; `; const IconImg = styled.img` diff --git a/src/components/Chatting/ChattingRoom.tsx b/src/components/Chatting/ChattingRoom.tsx index 73ad996..e50f8ed 100644 --- a/src/components/Chatting/ChattingRoom.tsx +++ b/src/components/Chatting/ChattingRoom.tsx @@ -7,7 +7,7 @@ import FormatTime from './FormatTime'; const ChattingPageContainer = styled.div` width: 23.4375rem; - height: 40.37rem; + height: 40.25rem; flex-direction: column; overflow-y: auto; overflow-x: hidden; diff --git a/src/components/ContactInfo/ContactInfo.tsx b/src/components/ContactInfo/ContactInfo.tsx index 6adee3b..ab50608 100644 --- a/src/components/ContactInfo/ContactInfo.tsx +++ b/src/components/ContactInfo/ContactInfo.tsx @@ -13,9 +13,18 @@ import RightArrow from '../../assets/img/edit-arrow.svg'; const ContactPageContainer = styled.div` width: 23.4375rem; - height: 43.12rem; + height: 45.245rem; background: #efeff4; position: relative; + + overflow-y: auto; + overflow-x: hidden; + + scrollbar-width: none; // Firefox + -ms-overflow-style: none; // Internet Explorer/Edge + &::-webkit-scrollbar { + display: none; // Chrome, Safari + } `; const ProfileImg = styled.img` diff --git a/src/components/iphone/HomeIndicator.tsx b/src/components/iphone/HomeIndicator.tsx index 2733367..fcacfd5 100644 --- a/src/components/iphone/HomeIndicator.tsx +++ b/src/components/iphone/HomeIndicator.tsx @@ -1,21 +1,14 @@ import styled from 'styled-components'; import HomeBar from '../../assets/img/home-indicator.svg'; -const HomeBarContainer = styled.div` - width: 23.4375rem; - height: 2.125rem; - background: none; -`; const HomeBarImg = styled.img` width: 8.375rem; height: 0.3125rem; - margin: 1.25rem 7.5rem 0.56rem 7.56rem; + position: absolute; + bottom: 0.56rem; + left: 7.56rem; `; export default function HomeIndicator() { - return ( - - - - ); + return ; } From ebfe9cca0b47f0d70b06337e6976fba6c4737b27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Sun, 14 Apr 2024 01:07:52 +0900 Subject: [PATCH 32/46] =?UTF-8?q?style:=20=EC=B1=84=ED=8C=85=EC=9D=98=20Da?= =?UTF-8?q?teContainer=EC=9D=98=20margin=20=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/img/CharileMoore.svg | 20 +++++++++++++++----- public/img/ColinBearman.svg | 20 +++++++++++++++----- public/img/DainPark.svg | 20 +++++++++++++++----- public/img/HaileyStephenson.svg | 20 +++++++++++++++----- public/img/JoshuaLawrence.svg | 19 +++++++++++++++++++ public/img/LeahStimptson.svg | 20 +++++++++++++++----- public/img/MyStatus.svg | 20 +++++++++++++++----- public/img/SenaAdams.svg | 20 +++++++++++++++----- public/img/SiennaHwang.svg | 20 +++++++++++++++----- src/assets/data/initialChatData.json | 13 +++++++++++++ src/assets/data/userData.json | 6 ++++++ src/components/Chatting/ChattingRoom.tsx | 17 ++++++----------- src/pages/ContactInfoPage.tsx | 2 +- 13 files changed, 165 insertions(+), 52 deletions(-) create mode 100644 public/img/JoshuaLawrence.svg diff --git a/public/img/CharileMoore.svg b/public/img/CharileMoore.svg index da0e510..be0ce2b 100644 --- a/public/img/CharileMoore.svg +++ b/public/img/CharileMoore.svg @@ -1,9 +1,19 @@ - - + + + + - - + + + + + + + + + + - + diff --git a/public/img/ColinBearman.svg b/public/img/ColinBearman.svg index 65b076e..a2129bb 100644 --- a/public/img/ColinBearman.svg +++ b/public/img/ColinBearman.svg @@ -1,9 +1,19 @@ - - + + + + - - + + + + + + + + + + - + diff --git a/public/img/DainPark.svg b/public/img/DainPark.svg index dbf656e..e0eb5a3 100644 --- a/public/img/DainPark.svg +++ b/public/img/DainPark.svg @@ -1,9 +1,19 @@ - - + + + + - - + + + + + + + + + + - + diff --git a/public/img/HaileyStephenson.svg b/public/img/HaileyStephenson.svg index bd3d4a6..4f08117 100644 --- a/public/img/HaileyStephenson.svg +++ b/public/img/HaileyStephenson.svg @@ -1,9 +1,19 @@ - - + + + + - - + + + + + + + + + + - + diff --git a/public/img/JoshuaLawrence.svg b/public/img/JoshuaLawrence.svg new file mode 100644 index 0000000..f5c6cec --- /dev/null +++ b/public/img/JoshuaLawrence.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/public/img/LeahStimptson.svg b/public/img/LeahStimptson.svg index 2268d16..62683b4 100644 --- a/public/img/LeahStimptson.svg +++ b/public/img/LeahStimptson.svg @@ -1,9 +1,19 @@ - - + + + + - - + + + + + + + + + + - + diff --git a/public/img/MyStatus.svg b/public/img/MyStatus.svg index 2b9c56b..832316a 100644 --- a/public/img/MyStatus.svg +++ b/public/img/MyStatus.svg @@ -1,9 +1,19 @@ - - + + + + - - + + + + + + + + + + - + diff --git a/public/img/SenaAdams.svg b/public/img/SenaAdams.svg index ef9512b..daad4ec 100644 --- a/public/img/SenaAdams.svg +++ b/public/img/SenaAdams.svg @@ -1,9 +1,19 @@ - - + + + + - - + + + + + + + + + + - + diff --git a/public/img/SiennaHwang.svg b/public/img/SiennaHwang.svg index 6b50d67..eb0ca48 100644 --- a/public/img/SiennaHwang.svg +++ b/public/img/SiennaHwang.svg @@ -1,9 +1,19 @@ - - + + + + - - + + + + + + + + + + - + diff --git a/src/assets/data/initialChatData.json b/src/assets/data/initialChatData.json index 30a531f..4946b09 100644 --- a/src/assets/data/initialChatData.json +++ b/src/assets/data/initialChatData.json @@ -139,6 +139,19 @@ "isRead": false } ] + }, + { + "chatRoomId": 8, + "userList": [0, 9], + "chatList": [ + { + "id": 0, + "senderId": 0, + "content": "Do you like WhatsApp UI?", + "time": "2019-10-20T09:41:00", + "isRead": true + } + ] } ] } diff --git a/src/assets/data/userData.json b/src/assets/data/userData.json index 3cc4339..d404369 100644 --- a/src/assets/data/userData.json +++ b/src/assets/data/userData.json @@ -53,6 +53,12 @@ "name": "Declan Walker", "profileImg": "/img/DeclanWalker.svg", "isActive": false + }, + { + "id": 9, + "name": "Joshua Lawrence", + "profileImg": "/img/JoshuaLawrence.svg", + "isActive": true } ] } diff --git a/src/components/Chatting/ChattingRoom.tsx b/src/components/Chatting/ChattingRoom.tsx index e50f8ed..2629366 100644 --- a/src/components/Chatting/ChattingRoom.tsx +++ b/src/components/Chatting/ChattingRoom.tsx @@ -19,15 +19,15 @@ const ChattingPageContainer = styled.div` } `; -const DateContainer = styled.div` +const DateContainer = styled.div` + width: fit-content; height: 0.875rem; - display: inline-flex; + display: block; + text-align: center; padding: 0.1875rem 1rem; - align-items: center; border-radius: 0.375rem; background: #dbdfeb; - margin: ${(props) => (props.$isToday ? '0.73rem' : '0.84rem')} 9.25rem 0.84rem - 9.56rem; + margin: 0.63rem auto; `; const DateText = styled.div` @@ -57,11 +57,6 @@ function getTodayDateStringDMY() { return formatDateToDMY(new Date()); } -// DateContainer에 전달되는 props의 타입을 정의 -interface DateContainerProps { - $isToday: boolean; -} - export default function ChattingRoom() { const nowUser = useSelector((state: RootState) => state.user.nowUser); // 현재 사용자 상태 가져오기 const chatList = useSelector( @@ -94,7 +89,7 @@ export default function ChattingRoom() { return ( {showDateText && ( - + {isToday ? 'Today' : chatDateDMY} )} diff --git a/src/pages/ContactInfoPage.tsx b/src/pages/ContactInfoPage.tsx index b7d3e12..dab73c3 100644 --- a/src/pages/ContactInfoPage.tsx +++ b/src/pages/ContactInfoPage.tsx @@ -30,7 +30,7 @@ export default function ContactInfoPage() { <> Date: Sun, 14 Apr 2024 01:39:23 +0900 Subject: [PATCH 33/46] =?UTF-8?q?style:=20=EC=B1=84=ED=8C=85=20=EC=9E=85?= =?UTF-8?q?=EB=A0=A5=EC=B0=BD=20padding=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Chatting/ChatInput.tsx | 9 ++++----- src/style/GlobalStyles.tsx | 8 ++++++-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/components/Chatting/ChatInput.tsx b/src/components/Chatting/ChatInput.tsx index 121a25f..0408041 100644 --- a/src/components/Chatting/ChatInput.tsx +++ b/src/components/Chatting/ChatInput.tsx @@ -26,9 +26,9 @@ const IconImg = styled.img` const InputBox = styled.div` display: flex; align-items: center; - justify-content: center; - width: 14.375rem; - height: 1.875rem; + width: 13.115rem; + height: 1.375rem; + padding: 0.25rem 0.63rem; border-radius: 0.9375rem; border: 0.6px solid #dedfe0; background: #fff; @@ -38,8 +38,7 @@ const InputBox = styled.div` `; const Input = styled.input` - width: 85%; - height: 100%; + width: 11.615rem; border: none; outline: none; font-family: 'SF Pro Text'; diff --git a/src/style/GlobalStyles.tsx b/src/style/GlobalStyles.tsx index b13b908..3278efa 100644 --- a/src/style/GlobalStyles.tsx +++ b/src/style/GlobalStyles.tsx @@ -11,7 +11,7 @@ const GlobalStyle = createGlobalStyle` background: #A7A7A7; } - body{ + body { width: 23.4375rem; height: 100%; display: flex; @@ -22,7 +22,11 @@ const GlobalStyle = createGlobalStyle` #root { height: 100dvh; width: 100%; - } + } + + input { + padding: 0; + } `; export default GlobalStyle; From cec65522725b7cb9298681340e7bcdd5e6b92086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Sun, 14 Apr 2024 03:46:00 +0900 Subject: [PATCH 34/46] =?UTF-8?q?style:=20=EC=B1=84=ED=8C=85=20ChatBubble?= =?UTF-8?q?=20=EC=B5=9C=EB=8C=80=20=EB=84=88=EB=B9=84=EB=A1=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Chatting/ChatBubble.tsx | 66 ++++++++++++++------------ 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/src/components/Chatting/ChatBubble.tsx b/src/components/Chatting/ChatBubble.tsx index 6d0570d..67488bb 100644 --- a/src/components/Chatting/ChatBubble.tsx +++ b/src/components/Chatting/ChatBubble.tsx @@ -4,43 +4,46 @@ import BubbleGreen from '../../assets/img/bubble-green.svg'; import BubbleGray from '../../assets/img/bubble-gray.svg'; const ChatBubbleContainer = styled.div` - width: 18.6rem; + display: flex; + justify-content: ${(props) => + props.$isSentByMe ? 'flex-end' : 'flex-start'}; margin-bottom: 0.31rem; margin-left: ${(props) => (props.$isSentByMe ? 'auto' : '0.44rem')}; margin-right: ${(props) => (props.$isSentByMe ? '0.44rem' : 'auto')}; position: relative; `; -const BubbleImg = styled.img` +const BubbleWrapper = styled.div` + display: flex; + flex-direction: ${(props) => (props.$isSentByMe ? 'row-reverse' : 'row')}; + align-items: flex-end; +`; + +const BubbleImg = styled.img` width: 0.5rem; height: 0.9375rem; - position: absolute; - left: ${(props) => (props.$isSentByMe ? '18.125rem' : '0')}; - right: ${(props) => (props.$isSentByMe ? '0' : '18.125rem')}; - bottom: 0; `; const BubbleRectangle = styled.div` - display: flex; - width: 17.125rem; + display: inline-flex; + min-width: 7.06rem; + max-width: 17.125rem; padding: 0.5rem; flex-direction: column; gap: 0.625rem; border-radius: 0.375rem; background: ${(props) => (props.$isSentByMe ? '#E1F3D2' : '#eaeaea')}; - margin-left: ${(props) => (props.$isSentByMe ? '0' : 'auto')}; - margin-right: ${(props) => (props.$isSentByMe ? 'auto' : '0')}; `; const TextContainer = styled.div` - display: flex; + display: inline-flex; flex-direction: column; align-items: flex-end; position: relative; `; const ChatText = styled.div` - width: 17.125rem; + width: 100%; color: #000; font-family: 'SF Pro Text'; font-size: 1.0625rem; @@ -94,25 +97,26 @@ export default function ChatBubble(props: ChatBubbleProps) { return ( - - - - {content} - - {time} - - - - + + + + + {content} + + {time} + + + + + ); } From a1a304ad6fb56aff56cc33de549b85c254628975 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Sun, 14 Apr 2024 04:47:59 +0900 Subject: [PATCH 35/46] =?UTF-8?q?style:=20=EC=B1=84=ED=8C=85=20=EB=84=88?= =?UTF-8?q?=EB=B9=84=EC=97=90=20=EB=94=B0=EB=A5=B8=20=20=ED=85=8D=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EC=99=80=20=EC=8B=9C=EA=B0=84=20flex-direction=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Chatting/ChatBubble.tsx | 50 +++++++++++++++++++++----- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/src/components/Chatting/ChatBubble.tsx b/src/components/Chatting/ChatBubble.tsx index 67488bb..dcc1ca3 100644 --- a/src/components/Chatting/ChatBubble.tsx +++ b/src/components/Chatting/ChatBubble.tsx @@ -1,3 +1,4 @@ +import { useState, useEffect, useRef } from 'react'; import styled from 'styled-components'; import CheckMark from '../../assets/img/checkmark.svg'; import BubbleGreen from '../../assets/img/bubble-green.svg'; @@ -6,7 +7,9 @@ import BubbleGray from '../../assets/img/bubble-gray.svg'; const ChatBubbleContainer = styled.div` display: flex; justify-content: ${(props) => - props.$isSentByMe ? 'flex-end' : 'flex-start'}; + props.$isSentByMe + ? 'flex-end' + : 'flex-start'}; // 나와 상대방의 채팅 버블을 각자 정렬 margin-bottom: 0.31rem; margin-left: ${(props) => (props.$isSentByMe ? 'auto' : '0.44rem')}; margin-right: ${(props) => (props.$isSentByMe ? '0.44rem' : 'auto')}; @@ -15,7 +18,8 @@ const ChatBubbleContainer = styled.div` const BubbleWrapper = styled.div` display: flex; - flex-direction: ${(props) => (props.$isSentByMe ? 'row-reverse' : 'row')}; + flex-direction: ${(props) => + props.$isSentByMe ? 'row-reverse' : 'row'}; // 말풍선과 버블의 순서 align-items: flex-end; `; @@ -26,7 +30,7 @@ const BubbleImg = styled.img` const BubbleRectangle = styled.div` display: inline-flex; - min-width: 7.06rem; + min-width: 5.5625rem; max-width: 17.125rem; padding: 0.5rem; flex-direction: column; @@ -35,9 +39,9 @@ const BubbleRectangle = styled.div` background: ${(props) => (props.$isSentByMe ? '#E1F3D2' : '#eaeaea')}; `; -const TextContainer = styled.div` +const TextContainer = styled.div` display: inline-flex; - flex-direction: column; + flex-direction: ${(props) => props.direction}; align-items: flex-end; position: relative; `; @@ -55,11 +59,12 @@ const ChatText = styled.div` margin-left: 0; `; -const MessageInfoContainer = styled.div` +const MessageInfoContainer = styled.div` display: flex; flex-direction: row; align-items: center; margin-top: 0.25rem; + margin-left: ${(props) => (props.direction === 'row' ? '0.75rem' : '0')}; `; const TimeText = styled.div` @@ -92,7 +97,34 @@ interface ChatBubbleProps extends ChatBubbleBaseProps { time: string; } +interface TextContainerProps { + direction: string; +} + export default function ChatBubble(props: ChatBubbleProps) { + const chatTextRef = useRef(null); + const [flexDirection, setFlexDirection] = useState('column'); + + useEffect(() => { + const checkTextWidth = () => { + if (chatTextRef.current) { + const width = chatTextRef.current.offsetWidth; + const maxWidth = 17 * 16; // rem을 px로 변환 (가정: 1rem = 16px) + if (width > maxWidth) { + setFlexDirection('column'); + } else { + setFlexDirection('row'); + } + } + }; + + checkTextWidth(); + + // 창 크기 변경에 따라 다시 검사 + window.addEventListener('resize', checkTextWidth); + return () => window.removeEventListener('resize', checkTextWidth); + }, []); + const { $isSentByMe, content, time, $isRead } = props; return ( @@ -103,9 +135,9 @@ export default function ChatBubble(props: ChatBubbleProps) { alt="채팅 버블 이미지" /> - - {content} - + + {content} + {time} Date: Tue, 16 Apr 2024 04:17:04 +0900 Subject: [PATCH 36/46] =?UTF-8?q?feat:=20Chats=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EC=B6=9C=EB=A0=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Chats/Chat.tsx | 38 +++++++++++++ src/components/Chats/ChatsList.tsx | 64 +++++++++++++++++++++- src/components/Chatting/ChattingRoom.tsx | 1 + src/components/Chatting/TitleBar.tsx | 9 ++- src/components/ContactInfo/ContactInfo.tsx | 2 +- src/features/userSlice.ts | 7 ++- 6 files changed, 114 insertions(+), 7 deletions(-) create mode 100644 src/components/Chats/Chat.tsx diff --git a/src/components/Chats/Chat.tsx b/src/components/Chats/Chat.tsx new file mode 100644 index 0000000..4005e25 --- /dev/null +++ b/src/components/Chats/Chat.tsx @@ -0,0 +1,38 @@ +import styled from 'styled-components'; +import { TitleBarProps } from '../../types/type'; + +// 각각의 채팅 +const ChatContainer = styled.div` + width: 21.4375rem; + height: 3.435rem; + padding: 0.5rem 1rem 0.69rem 1rem; + display: flex; +`; + +const ProfileImg = styled.img` + width: 3.25rem; + height: 3.25rem; + border-radius: 3.25rem; + margin-top: 0.19rem; +`; + +const UserName = styled.div` + color: #000; + font-family: 'SF Pro Text'; + font-size: 1rem; + font-style: normal; + font-weight: 600; + line-height: 1.3125rem; + letter-spacing: -0.02063rem; +`; + +export default function Chat(props: TitleBarProps) { + const { name, profileImg } = props; + + return ( + + + {name} + + ); +} diff --git a/src/components/Chats/ChatsList.tsx b/src/components/Chats/ChatsList.tsx index 7039ec2..b6f8436 100644 --- a/src/components/Chats/ChatsList.tsx +++ b/src/components/Chats/ChatsList.tsx @@ -1,11 +1,73 @@ +import React, { useRef, useState, useEffect } from 'react'; import styled from 'styled-components'; +import { useSelector } from 'react-redux'; +import { RootState } from '../../store'; +import { User } from '../../types/type'; +import Chat from '../Chats/Chat'; const ChatsListPageContainer = styled.div` width: 23.4375rem; height: 40.06rem; background: #fff; + display: flex; + flex-direction: column; + + overflow-y: auto; + overflow-x: hidden; + + scrollbar-width: none; // Firefox + -ms-overflow-style: none; // Internet Explorer/Edge + &::-webkit-scrollbar { + display: none; // Chrome, Safari + } +`; + +const TitleContainer = styled.div` + width: 21.4375rem; + height: 1.25rem; + padding: 0.75rem 1rem; + display: flex; + justify-content: space-between; +`; + +const TitleText = styled.div` + color: #1bd742; + font-family: 'SF Pro Text'; + font-size: 17px; + font-style: normal; + font-weight: 400; + line-height: normal; + letter-spacing: -0.4px; `; export default function ChatsList() { - return ; + const chattings = useSelector((state: RootState) => state.chat.chattings); + const userList = useSelector((state: RootState) => state.user.userList); + + return ( + + + Broadcast Lists + New Group + + + {chattings.map((chatRoom) => { + // 현재 채팅방에서 현재 사용자를 제외한 상대방의 정보를 찾습니다. + const partner = + userList.find( + (user) => chatRoom.userList.includes(user.id) && user.id !== 0 + ) ?? null; + + // 각 채팅방에 대한 상대방의 프로필을 출력합니다. + return ( + + ); + })} + + ); } diff --git a/src/components/Chatting/ChattingRoom.tsx b/src/components/Chatting/ChattingRoom.tsx index 2629366..d675ec3 100644 --- a/src/components/Chatting/ChattingRoom.tsx +++ b/src/components/Chatting/ChattingRoom.tsx @@ -8,6 +8,7 @@ import FormatTime from './FormatTime'; const ChattingPageContainer = styled.div` width: 23.4375rem; height: 40.25rem; + display: flex; flex-direction: column; overflow-y: auto; overflow-x: hidden; diff --git a/src/components/Chatting/TitleBar.tsx b/src/components/Chatting/TitleBar.tsx index 22f9e00..51f68a6 100644 --- a/src/components/Chatting/TitleBar.tsx +++ b/src/components/Chatting/TitleBar.tsx @@ -4,6 +4,7 @@ import { useSelector, useDispatch } from 'react-redux'; import { changeUser } from '../../features/userSlice'; import { RootState } from '../../store'; import { TitleBarProps } from '../../types/type'; +import { User } from '../../types/type'; import TopNavBar from '../TopNavBar/TopNavBar'; import Left from '../../assets/img/left.svg'; import Call from '../../assets/img/call.svg'; @@ -61,10 +62,14 @@ export default function TitleBar(props: TitleBarProps) { const navigate = useNavigate(); const handleChangeUser = () => { - const currentIndex = userList.indexOf(nowUser); // 현재 nowUser의 인덱스 + // 현재 nowUser와 일치하는 사용자 객체를 찾습니다. + const currentIndex = userList.findIndex( + (user: User) => user.id === nowUser + ); const nextIndex = currentIndex === 0 ? 1 : 0; const nextUser = userList[nextIndex]; - dispatch(changeUser(nextUser)); + // 여기서 nextUser.id를 전달해야 합니다. changeUser 액션이 사용자 ID를 기대한다고 가정합니다. + dispatch(changeUser(nextUser.id)); }; return ( diff --git a/src/components/ContactInfo/ContactInfo.tsx b/src/components/ContactInfo/ContactInfo.tsx index ab50608..c030d07 100644 --- a/src/components/ContactInfo/ContactInfo.tsx +++ b/src/components/ContactInfo/ContactInfo.tsx @@ -76,7 +76,7 @@ const InfoText = styled.div` `; const Seperator = styled.div` - height: 0.0625rem; + height: 1px; background: rgba(60, 60, 67, 0.29); margin-left: auto; `; diff --git a/src/features/userSlice.ts b/src/features/userSlice.ts index 13207c9..c3a9eac 100644 --- a/src/features/userSlice.ts +++ b/src/features/userSlice.ts @@ -1,14 +1,15 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import initialChatData from '../assets/data/initialChatData.json'; +import { User } from '../types/type'; +import UserData from '../assets/data/userData.json'; interface UserState { nowUser: number; - userList: number[]; + userList: User[]; } const initialState: UserState = { nowUser: 0, - userList: initialChatData.chattings[0].userList, // 첫 번째 채팅방의 userList를 초기 상태로 설정 + userList: UserData.users, }; export const userSlice = createSlice({ From 8b6e89a64c872a636981fc47cdfb5ce8a9a24120 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Tue, 16 Apr 2024 23:11:33 +0900 Subject: [PATCH 37/46] =?UTF-8?q?feat:=20chats=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EC=99=84=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Chats/Chat.tsx | 77 ++++++++++++++++++- src/components/Chats/ChatsList.tsx | 18 +++-- src/components/Chatting/ChattingRoom.tsx | 21 ++--- src/components/Chatting/FormatDateToDMY.ts | 11 +++ .../{FormatTime.ts => FormatTimeToAMPM.ts} | 3 +- src/types/type.ts | 6 ++ 6 files changed, 109 insertions(+), 27 deletions(-) create mode 100644 src/components/Chatting/FormatDateToDMY.ts rename src/components/Chatting/{FormatTime.ts => FormatTimeToAMPM.ts} (71%) diff --git a/src/components/Chats/Chat.tsx b/src/components/Chats/Chat.tsx index 4005e25..87230f4 100644 --- a/src/components/Chats/Chat.tsx +++ b/src/components/Chats/Chat.tsx @@ -1,5 +1,8 @@ import styled from 'styled-components'; import { TitleBarProps } from '../../types/type'; +import { ChatProps } from '../../types/type'; +import FormatDateToDMY from '../Chatting/FormatDateToDMY'; +import checkmark from '../../assets/img/checkmark.svg'; // 각각의 채팅 const ChatContainer = styled.div` @@ -16,6 +19,19 @@ const ProfileImg = styled.img` margin-top: 0.19rem; `; +const TextContainer = styled.div` + display: flex; + flex-direction: column; + padding-left: 0.75rem; +`; + +// 유저 이름과 채팅 시간 컨테이너 +const NameTimeContainer = styled.div` + width: 17.4325rem; + display: flex; + justify-content: space-between; +`; + const UserName = styled.div` color: #000; font-family: 'SF Pro Text'; @@ -26,13 +42,68 @@ const UserName = styled.div` letter-spacing: -0.02063rem; `; -export default function Chat(props: TitleBarProps) { - const { name, profileImg } = props; +const TimeText = styled.div` + margin-top: 0.13rem; + color: #8e8e93; + text-align: right; + font-family: 'SF Pro Text'; + font-size: 0.875rem; + font-style: normal; + font-weight: 400; + line-height: normal; + letter-spacing: -0.00938rem; +`; + +const ContentContainer = styled.div` + display: block; + align-items: center; +`; + +// 체크 이미지를 ChatText 컴포넌트 내부의 ::before 가상 요소로 삽입 +// 체크 이미지가 텍스트의 일부로 취급되어 텍스트가 줄바꿈될 때 체크 이미지는 첫 번째 줄에 그대로 유지되고, 텍스트는 체크 이미지 아래부터 시작 +const ChatText = styled.div<{ $isRead: boolean }>` + color: #8e8e93; + font-family: 'SF Pro Text'; + font-size: 0.875rem; + font-style: normal; + font-weight: 400; + line-height: normal; + letter-spacing: -0.0125rem; + text-align: left; + width: 16.25rem; + + ${({ $isRead }) => + $isRead && + ` + &::before { + content: url(${checkmark}); + display: inline-block; + width: 0.875rem; + height: 0.875rem; + margin-right: 0.28rem; // 이미지와 텍스트 사이 공간 + vertical-align: middle; + } + `} +`; + +// 두 타입을 결합하여 새로운 타입 정의 +type CombinedProps = TitleBarProps & ChatProps; + +export default function Chat(props: CombinedProps) { + const { name, profileImg, lastChatTime, lastChatContent, $isRead } = props; return ( - {name} + + + {name} + {FormatDateToDMY(lastChatTime)} + + + {lastChatContent} + + ); } diff --git a/src/components/Chats/ChatsList.tsx b/src/components/Chats/ChatsList.tsx index b6f8436..288bf75 100644 --- a/src/components/Chats/ChatsList.tsx +++ b/src/components/Chats/ChatsList.tsx @@ -1,8 +1,6 @@ -import React, { useRef, useState, useEffect } from 'react'; import styled from 'styled-components'; import { useSelector } from 'react-redux'; import { RootState } from '../../store'; -import { User } from '../../types/type'; import Chat from '../Chats/Chat'; const ChatsListPageContainer = styled.div` @@ -52,19 +50,25 @@ export default function ChatsList() { {chattings.map((chatRoom) => { - // 현재 채팅방에서 현재 사용자를 제외한 상대방의 정보를 찾습니다. + // 현재 채팅방에서 현재 사용자를 제외한 상대방의 정보를 찾기 const partner = userList.find( (user) => chatRoom.userList.includes(user.id) && user.id !== 0 ) ?? null; - // 각 채팅방에 대한 상대방의 프로필을 출력합니다. + // 마지막 채팅 정보를 추출 + const lastChat = + chatRoom.chatList[chatRoom.chatList.length - 1] || null; + + // 각 채팅방에 대한 상대방의 프로필을 출력 return ( ); })} diff --git a/src/components/Chatting/ChattingRoom.tsx b/src/components/Chatting/ChattingRoom.tsx index d675ec3..2673670 100644 --- a/src/components/Chatting/ChattingRoom.tsx +++ b/src/components/Chatting/ChattingRoom.tsx @@ -3,7 +3,8 @@ import styled from 'styled-components'; import { useSelector } from 'react-redux'; import { RootState } from '../../store'; import ChatBubble from './ChatBubble'; -import FormatTime from './FormatTime'; +import FormatTimeToAMPM from './FormatTimeToAMPM'; +import FormatDateToDMY from './FormatDateToDMY'; const ChattingPageContainer = styled.div` width: 23.4375rem; @@ -41,21 +42,9 @@ const DateText = styled.div` line-height: normal; `; -// 채팅 시간을 "dd/mm/yy" 형식의 문자열로 변환하는 함수 -function formatDateToDMY(dateString: Date | string) { - const date = new Date(dateString); - const day = date.getDate(); - const month = date.getMonth() + 1; // 월은 0부터 시작하므로 1을 더해줌 - const year = date.getFullYear() % 100; // 년도의 마지막 두 자리만 가져옴 - - return `${day < 10 ? `0${day}` : day}/${ - month < 10 ? `0${month}` : month - }/${year}`; -} - // 오늘 날짜를 "dd/mm/yy" 형식의 문자열로 반환하는 함수 function getTodayDateStringDMY() { - return formatDateToDMY(new Date()); + return FormatDateToDMY(new Date()); } export default function ChattingRoom() { @@ -76,7 +65,7 @@ export default function ChattingRoom() { return ( {chatList.map((chat) => { - const chatDateDMY = formatDateToDMY(chat.time); // 현재 채팅의 날짜를 "dd/mm/yy" 형식으로 변환 + const chatDateDMY = FormatDateToDMY(chat.time); // 현재 채팅의 날짜를 "dd/mm/yy" 형식으로 변환 let showDateText = false; if (chatDateDMY !== lastDate) { @@ -97,7 +86,7 @@ export default function ChattingRoom() { diff --git a/src/components/Chatting/FormatDateToDMY.ts b/src/components/Chatting/FormatDateToDMY.ts new file mode 100644 index 0000000..cb98f9a --- /dev/null +++ b/src/components/Chatting/FormatDateToDMY.ts @@ -0,0 +1,11 @@ +// 채팅 시간을 "dd/mm/yy" 형식의 문자열로 변환하는 함수 +export default function FormatDateToDMY(dateString: Date | string) { + const date = new Date(dateString); + const day = date.getDate(); + const month = date.getMonth() + 1; // 월은 0부터 시작하므로 1을 더해줌 + const year = date.getFullYear() % 100; // 년도의 마지막 두 자리만 가져옴 + + return `${day < 10 ? `0${day}` : day}/${ + month < 10 ? `0${month}` : month + }/${year}`; +} diff --git a/src/components/Chatting/FormatTime.ts b/src/components/Chatting/FormatTimeToAMPM.ts similarity index 71% rename from src/components/Chatting/FormatTime.ts rename to src/components/Chatting/FormatTimeToAMPM.ts index ecf4cfb..a95634d 100644 --- a/src/components/Chatting/FormatTime.ts +++ b/src/components/Chatting/FormatTimeToAMPM.ts @@ -1,4 +1,5 @@ -export default function FormatTime(dateInput: Date | string): string { +// 채팅 시간을 "09:41am" 형식의 문자열로 변환하는 함수 +export default function FormatTimeToAMPM(dateInput: Date | string): string { const date = typeof dateInput === 'string' ? new Date(dateInput) : dateInput; let hours = date.getHours(); diff --git a/src/types/type.ts b/src/types/type.ts index 399e29a..22816a9 100644 --- a/src/types/type.ts +++ b/src/types/type.ts @@ -10,3 +10,9 @@ export interface User { profileImg: string; isActive: boolean; } + +export interface ChatProps { + lastChatContent: string; + lastChatTime: string; + $isRead: boolean; +} From 9f14f5ccb713e50e9518b9ebbd0cb06ab5323ff3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Wed, 17 Apr 2024 03:03:00 +0900 Subject: [PATCH 38/46] =?UTF-8?q?feat:=20chats=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EA=B0=81=20=EC=B1=84=ED=8C=85=EB=B0=A9=20=ED=81=B4=EB=A6=AD=20?= =?UTF-8?q?=EC=8B=9C=20=ED=95=B4=EB=8B=B9=20=EC=B1=84=ED=8C=85=EB=B0=A9=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Chats/Chat.tsx | 2 ++ src/components/Chats/ChatsList.tsx | 25 +++++++++++++++++-------- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/components/Chats/Chat.tsx b/src/components/Chats/Chat.tsx index 87230f4..0d69cbe 100644 --- a/src/components/Chats/Chat.tsx +++ b/src/components/Chats/Chat.tsx @@ -1,4 +1,5 @@ import styled from 'styled-components'; +import { useNavigate } from 'react-router-dom'; import { TitleBarProps } from '../../types/type'; import { ChatProps } from '../../types/type'; import FormatDateToDMY from '../Chatting/FormatDateToDMY'; @@ -90,6 +91,7 @@ const ChatText = styled.div<{ $isRead: boolean }>` type CombinedProps = TitleBarProps & ChatProps; export default function Chat(props: CombinedProps) { + const navigate = useNavigate(); const { name, profileImg, lastChatTime, lastChatContent, $isRead } = props; return ( diff --git a/src/components/Chats/ChatsList.tsx b/src/components/Chats/ChatsList.tsx index 288bf75..f665c0b 100644 --- a/src/components/Chats/ChatsList.tsx +++ b/src/components/Chats/ChatsList.tsx @@ -1,4 +1,5 @@ import styled from 'styled-components'; +import { useNavigate } from 'react-router-dom'; import { useSelector } from 'react-redux'; import { RootState } from '../../store'; import Chat from '../Chats/Chat'; @@ -39,9 +40,15 @@ const TitleText = styled.div` `; export default function ChatsList() { + const navigate = useNavigate(); const chattings = useSelector((state: RootState) => state.chat.chattings); const userList = useSelector((state: RootState) => state.user.userList); + // 채팅방 클릭 핸들러 함수 + const handleChatClick = (chatRoomId: number) => { + navigate(`/chatroom/${chatRoomId}`); // `/chatroom/{id}` 경로로 이동 + }; + return ( @@ -62,14 +69,16 @@ export default function ChatsList() { // 각 채팅방에 대한 상대방의 프로필을 출력 return ( - +
handleChatClick(chatRoom.chatRoomId)}> + +
); })} From b152f091720c46ed731cd16eb1b75919a5722584 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Wed, 17 Apr 2024 03:58:08 +0900 Subject: [PATCH 39/46] =?UTF-8?q?feat:=20=EA=B0=81=20=EC=B1=84=ED=8C=85?= =?UTF-8?q?=EB=B0=A9=EC=97=90=20=EB=94=B0=EB=A5=B8=20ChattingPage=20?= =?UTF-8?q?=EC=99=84=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 4 +-- src/assets/data/initialChatData.json | 6 ++-- src/components/BottomTabBar/BottomTabBar.tsx | 6 ++-- src/components/Chats/Chat.tsx | 5 ++- src/components/Chatting/ChattingRoom.tsx | 6 ++-- src/components/Chatting/TitleBar.tsx | 6 ++-- src/components/ContactInfo/ContactInfo.tsx | 2 +- src/components/TopNavBar/TopNavBar.tsx | 23 ++++++------ src/features/userSlice.ts | 2 +- src/pages/ChattingPage.tsx | 38 ++++++++++++++++---- src/pages/ContactInfoPage.tsx | 2 +- src/types/{type.ts => interface.ts} | 12 +++++++ 12 files changed, 74 insertions(+), 38 deletions(-) rename src/types/{type.ts => interface.ts} (60%) diff --git a/src/App.tsx b/src/App.tsx index 9766004..07f426a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -40,9 +40,9 @@ function App() { - } /> + } /> + } /> } /> - } /> } /> } /> diff --git a/src/assets/data/initialChatData.json b/src/assets/data/initialChatData.json index 4946b09..0aa8f6a 100644 --- a/src/assets/data/initialChatData.json +++ b/src/assets/data/initialChatData.json @@ -55,7 +55,7 @@ "chatList": [ { "id": 0, - "senderId": 2, + "senderId": 0, "content": "Is that yours?", "time": "2024-03-05T09:41:00", "isRead": true @@ -81,7 +81,7 @@ "chatList": [ { "id": 0, - "senderId": 4, + "senderId": 0, "content": "Mate where???", "time": "2024-02-15T09:41:00", "isRead": true @@ -120,7 +120,7 @@ "chatList": [ { "id": 0, - "senderId": 7, + "senderId": 0, "content": "언니 이거 봄?", "time": "2024-01-15T09:41:00", "isRead": true diff --git a/src/components/BottomTabBar/BottomTabBar.tsx b/src/components/BottomTabBar/BottomTabBar.tsx index 7decfe2..3fb6c86 100644 --- a/src/components/BottomTabBar/BottomTabBar.tsx +++ b/src/components/BottomTabBar/BottomTabBar.tsx @@ -77,13 +77,13 @@ export default function BotttomTabBar() { Camera
- navigate('/chats')}> + navigate('/')}> - Chats + Chats ` type CombinedProps = TitleBarProps & ChatProps; export default function Chat(props: CombinedProps) { - const navigate = useNavigate(); const { name, profileImg, lastChatTime, lastChatContent, $isRead } = props; return ( diff --git a/src/components/Chatting/ChattingRoom.tsx b/src/components/Chatting/ChattingRoom.tsx index 2673670..1664175 100644 --- a/src/components/Chatting/ChattingRoom.tsx +++ b/src/components/Chatting/ChattingRoom.tsx @@ -2,6 +2,7 @@ import React, { useRef, useEffect } from 'react'; import styled from 'styled-components'; import { useSelector } from 'react-redux'; import { RootState } from '../../store'; +import { Chats } from '../../types/interface'; import ChatBubble from './ChatBubble'; import FormatTimeToAMPM from './FormatTimeToAMPM'; import FormatDateToDMY from './FormatDateToDMY'; @@ -47,11 +48,8 @@ function getTodayDateStringDMY() { return FormatDateToDMY(new Date()); } -export default function ChattingRoom() { +export default function ChattingRoom({ chatList }: Chats) { const nowUser = useSelector((state: RootState) => state.user.nowUser); // 현재 사용자 상태 가져오기 - const chatList = useSelector( - (state: RootState) => state.chat.chattings[0].chatList - ); const ChattingRoomRef = useRef(null); useEffect(() => { diff --git a/src/components/Chatting/TitleBar.tsx b/src/components/Chatting/TitleBar.tsx index 51f68a6..235b96d 100644 --- a/src/components/Chatting/TitleBar.tsx +++ b/src/components/Chatting/TitleBar.tsx @@ -3,8 +3,8 @@ import { useNavigate } from 'react-router-dom'; import { useSelector, useDispatch } from 'react-redux'; import { changeUser } from '../../features/userSlice'; import { RootState } from '../../store'; -import { TitleBarProps } from '../../types/type'; -import { User } from '../../types/type'; +import { TitleBarProps } from '../../types/interface'; +import { User } from '../../types/interface'; import TopNavBar from '../TopNavBar/TopNavBar'; import Left from '../../assets/img/left.svg'; import Call from '../../assets/img/call.svg'; @@ -77,7 +77,7 @@ export default function TitleBar(props: TitleBarProps) { leftImgSrc={Left} leftText="12" rightImgSrc={Call} - leftTextOnClick={() => navigate('/chats')} + leftTextOnClick={() => navigate('/')} > diff --git a/src/features/userSlice.ts b/src/features/userSlice.ts index c3a9eac..f42b0f7 100644 --- a/src/features/userSlice.ts +++ b/src/features/userSlice.ts @@ -1,5 +1,5 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { User } from '../types/type'; +import { User } from '../types/interface'; import UserData from '../assets/data/userData.json'; interface UserState { diff --git a/src/pages/ChattingPage.tsx b/src/pages/ChattingPage.tsx index 877e187..cd39636 100644 --- a/src/pages/ChattingPage.tsx +++ b/src/pages/ChattingPage.tsx @@ -1,21 +1,39 @@ import { useState, useEffect } from 'react'; import { useSelector } from 'react-redux'; import { RootState } from '../store'; -import { User } from '../types/type'; -import userData from '../assets/data/userData.json'; +import { User } from '../types/interface'; +import { useParams } from 'react-router-dom'; +import { Chats } from '../types/interface'; import TitleBar from '../components/Chatting/TitleBar'; import ChattingRoom from '../components/Chatting/ChattingRoom'; import ChatInput from '../components/Chatting/ChatInput'; export default function ChattingPage() { + const { chatRoomId } = useParams(); // URL에서 채팅방 ID를 추출 const nowUser = useSelector((state: RootState) => state.user.nowUser); const userList = useSelector((state: RootState) => state.user.userList); + const chattings = useSelector((state: RootState) => state.chat.chattings); const [partner, setPartner] = useState(null); + const [chatRoom, setChatRoom] = useState(null); useEffect(() => { - const nextUser = userData.users.find((user) => user.id !== nowUser) ?? null; - setPartner(nextUser); - }, [nowUser, userList]); // nowUser 또는 userList가 변경될 때마다 effect를 실행 + // chatRoomId를 사용하여 해당 채팅방의 userList를 찾기 + const foundChatRoom = chattings.find( + (chat) => chat.chatRoomId.toString() === chatRoomId + ); + if (foundChatRoom) { + setChatRoom(foundChatRoom); // 찾은 채팅방을 상태로 설정 + // 현재 유저 제외한 상대 유저 찾기 + const nextUser = + userList.find( + (user) => + foundChatRoom.userList.includes(user.id) && user.id !== nowUser + ) ?? null; + setPartner(nextUser); + } else { + setChatRoom(null); // 찾을 수 없으면 null로 설정 + } + }, [chatRoomId, nowUser, userList, chattings]); // 넷 중 하나라도 변경될 때마다 effect를 실행 return ( <> @@ -26,7 +44,15 @@ export default function ChattingPage() { profileImg={partner.profileImg} isActive={partner.isActive} /> - + {chatRoom ? ( + + ) : ( +

채팅 내용이 없습니다.

+ )} ) : ( diff --git a/src/pages/ContactInfoPage.tsx b/src/pages/ContactInfoPage.tsx index dab73c3..4b818c6 100644 --- a/src/pages/ContactInfoPage.tsx +++ b/src/pages/ContactInfoPage.tsx @@ -2,7 +2,7 @@ import { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import { useSelector } from 'react-redux'; import { RootState } from '../store'; -import { User } from '../types/type'; +import { User } from '../types/interface'; import userData from '../assets/data/userData.json'; import TopNavBar from '../components/TopNavBar/TopNavBar'; import ContactInfo from '../components/ContactInfo/ContactInfo'; diff --git a/src/types/type.ts b/src/types/interface.ts similarity index 60% rename from src/types/type.ts rename to src/types/interface.ts index 22816a9..f81120d 100644 --- a/src/types/type.ts +++ b/src/types/interface.ts @@ -11,6 +11,18 @@ export interface User { isActive: boolean; } +export interface Chats { + chatRoomId: number; + userList: number[]; + chatList: { + id: number; + senderId: number; + content: string; + time: string; + isRead: boolean; + }[]; +} + export interface ChatProps { lastChatContent: string; lastChatTime: string; From 1b62fa24896ad9300e2a201e8f8cd34c16ac22b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Wed, 17 Apr 2024 04:34:36 +0900 Subject: [PATCH 40/46] =?UTF-8?q?style:=20DateContainer=EC=99=80=20ChatBub?= =?UTF-8?q?ble=20=EC=82=AC=EC=9D=B4=20margin=20=EA=B2=B9=EC=B9=A8=20?= =?UTF-8?q?=ED=98=84=EC=83=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Chats/ChatsList.tsx | 5 ++- src/components/Chatting/ChatInput.tsx | 4 +- src/components/Chatting/ChattingRoom.tsx | 2 +- src/pages/ChattingPage.tsx | 48 ++++++++++++------------ 4 files changed, 29 insertions(+), 30 deletions(-) diff --git a/src/components/Chats/ChatsList.tsx b/src/components/Chats/ChatsList.tsx index f665c0b..6386202 100644 --- a/src/components/Chats/ChatsList.tsx +++ b/src/components/Chats/ChatsList.tsx @@ -41,8 +41,9 @@ const TitleText = styled.div` export default function ChatsList() { const navigate = useNavigate(); - const chattings = useSelector((state: RootState) => state.chat.chattings); + const nowUser = useSelector((state: RootState) => state.user.nowUser); const userList = useSelector((state: RootState) => state.user.userList); + const chattings = useSelector((state: RootState) => state.chat.chattings); // 채팅방 클릭 핸들러 함수 const handleChatClick = (chatRoomId: number) => { @@ -60,7 +61,7 @@ export default function ChatsList() { // 현재 채팅방에서 현재 사용자를 제외한 상대방의 정보를 찾기 const partner = userList.find( - (user) => chatRoom.userList.includes(user.id) && user.id !== 0 + (user) => chatRoom.userList.includes(user.id) && user.id !== nowUser ) ?? null; // 마지막 채팅 정보를 추출 diff --git a/src/components/Chatting/ChatInput.tsx b/src/components/Chatting/ChatInput.tsx index 0408041..1c593d1 100644 --- a/src/components/Chatting/ChatInput.tsx +++ b/src/components/Chatting/ChatInput.tsx @@ -3,6 +3,7 @@ import styled from 'styled-components'; import { useDispatch, useSelector } from 'react-redux'; import { RootState } from '../../store'; import { addChat } from '../../features/chatSlice'; +import { Chats } from '../../types/interface'; import Plus from '../../assets/img/plus.svg'; import Attachment from '../../assets/img/attachment.svg'; import Camera from '../../assets/img/camera.svg'; @@ -50,7 +51,7 @@ const Input = styled.input` background: transparent; `; -export default function ChatInput() { +export default function ChatInput({ chatRoomId }: Chats) { const nowUser = useSelector((state: RootState) => state.user.nowUser); // 현재 사용자 상태 가져오기 const [value, setValue] = useState(''); const dispatch = useDispatch(); @@ -63,7 +64,6 @@ export default function ChatInput() { return; } - const chatRoomId = 0; // 이번 과제 예시 const senderId = nowUser; const time = new Date().toISOString(); // 현재 시각을 string으로 변환 const isRead = true; // 상황에 따라 설정 diff --git a/src/components/Chatting/ChattingRoom.tsx b/src/components/Chatting/ChattingRoom.tsx index 1664175..5ad2e03 100644 --- a/src/components/Chatting/ChattingRoom.tsx +++ b/src/components/Chatting/ChattingRoom.tsx @@ -10,7 +10,7 @@ import FormatDateToDMY from './FormatDateToDMY'; const ChattingPageContainer = styled.div` width: 23.4375rem; height: 40.25rem; - display: flex; + display: block; // DateContainer와 ChatBubble 사이 마진 겹침 현상을 위함 flex-direction: column; overflow-y: auto; overflow-x: hidden; diff --git a/src/pages/ChattingPage.tsx b/src/pages/ChattingPage.tsx index cd39636..f6fd260 100644 --- a/src/pages/ChattingPage.tsx +++ b/src/pages/ChattingPage.tsx @@ -35,29 +35,27 @@ export default function ChattingPage() { } }, [chatRoomId, nowUser, userList, chattings]); // 넷 중 하나라도 변경될 때마다 effect를 실행 - return ( - <> - {partner !== null ? ( - <> - - {chatRoom ? ( - - ) : ( -

채팅 내용이 없습니다.

- )} - - - ) : ( -

파트너 정보가 없습니다.

- )} - - ); + if (chatRoom && partner !== null) { + return ( + <> + + + + + ); + } else { + return

파트너 정보가 없습니다.

; + } } From 646acd47f31ed9f7432d9807d3e9052a5ba0ef41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Wed, 17 Apr 2024 14:48:19 +0900 Subject: [PATCH 41/46] =?UTF-8?q?feat:=20chats=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=EC=97=90=EC=84=9C=20nowUser=EA=B0=80=20=ED=8F=AC?= =?UTF-8?q?=ED=95=A8=EB=90=9C=20=EC=B1=84=ED=8C=85=EB=B0=A9=EB=A7=8C=20?= =?UTF-8?q?=ED=95=84=ED=84=B0=EB=A7=81=ED=95=B4=EC=84=9C=20=EC=B6=9C?= =?UTF-8?q?=EB=A0=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Chats/ChatsList.tsx | 7 ++++++- src/components/Chatting/TitleBar.tsx | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/components/Chats/ChatsList.tsx b/src/components/Chats/ChatsList.tsx index 6386202..318162c 100644 --- a/src/components/Chats/ChatsList.tsx +++ b/src/components/Chats/ChatsList.tsx @@ -45,6 +45,11 @@ export default function ChatsList() { const userList = useSelector((state: RootState) => state.user.userList); const chattings = useSelector((state: RootState) => state.chat.chattings); + // 현재 nowUser가 포함된 채팅방만 필터링 + const filteredChattings = chattings.filter((chatRoom) => + chatRoom.userList.includes(nowUser) + ); + // 채팅방 클릭 핸들러 함수 const handleChatClick = (chatRoomId: number) => { navigate(`/chatroom/${chatRoomId}`); // `/chatroom/{id}` 경로로 이동 @@ -57,7 +62,7 @@ export default function ChatsList() { New Group - {chattings.map((chatRoom) => { + {filteredChattings.map((chatRoom) => { // 현재 채팅방에서 현재 사용자를 제외한 상대방의 정보를 찾기 const partner = userList.find( diff --git a/src/components/Chatting/TitleBar.tsx b/src/components/Chatting/TitleBar.tsx index 235b96d..70fd425 100644 --- a/src/components/Chatting/TitleBar.tsx +++ b/src/components/Chatting/TitleBar.tsx @@ -62,13 +62,13 @@ export default function TitleBar(props: TitleBarProps) { const navigate = useNavigate(); const handleChangeUser = () => { - // 현재 nowUser와 일치하는 사용자 객체를 찾습니다. + // 현재 nowUser와 일치하는 사용자 객체를 찾기 const currentIndex = userList.findIndex( (user: User) => user.id === nowUser ); const nextIndex = currentIndex === 0 ? 1 : 0; const nextUser = userList[nextIndex]; - // 여기서 nextUser.id를 전달해야 합니다. changeUser 액션이 사용자 ID를 기대한다고 가정합니다. + // 여기서 nextUser.id를 전달. changeUser 액션이 사용자 ID를 기대한다고 가정 dispatch(changeUser(nextUser.id)); }; From 58b82363ef41ed432aa0491794a038c41ae8cd1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Thu, 18 Apr 2024 03:18:12 +0900 Subject: [PATCH 42/46] =?UTF-8?q?feat:=20chatting=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EC=9C=A0=EC=A0=80=20=EB=B3=80=EA=B2=BD=EC=97=90=20?= =?UTF-8?q?=EB=94=B0=EB=A5=B8=20=EC=B1=84=ED=8C=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/data/initialChatData.json | 8 ++-- src/assets/data/userData.json | 12 +++--- src/components/Chats/Chat.tsx | 3 +- src/components/Chats/ChatsList.tsx | 5 ++- src/components/Chatting/ChatBubble.tsx | 4 +- src/components/Chatting/ChattingRoom.tsx | 7 ++-- src/components/Chatting/TitleBar.tsx | 39 +++++++++++++------ src/pages/ChattingPage.tsx | 2 +- src/pages/ContactInfoPage.tsx | 22 ++++++++--- .../formatDateToDMY.ts} | 2 +- .../formatTimeToAMPM.ts} | 2 +- 11 files changed, 68 insertions(+), 38 deletions(-) rename src/{components/Chatting/FormatDateToDMY.ts => utils/formatDateToDMY.ts} (86%) rename src/{components/Chatting/FormatTimeToAMPM.ts => utils/formatTimeToAMPM.ts} (88%) diff --git a/src/assets/data/initialChatData.json b/src/assets/data/initialChatData.json index 0aa8f6a..014fdf4 100644 --- a/src/assets/data/initialChatData.json +++ b/src/assets/data/initialChatData.json @@ -71,7 +71,7 @@ "senderId": 3, "content": "I just want you to know that if it is another choice I could change, yes I ...", "time": "2024-02-27T09:41:00", - "isRead": false + "isRead": true } ] }, @@ -97,7 +97,7 @@ "senderId": 5, "content": "Photo", "time": "2024-02-10T09:41:00", - "isRead": false + "isRead": true } ] }, @@ -110,7 +110,7 @@ "senderId": 6, "content": "You reacted 🥹 to “Now I arrived in Seoul! It’s insane. You have to see...", "time": "2024-01-25T09:41:00", - "isRead": false + "isRead": true } ] }, @@ -136,7 +136,7 @@ "senderId": 0, "content": "You reacted ❤️ to “Really honoured to be there with you, thank you for..", "time": "2024-01-09T09:41:00", - "isRead": false + "isRead": true } ] }, diff --git a/src/assets/data/userData.json b/src/assets/data/userData.json index d404369..aa77954 100644 --- a/src/assets/data/userData.json +++ b/src/assets/data/userData.json @@ -14,37 +14,37 @@ }, { "id": 2, - "name": "Leah Stimptson", + "name": "Leah S.", "profileImg": "/img/LeahStimptson.svg", "isActive": true }, { "id": 3, - "name": "Colin Bearman", + "name": "Colin B.", "profileImg": "/img/ColinBearman.svg", "isActive": false }, { "id": 4, - "name": "Charile Moore", + "name": "Charile M.", "profileImg": "/img/CharileMoore.svg", "isActive": true }, { "id": 5, - "name": "Hailey Stephenson", + "name": "Hailey S.", "profileImg": "/img/HaileyStephenson.svg", "isActive": false }, { "id": 6, - "name": "Sena Adams", + "name": "Sena A.", "profileImg": "/img/SenaAdams.svg", "isActive": false }, { "id": 7, - "name": "Sienna Hwang", + "name": "Sienna H.", "profileImg": "/img/SiennaHwang.svg", "isActive": true }, diff --git a/src/components/Chats/Chat.tsx b/src/components/Chats/Chat.tsx index c6a6f84..8544841 100644 --- a/src/components/Chats/Chat.tsx +++ b/src/components/Chats/Chat.tsx @@ -1,8 +1,7 @@ import styled from 'styled-components'; -import { useNavigate } from 'react-router-dom'; import { TitleBarProps } from '../../types/interface'; import { ChatProps } from '../../types/interface'; -import FormatDateToDMY from '../Chatting/FormatDateToDMY'; +import FormatDateToDMY from '../../utils/formatDateToDMY'; import checkmark from '../../assets/img/checkmark.svg'; // 각각의 채팅 diff --git a/src/components/Chats/ChatsList.tsx b/src/components/Chats/ChatsList.tsx index 318162c..e1984ce 100644 --- a/src/components/Chats/ChatsList.tsx +++ b/src/components/Chats/ChatsList.tsx @@ -75,7 +75,10 @@ export default function ChatsList() { // 각 채팅방에 대한 상대방의 프로필을 출력 return ( -
handleChatClick(chatRoom.chatRoomId)}> +
handleChatClick(chatRoom.chatRoomId)} + > (null); const [flexDirection, setFlexDirection] = useState('column'); + const { $isSentByMe, content, time, $isRead } = props; + // TextContainer의 flex-direction을 텍스트의 길이에 따라 결정 useEffect(() => { const checkTextWidth = () => { if (chatTextRef.current) { @@ -125,8 +127,6 @@ export default function ChatBubble(props: ChatBubbleProps) { return () => window.removeEventListener('resize', checkTextWidth); }, []); - const { $isSentByMe, content, time, $isRead } = props; - return ( diff --git a/src/components/Chatting/ChattingRoom.tsx b/src/components/Chatting/ChattingRoom.tsx index 5ad2e03..d215cf0 100644 --- a/src/components/Chatting/ChattingRoom.tsx +++ b/src/components/Chatting/ChattingRoom.tsx @@ -4,8 +4,8 @@ import { useSelector } from 'react-redux'; import { RootState } from '../../store'; import { Chats } from '../../types/interface'; import ChatBubble from './ChatBubble'; -import FormatTimeToAMPM from './FormatTimeToAMPM'; -import FormatDateToDMY from './FormatDateToDMY'; +import FormatTimeToAMPM from '../../utils/formatTimeToAMPM'; +import FormatDateToDMY from '../../utils/formatDateToDMY'; const ChattingPageContainer = styled.div` width: 23.4375rem; @@ -73,6 +73,7 @@ export default function ChattingRoom({ chatList }: Chats) { const todayDateStringDMY = getTodayDateStringDMY(); const isToday = chatDateDMY === todayDateStringDMY; // 현재 채팅 날짜가 오늘인지 확인 + const isSentByMe = chat.senderId === nowUser; return ( @@ -82,7 +83,7 @@ export default function ChattingRoom({ chatList }: Chats) { )} state.user.nowUser); - const userList = useSelector((state: RootState) => state.user.userList); + const chattings = useSelector((state: RootState) => state.chat.chattings); + const [chatRoom, setChatRoom] = useState(null); const dispatch = useDispatch(); const navigate = useNavigate(); - const handleChangeUser = () => { - // 현재 nowUser와 일치하는 사용자 객체를 찾기 - const currentIndex = userList.findIndex( - (user: User) => user.id === nowUser + useEffect(() => { + // chatRoomId를 사용하여 해당 채팅방 찾기 및 설정 + const foundChatRoom = chattings.find( + (chat) => chat.chatRoomId.toString() === chatRoomId ); - const nextIndex = currentIndex === 0 ? 1 : 0; - const nextUser = userList[nextIndex]; - // 여기서 nextUser.id를 전달. changeUser 액션이 사용자 ID를 기대한다고 가정 - dispatch(changeUser(nextUser.id)); + setChatRoom(foundChatRoom ?? null); + }, [chatRoomId, chattings]); // chattings 또는 chatRoomId 변경 시 effect 실행 + + const handleChangeUser = () => { + if (chatRoom && chatRoom.userList && chatRoom.userList.length > 0) { + const currentChatRoomUserList = chatRoom.userList; + const currentIndex = currentChatRoomUserList.findIndex( + (userId) => userId === nowUser + ); + const nextUserIndex = (currentIndex + 1) % currentChatRoomUserList.length; + const nextUserId = currentChatRoomUserList[nextUserIndex]; + + dispatch(changeUser(nextUserId)); + } }; return ( @@ -83,7 +96,9 @@ export default function TitleBar(props: TitleBarProps) { navigate('/contact-info')} + onClick={() => + navigate('/contact-info', { state: { userId: nowUser } }) + } /> {name} diff --git a/src/pages/ChattingPage.tsx b/src/pages/ChattingPage.tsx index f6fd260..a685c40 100644 --- a/src/pages/ChattingPage.tsx +++ b/src/pages/ChattingPage.tsx @@ -1,8 +1,8 @@ import { useState, useEffect } from 'react'; +import { useParams } from 'react-router-dom'; import { useSelector } from 'react-redux'; import { RootState } from '../store'; import { User } from '../types/interface'; -import { useParams } from 'react-router-dom'; import { Chats } from '../types/interface'; import TitleBar from '../components/Chatting/TitleBar'; import ChattingRoom from '../components/Chatting/ChattingRoom'; diff --git a/src/pages/ContactInfoPage.tsx b/src/pages/ContactInfoPage.tsx index 4b818c6..189803b 100644 --- a/src/pages/ContactInfoPage.tsx +++ b/src/pages/ContactInfoPage.tsx @@ -1,5 +1,5 @@ import { useState, useEffect } from 'react'; -import { useNavigate } from 'react-router-dom'; +import { useNavigate, useLocation } from 'react-router-dom'; import { useSelector } from 'react-redux'; import { RootState } from '../store'; import { User } from '../types/interface'; @@ -9,10 +9,12 @@ import ContactInfo from '../components/ContactInfo/ContactInfo'; import Left from '../assets/img/left.svg'; export default function ContactInfoPage() { + const location = useLocation(); + const navigate = useNavigate(); const nowUser = useSelector((state: RootState) => state.user.nowUser); const userList = useSelector((state: RootState) => state.user.userList); const [partner, setPartner] = useState(null); - const navigate = useNavigate(); + const { userId } = location.state || {}; // 전달받은 userId // 이전 페이지로 돌아가는 함수 const goBack = () => { @@ -20,9 +22,19 @@ export default function ContactInfoPage() { }; useEffect(() => { - const nextUser = userData.users.find((user) => user.id !== nowUser) ?? null; - setPartner(nextUser); - }, [nowUser, userList]); // nowUser 또는 userList가 변경될 때마다 effect를 실행 + console.log('전달받은 userId:', userId); // 전달받은 userId를 콘솔에 출력 + // 현재 사용자(nowUser)와 전달받은 userId가 동일한 경우 상대방 정보 찾기 + if (nowUser === userId) { + // userList에서 현재 사용자(nowUser)를 제외한 첫 번째 사용자를 상대방으로 설정 + // 실제 애플리케이션에서는 더 정교한 로직이 필요할 수 있음 + const partnerUser = userList.find((user) => user.id !== nowUser) ?? null; + setPartner(partnerUser); + } else { + // 전달받은 userId에 해당하는 사용자 정보 찾기 + const nextUser = userList.find((user) => user.id === userId) ?? null; + setPartner(nextUser); + } + }, [userId, userList, nowUser]); return ( <> diff --git a/src/components/Chatting/FormatDateToDMY.ts b/src/utils/formatDateToDMY.ts similarity index 86% rename from src/components/Chatting/FormatDateToDMY.ts rename to src/utils/formatDateToDMY.ts index cb98f9a..f120b9b 100644 --- a/src/components/Chatting/FormatDateToDMY.ts +++ b/src/utils/formatDateToDMY.ts @@ -1,5 +1,5 @@ // 채팅 시간을 "dd/mm/yy" 형식의 문자열로 변환하는 함수 -export default function FormatDateToDMY(dateString: Date | string) { +export default function formatDateToDMY(dateString: Date | string) { const date = new Date(dateString); const day = date.getDate(); const month = date.getMonth() + 1; // 월은 0부터 시작하므로 1을 더해줌 diff --git a/src/components/Chatting/FormatTimeToAMPM.ts b/src/utils/formatTimeToAMPM.ts similarity index 88% rename from src/components/Chatting/FormatTimeToAMPM.ts rename to src/utils/formatTimeToAMPM.ts index a95634d..f0d1a9f 100644 --- a/src/components/Chatting/FormatTimeToAMPM.ts +++ b/src/utils/formatTimeToAMPM.ts @@ -1,5 +1,5 @@ // 채팅 시간을 "09:41am" 형식의 문자열로 변환하는 함수 -export default function FormatTimeToAMPM(dateInput: Date | string): string { +export default function formatTimeToAMPM(dateInput: Date | string): string { const date = typeof dateInput === 'string' ? new Date(dateInput) : dateInput; let hours = date.getHours(); From f4722c5a7fa9339b65d1e1ce9958dca13eccd9fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Sun, 28 Apr 2024 05:48:32 +0900 Subject: [PATCH 43/46] =?UTF-8?q?feat:=20TitleBar=20=EC=9D=B4=EB=A6=84=20?= =?UTF-8?q?=EB=B3=80=ED=99=98=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/data/userData.json | 12 ++++++------ src/components/Chatting/TitleBar.tsx | 12 +++++++++++- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/assets/data/userData.json b/src/assets/data/userData.json index aa77954..d404369 100644 --- a/src/assets/data/userData.json +++ b/src/assets/data/userData.json @@ -14,37 +14,37 @@ }, { "id": 2, - "name": "Leah S.", + "name": "Leah Stimptson", "profileImg": "/img/LeahStimptson.svg", "isActive": true }, { "id": 3, - "name": "Colin B.", + "name": "Colin Bearman", "profileImg": "/img/ColinBearman.svg", "isActive": false }, { "id": 4, - "name": "Charile M.", + "name": "Charile Moore", "profileImg": "/img/CharileMoore.svg", "isActive": true }, { "id": 5, - "name": "Hailey S.", + "name": "Hailey Stephenson", "profileImg": "/img/HaileyStephenson.svg", "isActive": false }, { "id": 6, - "name": "Sena A.", + "name": "Sena Adams", "profileImg": "/img/SenaAdams.svg", "isActive": false }, { "id": 7, - "name": "Sienna H.", + "name": "Sienna Hwang", "profileImg": "/img/SiennaHwang.svg", "isActive": true }, diff --git a/src/components/Chatting/TitleBar.tsx b/src/components/Chatting/TitleBar.tsx index c9d7f79..e9049c2 100644 --- a/src/components/Chatting/TitleBar.tsx +++ b/src/components/Chatting/TitleBar.tsx @@ -72,6 +72,16 @@ export default function TitleBar(props: TitleBarProps) { setChatRoom(foundChatRoom ?? null); }, [chatRoomId, chattings]); // chattings 또는 chatRoomId 변경 시 effect 실행 + // 이름 변환 로직 + const getFormattedName = (name: string) => { + const parts = name.split(' '); + if (parts.length > 1) { + const lastInitial = parts.pop()!.charAt(0); // 띄어쓰기 다음 첫 글자 구하기. parts.pop()의 결과가 undefined가 아니라는 것을 명시하기 위해 !를 사용함 + return `${parts.join(' ')} ${lastInitial}.`; + } + return name; // 이름에 공백이 없는 경우 변환 없이 반환 + }; + const handleChangeUser = () => { if (chatRoom && chatRoom.userList && chatRoom.userList.length > 0) { const currentChatRoomUserList = chatRoom.userList; @@ -101,7 +111,7 @@ export default function TitleBar(props: TitleBarProps) { } /> - {name} + {getFormattedName(name)} {isActive ? ( online ) : ( From c6b6a1b838b790fc687666a7340eab585d77c5c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Sun, 28 Apr 2024 06:44:52 +0900 Subject: [PATCH 44/46] =?UTF-8?q?fix:=20status=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=EC=97=90=EC=84=9C=20=EB=B3=B8=EC=9D=B8=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=20=EB=B3=B4=EC=9D=B4=EA=B2=8C=EB=81=94=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/assets/data/initialChatData.json | 12 ++++++------ src/assets/img/my-profile.svg | 11 ----------- src/assets/img/status-add.svg | 4 ++++ src/components/Status/MyStatus.tsx | 21 ++++++++++++++++++--- src/pages/ContactInfoPage.tsx | 7 ++----- src/types/interface.ts | 1 + 6 files changed, 31 insertions(+), 25 deletions(-) delete mode 100644 src/assets/img/my-profile.svg create mode 100644 src/assets/img/status-add.svg diff --git a/src/assets/data/initialChatData.json b/src/assets/data/initialChatData.json index 014fdf4..c48830a 100644 --- a/src/assets/data/initialChatData.json +++ b/src/assets/data/initialChatData.json @@ -71,7 +71,7 @@ "senderId": 3, "content": "I just want you to know that if it is another choice I could change, yes I ...", "time": "2024-02-27T09:41:00", - "isRead": true + "isRead": false } ] }, @@ -97,7 +97,7 @@ "senderId": 5, "content": "Photo", "time": "2024-02-10T09:41:00", - "isRead": true + "isRead": false } ] }, @@ -108,9 +108,9 @@ { "id": 0, "senderId": 6, - "content": "You reacted 🥹 to “Now I arrived in Seoul! It’s insane. You have to see...", + "content": "You reacted 🥺 to “Now I arrived in Seoul! It’s insane. You have to see...", "time": "2024-01-25T09:41:00", - "isRead": true + "isRead": false } ] }, @@ -133,10 +133,10 @@ "chatList": [ { "id": 0, - "senderId": 0, + "senderId": 8, "content": "You reacted ❤️ to “Really honoured to be there with you, thank you for..", "time": "2024-01-09T09:41:00", - "isRead": true + "isRead": false } ] }, diff --git a/src/assets/img/my-profile.svg b/src/assets/img/my-profile.svg deleted file mode 100644 index 25ac3b0..0000000 --- a/src/assets/img/my-profile.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/src/assets/img/status-add.svg b/src/assets/img/status-add.svg new file mode 100644 index 0000000..a68b6e8 --- /dev/null +++ b/src/assets/img/status-add.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/components/Status/MyStatus.tsx b/src/components/Status/MyStatus.tsx index 6b1bcc4..18d4daa 100644 --- a/src/components/Status/MyStatus.tsx +++ b/src/components/Status/MyStatus.tsx @@ -1,6 +1,8 @@ import styled from 'styled-components'; import { useNavigate } from 'react-router-dom'; -import MyProfile from '../../assets/img/my-profile.svg'; +import { useSelector } from 'react-redux'; +import { RootState } from '../../store'; +import add from '../../assets/img/status-add.svg'; import camera from '../../assets/img/status-camera.svg'; import edit from '../../assets/img/status-edit.svg'; @@ -23,11 +25,20 @@ const MyStatusContainer = styled.div` const ProfileImg = styled.img` width: 3.625rem; height: 3.625rem; + border-radius: 3.625rem; position: absolute; left: 0.81rem; top: 0.56rem; `; +const AddImg = styled.img` + width: 1.25rem; + height: 1.25rem; + position: absolute; + left: 3.19rem; + top: 2.97rem; +`; + const MyStatusText = styled.div` color: #000; font-family: 'SF Pro Text'; @@ -83,12 +94,16 @@ const TipContainer = styled.div` export default function MyStatus() { const navigate = useNavigate(); + const nowUser = useSelector((state: RootState) => state.user.nowUser); // 현재 사용자 ID + const userList = useSelector((state: RootState) => state.user.userList); + const currentUser = userList.find((user) => user.id === nowUser); // 현재 사용자 정보 찾기 return ( - - My Status + + + {currentUser?.name} Give me magarita diff --git a/src/pages/ContactInfoPage.tsx b/src/pages/ContactInfoPage.tsx index 189803b..efd9be0 100644 --- a/src/pages/ContactInfoPage.tsx +++ b/src/pages/ContactInfoPage.tsx @@ -3,7 +3,6 @@ import { useNavigate, useLocation } from 'react-router-dom'; import { useSelector } from 'react-redux'; import { RootState } from '../store'; import { User } from '../types/interface'; -import userData from '../assets/data/userData.json'; import TopNavBar from '../components/TopNavBar/TopNavBar'; import ContactInfo from '../components/ContactInfo/ContactInfo'; import Left from '../assets/img/left.svg'; @@ -22,11 +21,9 @@ export default function ContactInfoPage() { }; useEffect(() => { - console.log('전달받은 userId:', userId); // 전달받은 userId를 콘솔에 출력 - // 현재 사용자(nowUser)와 전달받은 userId가 동일한 경우 상대방 정보 찾기 + // nowUser와 전달받은 userId가 동일한 경우 상대방 정보 찾기 if (nowUser === userId) { - // userList에서 현재 사용자(nowUser)를 제외한 첫 번째 사용자를 상대방으로 설정 - // 실제 애플리케이션에서는 더 정교한 로직이 필요할 수 있음 + // userList에서 nowUser를 제외한 첫 번째 사용자를 상대방으로 설정 const partnerUser = userList.find((user) => user.id !== nowUser) ?? null; setPartner(partnerUser); } else { diff --git a/src/types/interface.ts b/src/types/interface.ts index f81120d..8b7be0e 100644 --- a/src/types/interface.ts +++ b/src/types/interface.ts @@ -1,3 +1,4 @@ +// 재사용하는 경우 분리해놓음 export interface TitleBarProps { name: string; profileImg: string; From 08af216fb0b76d9a2309ee01ad5b4480be160788 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Fri, 3 May 2024 07:49:38 +0900 Subject: [PATCH 45/46] =?UTF-8?q?chore:=20=EC=9D=B4=EB=A6=84=20=EB=8B=A8?= =?UTF-8?q?=EC=B6=95=EB=90=98=EB=8A=94=20=EB=B6=80=EB=B6=84=20contact-info?= =?UTF-8?q?=20topNavBar=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Chats/Chat.tsx | 4 ++-- src/components/Chats/ChatsList.tsx | 3 ++- src/components/Chatting/ChatInput.tsx | 2 +- src/components/Chatting/TitleBar.tsx | 22 ++++++--------------- src/components/ContactInfo/ContactInfo.tsx | 4 ++-- src/components/TopNavBar/TopNavBar.tsx | 5 ++--- src/features/userSlice.ts | 4 ++-- src/pages/ChattingPage.tsx | 7 ++++--- src/pages/ContactInfoPage.tsx | 23 ++++++++-------------- src/types/interface.ts | 10 ++-------- src/utils/formatName.ts | 9 +++++++++ 11 files changed, 40 insertions(+), 53 deletions(-) create mode 100644 src/utils/formatName.ts diff --git a/src/components/Chats/Chat.tsx b/src/components/Chats/Chat.tsx index 8544841..c450cea 100644 --- a/src/components/Chats/Chat.tsx +++ b/src/components/Chats/Chat.tsx @@ -1,5 +1,5 @@ import styled from 'styled-components'; -import { TitleBarProps } from '../../types/interface'; +import { UserProps } from '../../types/interface'; import { ChatProps } from '../../types/interface'; import FormatDateToDMY from '../../utils/formatDateToDMY'; import checkmark from '../../assets/img/checkmark.svg'; @@ -87,7 +87,7 @@ const ChatText = styled.div<{ $isRead: boolean }>` `; // 두 타입을 결합하여 새로운 타입 정의 -type CombinedProps = TitleBarProps & ChatProps; +type CombinedProps = UserProps & ChatProps; export default function Chat(props: CombinedProps) { const { name, profileImg, lastChatTime, lastChatContent, $isRead } = props; diff --git a/src/components/Chats/ChatsList.tsx b/src/components/Chats/ChatsList.tsx index e1984ce..211ed1f 100644 --- a/src/components/Chats/ChatsList.tsx +++ b/src/components/Chats/ChatsList.tsx @@ -66,7 +66,8 @@ export default function ChatsList() { // 현재 채팅방에서 현재 사용자를 제외한 상대방의 정보를 찾기 const partner = userList.find( - (user) => chatRoom.userList.includes(user.id) && user.id !== nowUser + (user) => + chatRoom.userList.includes(user.id!) && user.id !== nowUser ) ?? null; // 마지막 채팅 정보를 추출 diff --git a/src/components/Chatting/ChatInput.tsx b/src/components/Chatting/ChatInput.tsx index 1c593d1..63908dd 100644 --- a/src/components/Chatting/ChatInput.tsx +++ b/src/components/Chatting/ChatInput.tsx @@ -52,7 +52,7 @@ const Input = styled.input` `; export default function ChatInput({ chatRoomId }: Chats) { - const nowUser = useSelector((state: RootState) => state.user.nowUser); // 현재 사용자 상태 가져오기 + const nowUser = useSelector((state: RootState) => state.user.nowUser); // 현재 사용자 ID 가져오기 const [value, setValue] = useState(''); const dispatch = useDispatch(); diff --git a/src/components/Chatting/TitleBar.tsx b/src/components/Chatting/TitleBar.tsx index e9049c2..ac06cf6 100644 --- a/src/components/Chatting/TitleBar.tsx +++ b/src/components/Chatting/TitleBar.tsx @@ -4,8 +4,7 @@ import { useNavigate, useParams } from 'react-router-dom'; import { useSelector, useDispatch } from 'react-redux'; import { changeUser } from '../../features/userSlice'; import { RootState } from '../../store'; -import { TitleBarProps } from '../../types/interface'; -import { User } from '../../types/interface'; +import { UserProps } from '../../types/interface'; import { Chats } from '../../types/interface'; import TopNavBar from '../TopNavBar/TopNavBar'; import Left from '../../assets/img/left.svg'; @@ -55,7 +54,8 @@ const OnlineText = styled.div` letter-spacing: -0.0015rem; `; -export default function TitleBar(props: TitleBarProps) { +export default function TitleBar(props: UserProps & { userId: number }) { + // 파트너 유저 아이디도 전달 const { name, profileImg, isActive } = props; const { chatRoomId } = useParams(); const nowUser = useSelector((state: RootState) => state.user.nowUser); @@ -72,16 +72,6 @@ export default function TitleBar(props: TitleBarProps) { setChatRoom(foundChatRoom ?? null); }, [chatRoomId, chattings]); // chattings 또는 chatRoomId 변경 시 effect 실행 - // 이름 변환 로직 - const getFormattedName = (name: string) => { - const parts = name.split(' '); - if (parts.length > 1) { - const lastInitial = parts.pop()!.charAt(0); // 띄어쓰기 다음 첫 글자 구하기. parts.pop()의 결과가 undefined가 아니라는 것을 명시하기 위해 !를 사용함 - return `${parts.join(' ')} ${lastInitial}.`; - } - return name; // 이름에 공백이 없는 경우 변환 없이 반환 - }; - const handleChangeUser = () => { if (chatRoom && chatRoom.userList && chatRoom.userList.length > 0) { const currentChatRoomUserList = chatRoom.userList; @@ -106,12 +96,12 @@ export default function TitleBar(props: TitleBarProps) { - navigate('/contact-info', { state: { userId: nowUser } }) + onClick={ + () => navigate('/contact-info', { state: { userId: props.userId } }) // 클릭된 이미지에 해당하는 유저 아이디 넘김 } /> - {getFormattedName(name)} + {name} {isActive ? ( online ) : ( diff --git a/src/components/ContactInfo/ContactInfo.tsx b/src/components/ContactInfo/ContactInfo.tsx index 0d6085f..511d463 100644 --- a/src/components/ContactInfo/ContactInfo.tsx +++ b/src/components/ContactInfo/ContactInfo.tsx @@ -1,5 +1,5 @@ import styled from 'styled-components'; -import { TitleBarProps } from '../../types/interface'; +import { UserProps } from '../../types/interface'; import Instagram from '../../assets/img/contact-instagram.svg'; import Github from '../../assets/img/contact-github.svg'; import Message from '../../assets/img/contact-message.svg'; @@ -148,7 +148,7 @@ const RightArrowImg = styled.img` margin-left: 0.75rem; `; -export default function ContactInfo(props: TitleBarProps) { +export default function ContactInfo(props: UserProps) { const { name, profileImg } = props; return ( diff --git a/src/components/TopNavBar/TopNavBar.tsx b/src/components/TopNavBar/TopNavBar.tsx index dd15772..9113041 100644 --- a/src/components/TopNavBar/TopNavBar.tsx +++ b/src/components/TopNavBar/TopNavBar.tsx @@ -78,7 +78,7 @@ interface TopNavBarProps { title?: string; leftTextOnClick?: () => void; // 클릭을 통해 이전 페이지로 돌아가기 위함 $noBorderBottom?: boolean; // edit-contact page에서는 테두리 없애기 위함 - $isEditPage?: boolean; // edit-contact page에서 오른쪽 텍스트가 회색 + $isEditPage?: boolean; // edit-contact page에서 오른쪽 텍스트를 회색으로 지정하기 위함 } export default function TopNavBar(props: TopNavBarProps) { @@ -99,9 +99,8 @@ export default function TopNavBar(props: TopNavBarProps) { {leftImgSrc && } {leftText} - {children} {/* 여기에 children을 렌더링 */} + {children} {title && {title}}{' '} - {/* 여기에 TitleText를 렌더링 */} {rightImgSrc && } {rightText && ( diff --git a/src/features/userSlice.ts b/src/features/userSlice.ts index f42b0f7..3ada918 100644 --- a/src/features/userSlice.ts +++ b/src/features/userSlice.ts @@ -1,10 +1,10 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { User } from '../types/interface'; +import { UserProps } from '../types/interface'; import UserData from '../assets/data/userData.json'; interface UserState { nowUser: number; - userList: User[]; + userList: UserProps[]; } const initialState: UserState = { diff --git a/src/pages/ChattingPage.tsx b/src/pages/ChattingPage.tsx index a685c40..aa973cb 100644 --- a/src/pages/ChattingPage.tsx +++ b/src/pages/ChattingPage.tsx @@ -2,7 +2,7 @@ import { useState, useEffect } from 'react'; import { useParams } from 'react-router-dom'; import { useSelector } from 'react-redux'; import { RootState } from '../store'; -import { User } from '../types/interface'; +import { UserProps } from '../types/interface'; import { Chats } from '../types/interface'; import TitleBar from '../components/Chatting/TitleBar'; import ChattingRoom from '../components/Chatting/ChattingRoom'; @@ -13,7 +13,7 @@ export default function ChattingPage() { const nowUser = useSelector((state: RootState) => state.user.nowUser); const userList = useSelector((state: RootState) => state.user.userList); const chattings = useSelector((state: RootState) => state.chat.chattings); - const [partner, setPartner] = useState(null); + const [partner, setPartner] = useState(null); const [chatRoom, setChatRoom] = useState(null); useEffect(() => { @@ -27,7 +27,7 @@ export default function ChattingPage() { const nextUser = userList.find( (user) => - foundChatRoom.userList.includes(user.id) && user.id !== nowUser + foundChatRoom.userList.includes(user.id!) && user.id !== nowUser ) ?? null; setPartner(nextUser); } else { @@ -42,6 +42,7 @@ export default function ChattingPage() { name={partner.name} profileImg={partner.profileImg} isActive={partner.isActive} + userId={partner.id!} // '!'를 사용하여 'undefined'가 아니라고 단언 /> state.user.nowUser); const userList = useSelector((state: RootState) => state.user.userList); - const [partner, setPartner] = useState(null); + const [partner, setPartner] = useState(null); const { userId } = location.state || {}; // 전달받은 userId // 이전 페이지로 돌아가는 함수 @@ -21,17 +21,10 @@ export default function ContactInfoPage() { }; useEffect(() => { - // nowUser와 전달받은 userId가 동일한 경우 상대방 정보 찾기 - if (nowUser === userId) { - // userList에서 nowUser를 제외한 첫 번째 사용자를 상대방으로 설정 - const partnerUser = userList.find((user) => user.id !== nowUser) ?? null; - setPartner(partnerUser); - } else { - // 전달받은 userId에 해당하는 사용자 정보 찾기 - const nextUser = userList.find((user) => user.id === userId) ?? null; - setPartner(nextUser); - } - }, [userId, userList, nowUser]); + // 전달받은 userId에 해당하는 사용자 정보 찾기 + const nextUser = userList.find((user) => user.id === userId) ?? null; + setPartner(nextUser); + }, [userId, userList]); return ( <> @@ -39,7 +32,7 @@ export default function ContactInfoPage() { <> 1) { + const lastInitial = parts.pop()!.charAt(0); // 띄어쓰기 다음 첫 글자 구하기. parts.pop()의 결과가 undefined가 아니라는 것을 명시하기 위해 !를 사용함 + return `${parts.join(' ')} ${lastInitial}.`; + } + return name; // 이름에 공백이 없는 경우 변환 없이 반환 +} From 2371c875f65cf3a6b6a31555120b9ccfc8d831cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Fri, 3 May 2024 09:00:28 +0900 Subject: [PATCH 46/46] =?UTF-8?q?docs:=20readme=20=EB=AC=B8=EC=84=9C=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 48 ++++++++++++++++++++++-------------------------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 2949d7b..031a4e4 100644 --- a/README.md +++ b/README.md @@ -4,46 +4,42 @@ - [배포링크](https://react-messenger-19th-tawny.vercel.app/) -## 느낀 점 +## 피그마 링크 -이번 과제를 하면서 타입스크립트를 처음 다뤄 어렵게 느껴졌습니다. 필수 기능 위주로 구현하고 Redux Toolkit으로 상태 관리를 해보았습니다. 4주차 과제 시작하기에 앞서 리팩토링을 해서 더 깔끔한 코드를 작성하고 싶습니다. - -## Key Questions +- [피그마 링크](https://www.figma.com/file/W7XzAbakNIOQxwiza0wLIo/CEOS-Messenger-Redesign?type=design&node-id=5-3617&mode=design&t=ic7kPkFW1UIT5FiD-11) -**1. JavaScript를 사용할때에 비해 TypeScript를 사용할 때의 장점은 무엇인가요?** +![image](https://github.com/CEOS-Developers/react-messenger-19th/assets/126255206/c1bbe729-7ada-4da0-84fb-4aced07614da) -타입스크립트는 개발자가 변수, 함수 매개변수, 반환 값 등에 대해 명시적으로 타입을 선언할 수 있어 코드의 의도를 명확하게 전달할 수 있습니다. 타입스크립트를 처음 써봐서 아직 미숙한 부분이 많았는데 익숙해지면 자바스크립트보다 개발의 효율성과 유지 보수 측면에서 더 나을 것 같습니다. 특히 타입스크립트는 다른 파일에서 작업할 때 자동 완성 기능을 통해 props 종류를 보여줘서 해당 props의 이름과 타입을 확인할 수 있습니다. 또한 타입이 맞지 않는 경우에 오류로 확인할 수 있어 문제를 해결하는 데 편리하다고 느껴졌습니다. +## 디자이너 분께 받은 QA -**2. 디자이너로부터 전달받은 피그마 링크 혹은, 피그마 캡처본** +- 입력창 폼 안에 양옆으로 10px 패딩 추가 +- 한줄짜리 채팅 버블 추가 +- 챗을 보내면 생성되는 ‘날짜 인디케이터’의 상하너비 수정 +- contact-info 페이지에서 이름 축약 -- [피그마 링크](https://www.figma.com/file/W7XzAbakNIOQxwiza0wLIo/CEOS-Messenger-Redesign?type=design&node-id=5-3617&mode=design&t=ic7kPkFW1UIT5FiD-11) +## Key Questions -![image](https://github.com/CEOS-Developers/react-messenger-19th/assets/126255206/c1bbe729-7ada-4da0-84fb-4aced07614da) +**1. Routing이란?** -**3. 컴포넌트를 분리한 기준은 무엇인가요?** +Routing이란 경로를 선택하면 웹의 URL을 변경하여 사용자가 선택한 해당 경로로 이동하게 해주며 새로 페이지를 로드하지 않고도 변경 가능합니다. React 자체에는 Routing 기능이 내장되어 있지 않습니다. 따라서 React 애플리케이션에서 Routing을 구현하기 위해 react-router-dom과 같은 외부 라이브러리를 사용합니다. react-router-dom은 React에서 SPA를 구현할 때 널리 사용되는 Routing 라이브러리입니다. -src 폴더 안에 크게 assets, components, features, pages, style로 나눠 assets 폴더 안에는 data와 img 폴더로 data 폴더 안에는 userData.json과 chatData.json을 포함시켰습니다. img 폴더 안에는 svg 이미지들을 저장했습니다. 4주차 과제 때 채팅방 이외에 추가로 구현할 것을 염두에 두고 components 폴더 안에 Chat 폴더를 생성해 작업했습니다. features 폴더 안에 Redux Toolkit 관련하여 chatSlice.ts 파일과 userSlice.ts 파일을 저장했습니다. pages 폴더 안에 ChattingPage.tsx 파일을 생성하여 채팅방 페이지를 구현했습니다. +이번 과제를 진행하며 react router에서 제공하는 여러 hook을 사용하였습니다. 가장 기본 경로를 채팅 목록 chats page로 지정하였고 이외에도 다른 페이지들은 각각의 URL에 맞는 컴포넌트를 정의하였습니다. URL 이동을 해야할 때에는 useNavigate을 사용해서 경로 이동을 하였습니다. useParams를 사용해서 url에서 채팅방 ID를 추출하기도 하고 useLocation을 사용하여 이전 페이지로부터 전달받은 userId 정보를 컴포넌트 내에서 사용할 수 있게 하기도 하였습니다. -**4. 디자인 시스템을 적용하면서 느낀 점은 무엇인가요?** +**2. SPA란?** -디자이너 분께서 그룹화를 꼼꼼하게 해주셔서 이를 바탕으로 고민 없이 스타일을 지정해줄 수 있어 편리하다고 느꼈습니다. -지난 1,2주차 과제에서는 스스로 디자인까지 해야했는데 이번주 프론트-디자인 협동 과제를 수행하며 디자인을 신경쓰지 않고 피그마 그대로 구현하니 편리했습니다. 기능을 구현하며 깔끔한 디자인의 중요성도 깨닫게 되었습니다. -또한 코드를 작성하고 즉각적으로 시각적 결과물을 확인하며 피그마 디자인과 맞춰가는 재미도 느낄 수 있었습니다. +SPA는 Single Page Application의 약자로 하나의 페이지로 이루어진 웹 애플리케이션을 말합니다. 새로 페이지를 로드하지 않고 필요한 부분만 수정합니다. SPA는 클라이언트 사이드 렌더링(Client Side Rendering, CSR) 방식을 취하고 있습니다. 이는 빠른 페이지 로딩과 부드러운 페이지 전환, 그리고 사용자 화면에 높은 반응성을 유지할 수 있도록 합니다. 하지만 검색 엔진 최적화에 제한이 있으며 첫 페이지 로딩 속도가 느리다는 단점이 있습니다. -**5. 디자이너와 소통하며 느낀점은 무엇인가요?** +**3. 상태관리란?** -디자이너 분께 질문이 있어 피그마에 코멘트를 남기거나 연락을 드렸을 때 항상 빠르게 답장을 주셔서 편하게 작업할 수 있었습니다. 추가로 요구를 드리더라도 이에 대해 바로바로 피드백을 주셔서 빠른 소통의 중요성을 느낄 수 있었습니다. -저도 협업을 진행할 때 팀원들과 빠르고 정확한 소통을 하려고 노력할 것입니다.! +리액트의 가장 큰 특징은 부모 요소에서 자식 요소로 데이터를 전달한다는 것입니다. 컴포넌트 간에 상태를 공유하려면 이처럼 상위 컴포넌트에서 하위 컴포넌트로 porp을 넘겨 전달합니다. 이런 prop 전달 과정이 복잡해지면 계속해서 추적하고 관리하기 어렵기 때문에 전역 상태 관리를 합니다. -## 미션 목표 +전역 상태 관리는 여러 방법이 있는데 그중에서도 Context API, Redux, React Query가 많이 쓰입니다. +제가 이번 과제에서 사용한 redux toolkit에 대해 간단히 설명을 해보면 . . . -- TypeScript를 사용해봅시다. -- useState로 컴포넌트의 상태를 관리합니다. -- useEffect와 useRef의 사용법을 이해합니다. -- styled-components를 통한 CSS-in-JS 및 CSS Preprocessor의 사용법에 익숙해집니다. +리액트는 컴포넌트 자신이 개별적으로 상태관리를 하는데 redux를 쓰면 전용 장소 store에서 상태를 관리하고, 리액트 컴포넌트는 그걸 보여주기만 하는 용도로 쓰입니다. 리액트 컴포넌트가 store에 접근하고 싶으면 action을 발행해야합니다. 여기서 reducer는 이전 상태와 action을 합쳐서 새로운 상태를 state을 만드는 조작을 말합니다. redux 사용시 저장소 구성의 복잡성, 많은 패키지 필요성, 한 작업 시 필요한 수 많은 코드양 등의 문제점을 보완하여 redux toolkit은 redux를 더 사용하기 쉽게 만들었습니다. 여기서 createSlice는 action과 reducer를 전부 가진 함수로 기존에는 액션생성함수와 액션 타입을 선언해 사용했다면 createSlice는 액션을 선언하고 해당 액션이 dispatch되면 바로 해당 액션을 처리합니다. ## 링크 및 참고자료 -- [이미지 경로 설정](https://whales.tistory.com/95) -- [React의 Hooks 완벽 정복하기](https://velog.io/@velopert/react-hooks#1-usestate) -- [useEffect 완벽 가이드](https://overreacted.io/ko/a-complete-guide-to-useeffect/) +- [React에서 상태 관리하기](https://mingule.tistory.com/74) +- [React - Router, useLocation 를 통해 상세페이지 구현](https://bmy1320.tistory.com/entry/React-Router-useLocation-%EB%A5%BC-%ED%86%B5%ED%95%B4-%EC%83%81%EC%84%B8%ED%8E%98%EC%9D%B4%EC%A7%80-%EA%B5%AC%ED%98%84) +- [SPA란](https://www.elancer.co.kr/blog/view?seq=214)