From 742a222f1b2bf2e45f7c11599e74f686464b7753 Mon Sep 17 00:00:00 2001 From: Jiseok Date: Thu, 28 Sep 2023 11:58:06 +0900 Subject: [PATCH 01/32] =?UTF-8?q?[feat]=20=EC=B4=88=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 72 +- package-lock.json | 874 ++++++++++-------- package.json | 11 +- src/{App.test.js => App.test.tsx} | 1 + src/{App.js => App.tsx} | 3 +- src/assets/images/Arrow-Left.svg | 3 + src/assets/images/BE.svg | 9 + src/assets/images/Camera.svg | 5 + src/assets/images/Chat-black.svg | 3 + src/assets/images/Chat.svg | 3 + src/assets/images/Friends-black.svg | 6 + src/assets/images/Friends.svg | 6 + src/assets/images/Gallery.svg | 5 + src/assets/images/Github.svg | 9 + src/assets/images/Instagram.svg | 9 + src/assets/images/Link.svg | 5 + src/assets/images/Magnifer.svg | 11 + src/assets/images/Microphone.svg | 6 + src/assets/images/More-black.svg | 5 + src/assets/images/More-white.svg | 5 + src/assets/images/Phone.svg | 3 + src/assets/images/Profile-Icon.svg | 9 + src/assets/images/Safari-Black.svg | 3 + src/assets/images/Safari.svg | 3 + src/assets/images/Sticker-Smile.svg | 6 + src/assets/images/Videocamera.svg | 4 + src/assets/images/Write-pen.svg | 4 + src/{index.js => index.tsx} | 4 +- src/react-app-env.d.ts | 1 + ...{reportWebVitals.js => reportWebVitals.ts} | 4 +- src/{setupTests.js => setupTests.ts} | 0 src/styles/GlobalStyle.tsx | 8 + tsconfig.json | 26 + 33 files changed, 670 insertions(+), 456 deletions(-) rename src/{App.test.js => App.test.tsx} (90%) rename src/{App.js => App.tsx} (84%) create mode 100644 src/assets/images/Arrow-Left.svg create mode 100644 src/assets/images/BE.svg create mode 100644 src/assets/images/Camera.svg create mode 100644 src/assets/images/Chat-black.svg create mode 100644 src/assets/images/Chat.svg create mode 100644 src/assets/images/Friends-black.svg create mode 100644 src/assets/images/Friends.svg create mode 100644 src/assets/images/Gallery.svg create mode 100644 src/assets/images/Github.svg create mode 100644 src/assets/images/Instagram.svg create mode 100644 src/assets/images/Link.svg create mode 100644 src/assets/images/Magnifer.svg create mode 100644 src/assets/images/Microphone.svg create mode 100644 src/assets/images/More-black.svg create mode 100644 src/assets/images/More-white.svg create mode 100644 src/assets/images/Phone.svg create mode 100644 src/assets/images/Profile-Icon.svg create mode 100644 src/assets/images/Safari-Black.svg create mode 100644 src/assets/images/Safari.svg create mode 100644 src/assets/images/Sticker-Smile.svg create mode 100644 src/assets/images/Videocamera.svg create mode 100644 src/assets/images/Write-pen.svg rename src/{index.js => index.tsx} (85%) create mode 100644 src/react-app-env.d.ts rename src/{reportWebVitals.js => reportWebVitals.ts} (75%) rename src/{setupTests.js => setupTests.ts} (100%) create mode 100644 src/styles/GlobalStyle.tsx create mode 100644 tsconfig.json diff --git a/README.md b/README.md index 6ab8e98..b87cb00 100644 --- a/README.md +++ b/README.md @@ -1,66 +1,46 @@ -# 서론 +# Getting Started with Create React App -안녕하세요 🙌🏻 18기 프론트 운영진 김문기입니다. 이번 미션에서는 드디어 투두리스트에서 벗어나 새로운 프로젝트인 **messenger** 만들기를 진행합니다. +This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). -이번주는 특별히 **디자이너와의 협업**으로 진행되는 미션입니다. 디자이너분께서 열심히 리디자인 한 메신저 프로젝트를 여러분들께서 구현해주시면 됩니다. +## Available Scripts -동시에, 이번주부터는 새로 **TypeScript**를 적용해보려고 합니다. +In the project directory, you can run: -프로젝트의 규모가 커지게 될 수록, 컴포넌트가 가지는 props의 종류 또한 다양해지게 됩니다. 무지성 코딩을 하다보면 가끔 이 props의 구성과 이름, 어떤 타입이 들어가야 하는지 헷갈리기 마련이죠. 보통 그럴 때 다시 컴포넌트 정의 부분으로 돌아가 props의 구성을 보고 오곤 합니다. +### `npm start` -하지만 이럴 때, typescript를 적용한다면 컴포넌트의 구성과 그 이름, 심지어 타입까지 한번에 자동완성으로 편리하게 관리할 수 있고, 생산성이 증대되겠죠. +Runs the app in the development mode.\ +Open [http://localhost:3000](http://localhost:3000) to view it in the browser. -또한, **React Hooks**에 조금 더 익숙해지는 것을 목표로 합니다. 여러 Hook들이 있지만 그 중에서도 `useState`, `useEffect`, `useRef`를 중점적으로 사용해 보는 미션인데요, React를 사용하면서 굉장히 자주 쓰이는 Hook들이기 때문에 이 부분을 집중적으로 공부해 보아요! +The page will reload if you make edits.\ +You will also see any lint errors in the console. -그럼 이번 미션도 파이팅입니다!!🎉 +### `npm test` -# 미션 +Launches the test runner in the interactive watch mode.\ +See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. -## Key Questions +### `npm run build` -- JavaScript를 사용할때에 비해 TypeScript를 사용할 때의 장점은 무엇인가요? -- 디자이너로부터 전달받은 피그마 링크 혹은, 피그마 캡처본 -- 컴포넌트를 분리한 기준은 무엇인가요? -- 디자인 시스템을 적용하면서 느낀 점은 무엇인가요? -- 디자이너와 소통하며 느낀점은 무엇인가요? +Builds the app for production to the `build` folder.\ +It correctly bundles React in production mode and optimizes the build for the best performance. -## 미션 목표 +The build is minified and the filenames include the hashes.\ +Your app is ready to be deployed! -- TypeScript를 사용해봅시다. -- useState로 컴포넌트의 상태를 관리합니다. -- useEffect와 useRef의 사용법을 이해합니다. -- styled-components를 통한 CSS-in-JS 및 CSS Preprocessor의 사용법에 익숙해집니다. +See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. -## 기한 +### `npm run eject` -2023년 9월 29일 금요일 +**Note: this is a one-way operation. Once you `eject`, you can’t go back!** -## 필수 구현 기능 +If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. -- 피그마를 보고 [결과화면](https://3th-fb-messenger.netlify.app)과 같이 구현합니다. -- 디자인 시스템을 구축합니다. -- 채팅방 상단의 프로필을 클릭하면 사용자를 변경할 수 있습니다. -- 메세지를 보내면 채팅방 하단으로 스크롤을 이동시킵니다. -- 메세지에 유저 정보(프로필 사진, 이름)를 표시합니다. -- user와 message 데이터를 json 파일에 저장합니다. -- UI는 **반응형을 제외**하고 피그마파일을 따라서 진행합니다. +Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. -### 추가 구현 기능 +You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. -- 더블 클릭 하면 감정표현을 추가합니다. -- 그 외 추가하고 싶은 기능이 있다면 마음껏 추가해 주세요! +## Learn More -참고로 이번 과제는 다음주까지 이어지는 과제이므로 **확장성**을 충분히 고려해 주세요. 참고로 **4주차 과제에서는 유저 및 기능 추가와 Routing을** 진행합니다. 이를 위해 [recoil](https://recoiljs.org/ko/)이나 [redux](https://ko.redux.js.org/introduction/getting-started/)를 이용한 상태관리를 미리 해보시는 것을 추천합니다!! 모두 공식문서 많이 읽어보시고 자신만의 상태관리 조합도 찾아보면 재밌을 거에요 XD +You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). -## 링크 및 참고자료 - -- [React docs - Hook](https://ko.reactjs.org/docs/hooks-intro.html) -- [React의 Hooks 완벽 정복하기](https://velog.io/@velopert/react-hooks#1-usestate) -- [useEffect 완벽 가이드](https://overreacted.io/ko/a-complete-guide-to-useeffect/) -- [코딩 컨벤션](https://ui.toast.com/fe-guide/ko_CODING-CONVENTION) -- [타입스크립트 핸드북](https://joshua1988.github.io/ts/intro.html) -- [리액트 프로젝트에서 타입스크립트 사용하기 (시리즈)](https://velog.io/@velopert/series/react-with-typescript) -- [디자인 시스템 구축기](https://yozm.wishket.com/magazine/detail/1830/) -- [[영상] : 컴포넌트에 대한 이해](https://www.youtube.com/watch?v=21eiJc90ggo) -- [Styled Component로 디자인 시스템 구축하기](https://zaat.dev/blog/building-a-design-system-in-react-with-styled-components/) -- [ts 절대경로 설정하기](https://tesseractjh.tistory.com/232) +To learn React, check out the [React documentation](https://reactjs.org/). diff --git a/package-lock.json b/package-lock.json index 82a715f..540f5a8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,20 +1,29 @@ { - "name": "react-messenger-18th", + "name": "my-app", "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "react-messenger-18th", + "name": "my-app", "version": "0.1.0", "dependencies": { "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "@types/jest": "^27.5.2", + "@types/node": "^16.18.54", + "@types/react": "^18.2.23", + "@types/react-dom": "^18.2.8", "react": "^18.2.0", "react-dom": "^18.2.0", "react-scripts": "5.0.1", + "styled-reset": "^4.5.1", + "typescript": "^4.9.5", "web-vitals": "^2.1.4" + }, + "devDependencies": { + "@types/styled-components": "^5.1.28" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -53,6 +62,84 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/cli": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.23.0.tgz", + "integrity": "sha512-17E1oSkGk2IwNILM4jtfAvgjt+ohmpfBky8aLerUfYZhiPNg7ca+CRCxZn8QDxwNhV/upsc2VHBCqGFIR+iBfA==", + "peer": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.17", + "commander": "^4.0.1", + "convert-source-map": "^2.0.0", + "fs-readdir-recursive": "^1.1.0", + "glob": "^7.2.0", + "make-dir": "^2.1.0", + "slash": "^2.0.0" + }, + "bin": { + "babel": "bin/babel.js", + "babel-external-helpers": "bin/babel-external-helpers.js" + }, + "engines": { + "node": ">=6.9.0" + }, + "optionalDependencies": { + "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.3", + "chokidar": "^3.4.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/cli/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "peer": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@babel/cli/node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "peer": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/cli/node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/cli/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "peer": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/@babel/cli/node_modules/slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "peer": true, + "engines": { + "node": ">=6" + } + }, "node_modules/@babel/code-frame": { "version": "7.22.13", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", @@ -74,21 +161,21 @@ } }, "node_modules/@babel/core": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.20.tgz", - "integrity": "sha512-Y6jd1ahLubuYweD/zJH+vvOY141v4f9igNQAQ+MBgq9JlHS2iTsZKn1aMsb3vGccZsXI16VzTBw52Xx0DWmtnA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.0.tgz", + "integrity": "sha512-97z/ju/Jy1rZmDxybphrBuI+jtJjFVoz7Mr9yUQVVVi+DNZE333uFQeMOqcCIy1x3WYBIbWftUSLmbNXNT7qFQ==", "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.22.15", + "@babel/generator": "^7.23.0", "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-module-transforms": "^7.22.20", - "@babel/helpers": "^7.22.15", - "@babel/parser": "^7.22.16", + "@babel/helper-module-transforms": "^7.23.0", + "@babel/helpers": "^7.23.0", + "@babel/parser": "^7.23.0", "@babel/template": "^7.22.15", - "@babel/traverse": "^7.22.20", - "@babel/types": "^7.22.19", - "convert-source-map": "^1.7.0", + "@babel/traverse": "^7.23.0", + "@babel/types": "^7.23.0", + "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", @@ -144,11 +231,11 @@ } }, "node_modules/@babel/generator": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.15.tgz", - "integrity": "sha512-Zu9oWARBqeVOW0dZOjXc3JObrzuqothQ3y/n1kUtrjCoCPLkXUwMvOo/F/TCfoHMbWIFlWwpZtkZVb9ga4U2pA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dependencies": { - "@babel/types": "^7.22.15", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -280,12 +367,12 @@ } }, "node_modules/@babel/helper-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", - "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dependencies": { - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" @@ -303,11 +390,11 @@ } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.15.tgz", - "integrity": "sha512-qLNsZbgrNh0fDQBCPocSL8guki1hcPvltGDv/NxvUoABwFq7GkKSu1nRXeJkVZc+wJvne2E0RKQz+2SQrz6eAA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", + "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", "dependencies": { - "@babel/types": "^7.22.15" + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" @@ -325,9 +412,9 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.20.tgz", - "integrity": "sha512-dLT7JVWIUUxKOs1UnJUBR3S70YK+pKX6AbJgB2vMIvEkZkrfJDbYDJesnPshtKV4LhDOR3Oc5YULeDizRek+5A==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz", + "integrity": "sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==", "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-module-imports": "^7.22.15", @@ -464,13 +551,13 @@ } }, "node_modules/@babel/helpers": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.15.tgz", - "integrity": "sha512-7pAjK0aSdxOwR+CcYAqgWOGy5dcfvzsTIfFTb2odQqW47MDfv14UaJDY6eng8ylM2EaeKXdxaSWESbkmaQHTmw==", + "version": "7.23.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.1.tgz", + "integrity": "sha512-chNpneuK18yW5Oxsr+t553UZzzAs3aZnFm4bxhebsNTeshrC95yA7l5yl7GBAG+JG1rF0F7zzD2EixK9mWSDoA==", "dependencies": { "@babel/template": "^7.22.15", - "@babel/traverse": "^7.22.15", - "@babel/types": "^7.22.15" + "@babel/traverse": "^7.23.0", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" @@ -490,9 +577,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.22.16", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.16.tgz", - "integrity": "sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "bin": { "parser": "bin/babel-parser.js" }, @@ -530,6 +617,21 @@ "@babel/core": "^7.13.0" } }, + "node_modules/@babel/plugin-external-helpers": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-external-helpers/-/plugin-external-helpers-7.22.5.tgz", + "integrity": "sha512-ngnNEWxmykPk82mH4ajZT0qTztr3Je6hrMuKAslZVM8G1YZTENJSYwrIGtt6KOtznug3exmAtF4so/nPqJuA4A==", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-proposal-class-properties": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", @@ -547,13 +649,13 @@ } }, "node_modules/@babel/plugin-proposal-decorators": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.22.15.tgz", - "integrity": "sha512-kc0VvbbUyKelvzcKOSyQUSVVXS5pT3UhRB0e3c9An86MvLqs+gx0dN4asllrDluqSa3m9YyooXKGOFVomnyFkg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.23.0.tgz", + "integrity": "sha512-kYsT+f5ARWF6AdFmqoEEp+hpqxEB8vGmRWfw2aj78M2vTwS2uHW91EF58iFm1Z9U8Y/RrLu2XKJn46P9ca1b0w==", "dependencies": { "@babel/helper-create-class-features-plugin": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.9", + "@babel/helper-replace-supers": "^7.22.20", "@babel/helper-split-export-declaration": "^7.22.6", "@babel/plugin-syntax-decorators": "^7.22.10" }, @@ -596,6 +698,26 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-proposal-object-rest-spread": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", + "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead.", + "peer": true, + "dependencies": { + "@babel/compat-data": "^7.20.5", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-proposal-optional-chaining": { "version": "7.21.0", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz", @@ -986,9 +1108,9 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.15.tgz", - "integrity": "sha512-G1czpdJBZCtngoK1sJgloLiOHUnkb/bLZwqVZD8kXmq0ZnVfTTWUcs9OWtp0mBtYJ+4LQY1fllqBkOIPhXmFmw==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.0.tgz", + "integrity": "sha512-cOsrbmIOXmf+5YbL99/S49Y3j46k/T16b9ml8bm9lP6N9US5iQ2yBK7gpui1pg0V/WMcXdkfKbTb7HXq9u+v4g==", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, @@ -1068,9 +1190,9 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.15.tgz", - "integrity": "sha512-HzG8sFl1ZVGTme74Nw+X01XsUTqERVQ6/RLHo3XjGRzm7XD6QTtfS3NJotVgCGy8BzkDqRjRBD8dAyJn5TuvSQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.0.tgz", + "integrity": "sha512-vaMdgNXFkYrB+8lbgniSYWHsgqK5gjaMNcc84bMIOMRLH0L9AqYq3hwMdvnyqj1OPqea8UtjPEuS/DCenah1wg==", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, @@ -1259,11 +1381,11 @@ } }, "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.22.5.tgz", - "integrity": "sha512-R+PTfLTcYEmb1+kK7FNkhQ1gP4KgjpSO6HfH9+f8/yfp2Nt3ggBjiVpRwmwTlfqZLafYKJACy36yDXlEmI9HjQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.0.tgz", + "integrity": "sha512-xWT5gefv2HGSm4QHtgc1sYPbseOyf+FFDo2JbpE25GWl5BqTGO9IMwTYJRoIdjsF85GE+VegHxSCUt5EvoYTAw==", "dependencies": { - "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-module-transforms": "^7.23.0", "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { @@ -1274,11 +1396,11 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.15.tgz", - "integrity": "sha512-jWL4eh90w0HQOTKP2MoXXUpVxilxsB2Vl4ji69rSjS3EcZ/v4sBmn+A3NpepuJzBhOaEBbR7udonlHHn5DWidg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.0.tgz", + "integrity": "sha512-32Xzss14/UVc7k9g775yMIvkVK8xwKE0DPdP5JTapr3+Z9w4tzeOuLNY6BXDQR6BdnzIlXnCGAzsk/ICHBLVWQ==", "dependencies": { - "@babel/helper-module-transforms": "^7.22.15", + "@babel/helper-module-transforms": "^7.23.0", "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-simple-access": "^7.22.5" }, @@ -1290,14 +1412,14 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.11.tgz", - "integrity": "sha512-rIqHmHoMEOhI3VkVf5jQ15l539KrwhzqcBO6wdCNWPWc/JWt9ILNYNUssbRpeq0qWns8svuw8LnMNCvWBIJ8wA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.0.tgz", + "integrity": "sha512-qBej6ctXZD2f+DhlOC9yO47yEYgUh5CZNz/aBoH4j/3NOlRfJXJbY7xDQCqQVf9KbrqGzIWER1f23doHGrIHFg==", "dependencies": { "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-module-transforms": "^7.22.9", + "@babel/helper-module-transforms": "^7.23.0", "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5" + "@babel/helper-validator-identifier": "^7.22.20" }, "engines": { "node": ">=6.9.0" @@ -1429,9 +1551,9 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.15.tgz", - "integrity": "sha512-ngQ2tBhq5vvSJw2Q2Z9i7ealNkpDMU0rGWnHPKqRZO0tzZ5tlaoz4hDvhXioOoaE0X2vfNss1djwg0DXlfu30A==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.0.tgz", + "integrity": "sha512-sBBGXbLJjxTzLBF5rFWaikMnOGOk/BmK6vVByIdEggZ7Vn6CvWXZyRkkLFK6WE0IF8jSliyOkUN6SScFgzCM0g==", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", @@ -1916,14 +2038,14 @@ } }, "node_modules/@babel/preset-typescript": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.22.15.tgz", - "integrity": "sha512-HblhNmh6yM+cU4VwbBRpxFhxsTdfS1zsvH9W+gEjD0ARV9+8B4sNfpI6GuhePti84nuvhiwKS539jKPFHskA9A==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.23.0.tgz", + "integrity": "sha512-6P6VVa/NM/VlAYj5s2Aq/gdVg8FSENCg3wlZ6Qau9AcPaoF5LbN1nyGlR9DTRIw9PpxI94e+ReydsJHcjwAweg==", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-validator-option": "^7.22.15", "@babel/plugin-syntax-jsx": "^7.22.5", - "@babel/plugin-transform-modules-commonjs": "^7.22.15", + "@babel/plugin-transform-modules-commonjs": "^7.23.0", "@babel/plugin-transform-typescript": "^7.22.15" }, "engines": { @@ -1939,9 +2061,9 @@ "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" }, "node_modules/@babel/runtime": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.15.tgz", - "integrity": "sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA==", + "version": "7.23.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.1.tgz", + "integrity": "sha512-hC2v6p8ZSI/W0HUzh3V8C5g+NwSKzKPtJwSpTjwl0o297GP9+ZLQSkdvHz46CM3LqyoXxq+5G9komY+eSqSO0g==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -1963,18 +2085,18 @@ } }, "node_modules/@babel/traverse": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.20.tgz", - "integrity": "sha512-eU260mPZbU7mZ0N+X10pxXhQFMGTeLb9eFS0mxehS8HZp9o1uSnFeWQuG1UPrlxgA7QoUzFhOnilHDp0AXCyHw==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.0.tgz", + "integrity": "sha512-t/QaEvyIoIkwzpiZ7aoSKK8kObQYeF7T2v+dazAYCb8SXtp58zEVkWW7zAnju8FNKNdr4ScAOEDmMItbyOmEYw==", "dependencies": { "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.22.15", + "@babel/generator": "^7.23.0", "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.22.5", + "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.22.16", - "@babel/types": "^7.22.19", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -1983,12 +2105,12 @@ } }, "node_modules/@babel/types": { - "version": "7.22.19", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.19.tgz", - "integrity": "sha512-P7LAw/LbojPzkgp5oznjE6tQEIWbp4PkkfrZDINTro9zgBRtI324/EYsiSI7lhPbpIQ+DCeR2NNmMWANGGfZsg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dependencies": { "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.19", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { @@ -2270,6 +2392,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.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==", + "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", @@ -2285,9 +2428,9 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.1.tgz", - "integrity": "sha512-PWiOzLIUAjN/w5K17PoF4n6sKBw0gqLHPhywmYHP4t1VFQQVYeb1yWsJwnMVEMl3tUHME7X/SJPZLmtG7XBDxQ==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.9.0.tgz", + "integrity": "sha512-zJmuCWj2VLBt4c25CfBIbMZLGLyhkvs7LznyVX5HfpzeocThgIj5XQK4L+g3U36mMcx8bPMhGyPpwCATamC4jQ==", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } @@ -2676,25 +2819,6 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/@jest/expect-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", - "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", - "dependencies": { - "jest-get-type": "^29.6.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/expect-utils/node_modules/jest-get-type": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", - "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/@jest/fake-timers": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.5.1.tgz", @@ -2969,6 +3093,11 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "node_modules/@jest/transform/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, "node_modules/@jest/transform/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -3132,6 +3261,13 @@ "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==" }, + "node_modules/@nicolo-ribaudo/chokidar-2": { + "version": "2.1.8-no-fsevents.3", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz", + "integrity": "sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==", + "optional": true, + "peer": true + }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", @@ -3316,9 +3452,9 @@ "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==" }, "node_modules/@rushstack/eslint-patch": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.4.0.tgz", - "integrity": "sha512-cEjvTPU32OM9lUFegJagO0mRnIn+rbqrG89vV8/xLnLFX0DoR0r1oy5IlTga71Q7uT3Qus7qm7wgeiMT/+Irlg==" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.5.0.tgz", + "integrity": "sha512-EF3948ckf3f5uPgYbQ6GhyA56Dmv8yg0+ir+BroRjwdxyZJsekhZzawOecC2rOTPCz173t7ZcR1HHZu0dZgOCw==" }, "node_modules/@sinclair/typebox": { "version": "0.24.51", @@ -4006,6 +4142,16 @@ "@types/node": "*" } }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-YIQtIg4PKr7ZyqNPZObpxfHsHEmuB8dXCxd6qVcGuQVDK2bpsF7bYNnBJ4Nn7giuACZg+WewExgrtAJ3XnA4Xw==", + "dev": true, + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "node_modules/@types/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", @@ -4030,255 +4176,28 @@ "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==" }, "node_modules/@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-gPQuzaPR5h/djlAv2apEG1HVOyj1IUs7GpfMZixU0/0KXT3pm64ylHuMUI1/Akh+sq/iikxg6Z2j+fcMDXaaTQ==", "dependencies": { "@types/istanbul-lib-coverage": "*" } }, "node_modules/@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.2.tgz", + "integrity": "sha512-kv43F9eb3Lhj+lr/Hn6OcLCs/sSM8bt+fIaP11rCYngfV6NVjzWXJ17owQtDQTL9tQ8WSLUrGsSJ6rJz0F1w1A==", "dependencies": { "@types/istanbul-lib-report": "*" } }, "node_modules/@types/jest": { - "version": "29.5.5", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.5.tgz", - "integrity": "sha512-ebylz2hnsWR9mYvmBFbXJXr+33UPc4+ZdxyDXh5w0FlPBTfCVN3wPL+kuOiQt3xvrK419v7XWeAs+AeOksafXg==", - "dependencies": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" - } - }, - "node_modules/@types/jest/node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@types/jest/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@types/jest/node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==" - }, - "node_modules/@types/jest/node_modules/@types/yargs": { - "version": "17.0.24", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", - "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/jest/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@types/jest/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@types/jest/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@types/jest/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@types/jest/node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@types/jest/node_modules/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", - "dependencies": { - "@jest/expect-utils": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@types/jest/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@types/jest/node_modules/jest-diff": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", - "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@types/jest/node_modules/jest-get-type": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", - "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@types/jest/node_modules/jest-matcher-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", - "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@types/jest/node_modules/jest-message-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", - "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@types/jest/node_modules/jest-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@types/jest/node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@types/jest/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@types/jest/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" - }, - "node_modules/@types/jest/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "version": "27.5.2", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.5.2.tgz", + "integrity": "sha512-mpT8LJJ4CMeeahobofYWIjFo0xonRS/HfxnVEPMPFSQdGUt1uHCnoPT7Zhb+sjDU2wz0oKV0OLUR0WzrHNgfeA==", "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "jest-matcher-utils": "^27.0.0", + "pretty-format": "^27.0.0" } }, "node_modules/@types/json-schema": { @@ -4292,14 +4211,14 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" }, "node_modules/@types/mime": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", - "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.3.tgz", + "integrity": "sha512-Ys+/St+2VF4+xuY6+kDIXGxbNRO0mesVg0bbxEfB97Od1Vjpjx9KD1qxs64Gcb3CWPirk9Xe+PT4YiiHQ9T+eg==" }, "node_modules/@types/node": { - "version": "20.6.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.6.4.tgz", - "integrity": "sha512-nU6d9MPY0NBUMiE/nXd2IIoC4OLvsLpwAjheoAeuzgvDZA1Cb10QYg+91AF6zQiKWRN5i1m07x6sMe0niBznoQ==" + "version": "16.18.54", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.54.tgz", + "integrity": "sha512-oTmGy68gxZZ21FhTJVVvZBYpQHEBZxHKTsGshobMqm9qWpbqdZsA5jvsuPZcHu0KwpmLrOHWPdEfg7XDpNT9UA==" }, "node_modules/@types/parse-json": { "version": "4.0.0", @@ -4327,14 +4246,14 @@ "integrity": "sha512-u95svzDlTysU5xecFNTgfFG5RUWu1A9P0VzgpcIiGZA9iraHOdSzcxMxQ55DyeRaGCSxQi7LxXDI4rzq/MYfdg==" }, "node_modules/@types/range-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.5.tgz", + "integrity": "sha512-xrO9OoVPqFuYyR/loIHjnbvvyRZREYKLjxV4+dY6v3FQR3stQ9ZxIGkaclF7YhI9hfjpuTbu14hZEy94qKLtOA==" }, "node_modules/@types/react": { - "version": "18.2.22", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.22.tgz", - "integrity": "sha512-60fLTOLqzarLED2O3UQImc/lsNRgG0jE/a1mPW9KjMemY0LMITWEsbS4VvZ4p6rorEHd5YKxxmMKSDK505GHpA==", + "version": "18.2.23", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.23.tgz", + "integrity": "sha512-qHLW6n1q2+7KyBEYnrZpcsAmU/iiCh9WGCKgXvMxx89+TYdJWRjZohVIo9XTcoLhfX3+/hP0Pbulu3bCZQ9PSA==", "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -4342,9 +4261,9 @@ } }, "node_modules/@types/react-dom": { - "version": "18.2.7", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.7.tgz", - "integrity": "sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==", + "version": "18.2.8", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.8.tgz", + "integrity": "sha512-bAIvO5lN/U8sPGvs1Xm61rlRHHaq5rp5N3kp9C+NJ/Q41P8iqjkXSu0+/qu8POsjH9pNWb0OYabFez7taP7omw==", "dependencies": { "@types/react": "*" } @@ -4363,36 +4282,36 @@ "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" }, "node_modules/@types/scheduler": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", - "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==" + "version": "0.16.4", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.4.tgz", + "integrity": "sha512-2L9ifAGl7wmXwP4v3pN4p2FLhD0O1qsJpvKmNin5VA8+UvNVb447UDaAEV6UdrkA+m/Xs58U1RFps44x6TFsVQ==" }, "node_modules/@types/semver": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.2.tgz", - "integrity": "sha512-7aqorHYgdNO4DM36stTiGO3DvKoex9TQRwsJU6vMaFGyqpBA1MNZkz+PG3gaNUPpTAOYhT1WR7M1JyA3fbS9Cw==" + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw==" }, "node_modules/@types/send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz", - "integrity": "sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==", + "version": "0.17.2", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.2.tgz", + "integrity": "sha512-aAG6yRf6r0wQ29bkS+x97BIs64ZLxeE/ARwyS6wrldMm3C1MdKwCcnnEwMC1slI8wuxJOpiUH9MioC0A0i+GJw==", "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "node_modules/@types/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.2.tgz", + "integrity": "sha512-asaEIoc6J+DbBKXtO7p2shWUpKacZOoMBEGBgPG91P8xhO53ohzHWGCs4ScZo5pQMf5ukQzVT9fhX1WzpHihig==", "dependencies": { "@types/express": "*" } }, "node_modules/@types/serve-static": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.2.tgz", - "integrity": "sha512-J2LqtvFYCzaj8pVYKw8klQXrLLk7TBZmQ4ShlcdkELFKGwGMfevMLneMMRkMgZxotOD9wg497LpC7O8PcvAmfw==", + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.3.tgz", + "integrity": "sha512-yVRvFsEMrv7s0lGhzrggJjNOSmZCdgCjw9xWrPr/kNNLp6FaDfMC1KaYl3TSJ0c58bECwNBMoQrZJ8hA8E1eFg==", "dependencies": { "@types/http-errors": "*", "@types/mime": "*", @@ -4400,9 +4319,9 @@ } }, "node_modules/@types/sockjs": { - "version": "0.3.33", - "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", - "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", + "version": "0.3.34", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.34.tgz", + "integrity": "sha512-R+n7qBFnm/6jinlteC9DBL5dGiDGjWAvjo4viUanpnc/dG1y7uDoacXPIQ/PQEg1fI912SMHIa014ZjRpvDw4g==", "dependencies": { "@types/node": "*" } @@ -4412,6 +4331,23 @@ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==" }, + "node_modules/@types/styled-components": { + "version": "5.1.28", + "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.28.tgz", + "integrity": "sha512-nu0VKNybkjvUqJAXWtRqKd7j3iRUl8GbYSTvZNuIBJcw/HUp1Y4QUXNLlj7gcnRV/t784JnHAlvRnSnE3nPbJA==", + "dev": true, + "dependencies": { + "@types/hoist-non-react-statics": "*", + "@types/react": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/stylis": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.1.tgz", + "integrity": "sha512-OSaMrXUKxVigGlKRrET39V2xdhzlztQ9Aqumn1WbCBKHOi9ry7jKSd7rkyj0GzmWaU960Rd+LpOFpLfx5bMQAg==", + "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", @@ -4426,25 +4362,25 @@ "integrity": "sha512-IDaobHimLQhjwsQ/NMwRVfa/yL7L/wriQPMhw1ZJall0KX6E1oxk29XMDeilW5qTIg5aoiqf5Udy8U/51aNoQQ==" }, "node_modules/@types/ws": { - "version": "8.5.5", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.5.tgz", - "integrity": "sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.6.tgz", + "integrity": "sha512-8B5EO9jLVCy+B58PLHvLDuOD8DRVMgQzq8d55SjLCOn9kqGyqOvy27exVaTio1q1nX5zLu8/6N0n2ThSxOM6tg==", "dependencies": { "@types/node": "*" } }, "node_modules/@types/yargs": { - "version": "16.0.5", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.5.tgz", - "integrity": "sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==", + "version": "16.0.6", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.6.tgz", + "integrity": "sha512-oTP7/Q13GSPrgcwEwdlnkoZSQ1Hg9THe644qq8PG6hhJzjZ3qj1JjEFPIwWV/IXVs5XGIVqtkNOS9kh63WIJ+A==", "dependencies": { "@types/yargs-parser": "*" } }, "node_modules/@types/yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==" + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.1.tgz", + "integrity": "sha512-axdPBuLuEJt0c4yI5OZssC19K2Mq1uKdrfZBzuxLvaztgqUtFYZUNw7lETExPYJR9jdEoIg4mb7RQKRQzOkeGQ==" }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "5.62.0", @@ -5716,9 +5652,9 @@ "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" }, "node_modules/browserslist": { - "version": "4.21.11", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.11.tgz", - "integrity": "sha512-xn1UXOKUz7DjdGlg9RrUr0GGiWzI97UQJnugHtH0OLDfJB7jMgoIkYvRIEO1l9EeEERVqeqLYOcFBW9ldjypbQ==", + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.0.tgz", + "integrity": "sha512-v+Jcv64L2LbfTC6OnRcaxtqJNJuQAVhZKSJfR/6hn7lhnChUXl4amwVviqN1k411BB+3rRoKMitELRn1CojeRA==", "funding": [ { "type": "opencollective", @@ -5734,8 +5670,8 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001538", - "electron-to-chromium": "^1.4.526", + "caniuse-lite": "^1.0.30001539", + "electron-to-chromium": "^1.4.530", "node-releases": "^2.0.13", "update-browserslist-db": "^1.0.13" }, @@ -5826,6 +5762,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", @@ -5838,9 +5783,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001538", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001538.tgz", - "integrity": "sha512-HWJnhnID+0YMtGlzcp3T9drmBJUVDchPJ08tpUGFLs9CYlwWPH2uLgpHn8fND5pCgXVtnGS3H4QR9XLMHVNkHw==", + "version": "1.0.30001540", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001540.tgz", + "integrity": "sha512-9JL38jscuTJBTcuETxm8QLsFr/F6v0CYYTEU6r5+qSM98P2Q0Hmu0eG1dTG5GBUmywU3UlcVOUSIJYY47rdFSw==", "funding": [ { "type": "opencollective", @@ -6154,9 +6099,9 @@ } }, "node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, "node_modules/cookie": { "version": "0.5.0", @@ -6261,6 +6206,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", @@ -6442,6 +6396,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", @@ -7033,9 +6998,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.528", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.528.tgz", - "integrity": "sha512-UdREXMXzLkREF4jA8t89FQjA8WHI6ssP38PMY4/4KhXFQbtImnghh4GkCgrtiZwLKUKVD2iTVXvDVQjfomEQuA==" + "version": "1.4.532", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.532.tgz", + "integrity": "sha512-piIR0QFdIGKmOJTSNg5AwxZRNWQSXlRYycqDB9Srstx4lip8KpcmRxVP6zuFWExWziHYZpJ0acX7TxqX95KBpg==" }, "node_modules/emittery": { "version": "0.8.1", @@ -8545,9 +8510,15 @@ } }, "node_modules/fs-monkey": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.4.tgz", - "integrity": "sha512-INM/fWAxMICjttnD0DX1rBvinKskj5G1w+oy/pnm9u/tSlnBrzFonJMcalKJ30P8RRsPzKcCG7Q8l0jx5Fh9YQ==" + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.5.tgz", + "integrity": "sha512-8uMbBjrhzW76TYgEV27Y5E//W2f/lTFmx78P2w19FZSxarhI/798APGQyuGCwmkNxgwGRhrLfvWyLBvNtuOmew==" + }, + "node_modules/fs-readdir-recursive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", + "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", + "peer": true }, "node_modules/fs.realpath": { "version": "1.0.0", @@ -8904,6 +8875,21 @@ "he": "bin/he" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dev": true, + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true + }, "node_modules/hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -11425,9 +11411,9 @@ } }, "node_modules/jest-watch-typeahead/node_modules/@types/yargs": { - "version": "17.0.24", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", - "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", + "version": "17.0.25", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.25.tgz", + "integrity": "sha512-gy7iPgwnzNvxgAEi2bXOHWCVOG6f7xsprVJH4MjlAWeBmJ7vh/Y1kwMtUrs64ztf24zVIRCpr3n/z6gm9QIkgg==", "dependencies": { "@types/yargs-parser": "*" } @@ -15020,6 +15006,11 @@ } } }, + "node_modules/resolve-url-loader/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, "node_modules/resolve-url-loader/node_modules/picocolors": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", @@ -15513,6 +15504,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", @@ -16009,6 +16006,61 @@ "webpack": "^5.0.0" } }, + "node_modules/styled-components": { + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.0.8.tgz", + "integrity": "sha512-AwI02MTWZwqjzfXgR5QcbmcSn5xVjY4N2TLjSuYnmuBGF3y7GicHz3ysbpUq2EMJP5M8/Nc22vcmF3V3WNZDFA==", + "peer": true, + "dependencies": { + "@babel/cli": "^7.21.0", + "@babel/core": "^7.21.0", + "@babel/helper-module-imports": "^7.18.6", + "@babel/plugin-external-helpers": "^7.18.6", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-proposal-object-rest-spread": "^7.20.7", + "@babel/preset-env": "^7.20.2", + "@babel/preset-react": "^7.18.6", + "@babel/preset-typescript": "^7.21.0", + "@babel/traverse": "^7.21.2", + "@emotion/is-prop-valid": "^1.2.1", + "@emotion/unitless": "^0.8.0", + "@types/stylis": "^4.0.2", + "css-to-react-native": "^3.2.0", + "csstype": "^3.1.2", + "postcss": "^8.4.23", + "shallowequal": "^1.1.0", + "stylis": "^4.3.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "babel-plugin-styled-components": ">= 2", + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0" + }, + "peerDependenciesMeta": { + "babel-plugin-styled-components": { + "optional": true + } + } + }, + "node_modules/styled-reset": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/styled-reset/-/styled-reset-4.5.1.tgz", + "integrity": "sha512-6EvFWZRwaFRFxiPYMwmnzOe33rDkw5r9jIU0eEi49bkt6VSrvjeMp2ZOw/YFbw5SVs81llIY+5fzHtR2/VBZfQ==", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "styled-components": ">=4.0.0 || >=5.0.0 || >=6.0.0" + } + }, "node_modules/stylehacks": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", @@ -16024,6 +16076,12 @@ "postcss": "^8.2.15" } }, + "node_modules/stylis": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.0.tgz", + "integrity": "sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ==", + "peer": true + }, "node_modules/sucrase": { "version": "3.34.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz", @@ -16656,7 +16714,6 @@ "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -16860,6 +16917,11 @@ "node": ">=10.12.0" } }, + "node_modules/v8-to-istanbul/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/package.json b/package.json index 49b3308..538fb11 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,20 @@ { - "name": "react-messenger-18th", + "name": "my-app", "version": "0.1.0", "private": true, "dependencies": { "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "@types/jest": "^27.5.2", + "@types/node": "^16.18.54", + "@types/react": "^18.2.23", + "@types/react-dom": "^18.2.8", "react": "^18.2.0", "react-dom": "^18.2.0", "react-scripts": "5.0.1", + "styled-reset": "^4.5.1", + "typescript": "^4.9.5", "web-vitals": "^2.1.4" }, "scripts": { @@ -34,5 +40,8 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "devDependencies": { + "@types/styled-components": "^5.1.28" } } diff --git a/src/App.test.js b/src/App.test.tsx similarity index 90% rename from src/App.test.js rename to src/App.test.tsx index 1f03afe..2a68616 100644 --- a/src/App.test.js +++ b/src/App.test.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import { render, screen } from '@testing-library/react'; import App from './App'; diff --git a/src/App.js b/src/App.tsx similarity index 84% rename from src/App.js rename to src/App.tsx index 3784575..a53698a 100644 --- a/src/App.js +++ b/src/App.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import logo from './logo.svg'; import './App.css'; @@ -7,7 +8,7 @@ function App() {
logo

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

+ + diff --git a/src/assets/images/BE.svg b/src/assets/images/BE.svg new file mode 100644 index 0000000..71a01da --- /dev/null +++ b/src/assets/images/BE.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/images/Camera.svg b/src/assets/images/Camera.svg new file mode 100644 index 0000000..8e0f859 --- /dev/null +++ b/src/assets/images/Camera.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/images/Chat-black.svg b/src/assets/images/Chat-black.svg new file mode 100644 index 0000000..57042cd --- /dev/null +++ b/src/assets/images/Chat-black.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/Chat.svg b/src/assets/images/Chat.svg new file mode 100644 index 0000000..a468549 --- /dev/null +++ b/src/assets/images/Chat.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/Friends-black.svg b/src/assets/images/Friends-black.svg new file mode 100644 index 0000000..1f8f5c3 --- /dev/null +++ b/src/assets/images/Friends-black.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/images/Friends.svg b/src/assets/images/Friends.svg new file mode 100644 index 0000000..4158682 --- /dev/null +++ b/src/assets/images/Friends.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/images/Gallery.svg b/src/assets/images/Gallery.svg new file mode 100644 index 0000000..c3b8c71 --- /dev/null +++ b/src/assets/images/Gallery.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/images/Github.svg b/src/assets/images/Github.svg new file mode 100644 index 0000000..8652b95 --- /dev/null +++ b/src/assets/images/Github.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/images/Instagram.svg b/src/assets/images/Instagram.svg new file mode 100644 index 0000000..d7de4ab --- /dev/null +++ b/src/assets/images/Instagram.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/images/Link.svg b/src/assets/images/Link.svg new file mode 100644 index 0000000..2147080 --- /dev/null +++ b/src/assets/images/Link.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/images/Magnifer.svg b/src/assets/images/Magnifer.svg new file mode 100644 index 0000000..2ad4e8f --- /dev/null +++ b/src/assets/images/Magnifer.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/assets/images/Microphone.svg b/src/assets/images/Microphone.svg new file mode 100644 index 0000000..47dd9b1 --- /dev/null +++ b/src/assets/images/Microphone.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/images/More-black.svg b/src/assets/images/More-black.svg new file mode 100644 index 0000000..1d21cee --- /dev/null +++ b/src/assets/images/More-black.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/images/More-white.svg b/src/assets/images/More-white.svg new file mode 100644 index 0000000..8c64458 --- /dev/null +++ b/src/assets/images/More-white.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/images/Phone.svg b/src/assets/images/Phone.svg new file mode 100644 index 0000000..0c6d74d --- /dev/null +++ b/src/assets/images/Phone.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/Profile-Icon.svg b/src/assets/images/Profile-Icon.svg new file mode 100644 index 0000000..7770f39 --- /dev/null +++ b/src/assets/images/Profile-Icon.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/images/Safari-Black.svg b/src/assets/images/Safari-Black.svg new file mode 100644 index 0000000..54644a6 --- /dev/null +++ b/src/assets/images/Safari-Black.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/Safari.svg b/src/assets/images/Safari.svg new file mode 100644 index 0000000..56e0b15 --- /dev/null +++ b/src/assets/images/Safari.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/Sticker-Smile.svg b/src/assets/images/Sticker-Smile.svg new file mode 100644 index 0000000..a24e36f --- /dev/null +++ b/src/assets/images/Sticker-Smile.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/images/Videocamera.svg b/src/assets/images/Videocamera.svg new file mode 100644 index 0000000..5a42eb2 --- /dev/null +++ b/src/assets/images/Videocamera.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/images/Write-pen.svg b/src/assets/images/Write-pen.svg new file mode 100644 index 0000000..698f66d --- /dev/null +++ b/src/assets/images/Write-pen.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/index.js b/src/index.tsx similarity index 85% rename from src/index.js rename to src/index.tsx index d563c0f..032464f 100644 --- a/src/index.js +++ b/src/index.tsx @@ -4,7 +4,9 @@ import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; -const root = ReactDOM.createRoot(document.getElementById('root')); +const root = ReactDOM.createRoot( + document.getElementById('root') as HTMLElement +); root.render( diff --git a/src/react-app-env.d.ts b/src/react-app-env.d.ts new file mode 100644 index 0000000..6431bc5 --- /dev/null +++ b/src/react-app-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/src/reportWebVitals.js b/src/reportWebVitals.ts similarity index 75% rename from src/reportWebVitals.js rename to src/reportWebVitals.ts index 5253d3a..49a2a16 100644 --- a/src/reportWebVitals.js +++ b/src/reportWebVitals.ts @@ -1,4 +1,6 @@ -const reportWebVitals = onPerfEntry => { +import { ReportHandler } from 'web-vitals'; + +const reportWebVitals = (onPerfEntry?: ReportHandler) => { if (onPerfEntry && onPerfEntry instanceof Function) { import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { getCLS(onPerfEntry); diff --git a/src/setupTests.js b/src/setupTests.ts similarity index 100% rename from src/setupTests.js rename to src/setupTests.ts diff --git a/src/styles/GlobalStyle.tsx b/src/styles/GlobalStyle.tsx new file mode 100644 index 0000000..e09b027 --- /dev/null +++ b/src/styles/GlobalStyle.tsx @@ -0,0 +1,8 @@ +import { createGlobalStyle } from "styled-components"; +import reset from "styled-reset"; + +const GlobalStyle = createGlobalStyle` +${reset} +`; + +export default GlobalStyle; \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..a273b0c --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": [ + "src" + ] +} From 2ae3238557b8e7fd0a378d266e8c6a6f5e214cc3 Mon Sep 17 00:00:00 2001 From: Jiseok Date: Thu, 28 Sep 2023 22:16:30 +0900 Subject: [PATCH 02/32] [feat]Input field --- src/components/ChatInput.tsx | 97 ++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 src/components/ChatInput.tsx diff --git a/src/components/ChatInput.tsx b/src/components/ChatInput.tsx new file mode 100644 index 0000000..4abd155 --- /dev/null +++ b/src/components/ChatInput.tsx @@ -0,0 +1,97 @@ +import React, { useState } from 'react'; +import styled from 'styled-components'; + +import Camera from '../assets/images/Camera.svg'; // Replace 'image1.svg' with the actual name of your image. +import Gallery from '../assets/images/Gallery.svg'; +import Mic from '../assets/images/Microphone.svg'; +import Smile from '../assets/images/Sticker-Smile.svg'; + +interface ChatInputProps { + onSend: (message: string) => void; +} + +const ChatInput: React.FC = ({ onSend }) => { + const [inputValue, setInputValue] = useState(''); + + const handleSendMessage = (e: React.FormEvent) => { + e.preventDefault(); + if (inputValue.trim()) { + onSend(inputValue); + setInputValue(''); + } + }; + + return ( + + + + + + setInputValue(e.target.value)} + placeholder="Enter your message..." + /> + + + + + + ); + }; + + + export default ChatInput; + + +const StyledInputBar = styled.form` + display: flex; // set to flex + align-items: center; // vertically align items in the center + padding: 0 10px; // some padding to give some space + width: 100%; + background: white; + gap: 10px; // space between items in the flex container +`; + +const TextInputContainer = styled.div` + flex-grow: 1; // this makes sure it takes up all available space + height: 44px; + background: #F1F1F1; + border-radius: 22px; + border: 1px #CCCCCC solid; +`; + +const Input = styled.input` + width: 100%; + height: 100%; + border: none; + outline: none; + padding-left: 15px; // Adjust padding so text doesn't start at the very edge + border-radius: 22px; + background: #F1F1F1; + &:focus { + box-shadow: none; + } +`; + +const CameraIcon = styled.img.attrs({ src: Camera })` + width: 24px; + height: 24px; +`; + +const GalleryIcon = styled.img.attrs({ src: Gallery })` + width: 24px; + height: 24px; +`; + +const MicIcon = styled.img.attrs({ src: Mic })` + width: 24px; + height: 24px; +`; + +const SmileIcon = styled.img.attrs({ src: Smile })` + width: 24px; + height: 24px; +`; + +// ... Rest of the code remains unchanged. From e660cdcc1ae54994cf392e559bfd05712c1f5d8c Mon Sep 17 00:00:00 2001 From: Jiseok Date: Fri, 29 Sep 2023 01:59:17 +0900 Subject: [PATCH 03/32] Empty --- README.md | 72 ++++++++++++++++++++++++++++++++------------------- src/index.css | 6 +++-- 2 files changed, 50 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index b87cb00..3d3e0e4 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,66 @@ -# Getting Started with Create React App +# 서론 -This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). +안녕하세요 🙌🏻 18기 프론트 운영진 김문기입니다. 이번 미션에서는 드디어 투두리스트에서 벗어나 새로운 프로젝트인 **messenger** 만들기를 진행합니다. -## Available Scripts +이번주는 특별히 **디자이너와의 협업**으로 진행되는 미션입니다. 디자이너분께서 열심히 리디자인 한 메신저 프로젝트를 여러분들께서 구현해주시면 됩니다. -In the project directory, you can run: +동시에, 이번주부터는 새로 **TypeScript**를 적용해보려고 합니다. -### `npm start` +프로젝트의 규모가 커지게 될 수록, 컴포넌트가 가지는 props의 종류 또한 다양해지게 됩니다. 무지성 코딩을 하다보면 가끔 이 props의 구성과 이름, 어떤 타입이 들어가야 하는지 헷갈리기 마련이죠. 보통 그럴 때 다시 컴포넌트 정의 부분으로 돌아가 props의 구성을 보고 오곤 합니다. -Runs the app in the development mode.\ -Open [http://localhost:3000](http://localhost:3000) to view it in the browser. +하지만 이럴 때, typescript를 적용한다면 컴포넌트의 구성과 그 이름, 심지어 타입까지 한번에 자동완성으로 편리하게 관리할 수 있고, 생산성이 증대되겠죠. -The page will reload if you make edits.\ -You will also see any lint errors in the console. +또한, **React Hooks**에 조금 더 익숙해지는 것을 목표로 합니다. 여러 Hook들이 있지만 그 중에서도 `useState`, `useEffect`, `useRef`를 중점적으로 사용해 보는 미션인데요, React를 사용하면서 굉장히 자주 쓰이는 Hook들이기 때문에 이 부분을 집중적으로 공부해 보아요! -### `npm test` +그럼 이번 미션도 파이팅입니다!!🎉 -Launches the test runner in the interactive watch mode.\ -See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. +# 미션 -### `npm run build` +## Key Questions -Builds the app for production to the `build` folder.\ -It correctly bundles React in production mode and optimizes the build for the best performance. +- JavaScript를 사용할때에 비해 TypeScript를 사용할 때의 장점은 무엇인가요? +- 디자이너로부터 전달받은 피그마 링크 혹은, 피그마 캡처본 +- 컴포넌트를 분리한 기준은 무엇인가요? +- 디자인 시스템을 적용하면서 느낀 점은 무엇인가요? +- 디자이너와 소통하며 느낀점은 무엇인가요? -The build is minified and the filenames include the hashes.\ -Your app is ready to be deployed! +## 미션 목표 -See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. +- TypeScript를 사용해봅시다. +- useState로 컴포넌트의 상태를 관리합니다. +- useEffect와 useRef의 사용법을 이해합니다. +- styled-components를 통한 CSS-in-JS 및 CSS Preprocessor의 사용법에 익숙해집니다. -### `npm run eject` +## 기한 -**Note: this is a one-way operation. Once you `eject`, you can’t go back!** +2023년 9월 29일 금요일 -If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. +## 필수 구현 기능 -Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. +- 피그마를 보고 [결과화면](https://3th-fb-messenger.netlify.app)과 같이 구현합니다. +- 디자인 시스템을 구축합니다. +- 채팅방 상단의 프로필을 클릭하면 사용자를 변경할 수 있습니다. +- 메세지를 보내면 채팅방 하단으로 스크롤을 이동시킵니다. +- 메세지에 유저 정보(프로필 사진, 이름)를 표시합니다. +- user와 message 데이터를 json 파일에 저장합니다. +- UI는 **반응형을 제외**하고 피그마파일을 따라서 진행합니다. -You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. +### 추가 구현 기능 -## Learn More +- 더블 클릭 하면 감정표현을 추가합니다. +- 그 외 추가하고 싶은 기능이 있다면 마음껏 추가해 주세요! -You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). +참고로 이번 과제는 다음주까지 이어지는 과제이므로 **확장성**을 충분히 고려해 주세요. 참고로 **4주차 과제에서는 유저 및 기능 추가와 Routing을** 진행합니다. 이를 위해 [recoil](https://recoiljs.org/ko/)이나 [redux](https://ko.redux.js.org/introduction/getting-started/)를 이용한 상태관리를 미리 해보시는 것을 추천합니다!! 모두 공식문서 많이 읽어보시고 자신만의 상태관리 조합도 찾아보면 재밌을 거에요 XD -To learn React, check out the [React documentation](https://reactjs.org/). +## 링크 및 참고자료 + +- [React docs - Hook](https://ko.reactjs.org/docs/hooks-intro.html) +- [React의 Hooks 완벽 정복하기](https://velog.io/@velopert/react-hooks#1-usestate) +- [useEffect 완벽 가이드](https://overreacted.io/ko/a-complete-guide-to-useeffect/) +- [코딩 컨벤션](https://ui.toast.com/fe-guide/ko_CODING-CONVENTION) +- [타입스크립트 핸드북](https://joshua1988.github.io/ts/intro.html) +- [리액트 프로젝트에서 타입스크립트 사용하기 (시리즈)](https://velog.io/@velopert/series/react-with-typescript) +- [디자인 시스템 구축기](https://yozm.wishket.com/magazine/detail/1830/) +- [[영상] : 컴포넌트에 대한 이해](https://www.youtube.com/watch?v=21eiJc90ggo) +- [Styled Component로 디자인 시스템 구축하기](https://zaat.dev/blog/building-a-design-system-in-react-with-styled-components/) +- [ts 절대경로 설정하기](https://tesseractjh.tistory.com/232) \ No newline at end of file diff --git a/src/index.css b/src/index.css index ec2585e..edcb1ba 100644 --- a/src/index.css +++ b/src/index.css @@ -1,12 +1,14 @@ body { margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + font-family: 'Pretendard' ,-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; } - +::-webkit-scrollbar { + display: none; +} code { font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; From a632bfbb0f483aa344d44a97a023501ed5747c1e Mon Sep 17 00:00:00 2001 From: Jiseok Date: Fri, 29 Sep 2023 02:00:11 +0900 Subject: [PATCH 04/32] =?UTF-8?q?[feat]=EC=84=B8=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 64 +++++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 ++ 2 files changed, 66 insertions(+) diff --git a/package-lock.json b/package-lock.json index 540f5a8..ccc8792 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,8 +15,10 @@ "@types/node": "^16.18.54", "@types/react": "^18.2.23", "@types/react-dom": "^18.2.8", + "@types/react-router-dom": "^5.3.3", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-router-dom": "^6.16.0", "react-scripts": "5.0.1", "styled-reset": "^4.5.1", "typescript": "^4.9.5", @@ -3377,6 +3379,14 @@ } } }, + "node_modules/@remix-run/router": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.9.0.tgz", + "integrity": "sha512-bV63itrKBC0zdT27qYm6SDZHlkXwFL1xMBuhkn+X7l0+IIhNaH5wuuvZKp6eKhCD4KFhujhfhCT1YxXW6esUIA==", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -4142,6 +4152,11 @@ "@types/node": "*" } }, + "node_modules/@types/history": { + "version": "4.7.11", + "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", + "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==" + }, "node_modules/@types/hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -4268,6 +4283,25 @@ "@types/react": "*" } }, + "node_modules/@types/react-router": { + "version": "5.1.20", + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz", + "integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*" + } + }, + "node_modules/@types/react-router-dom": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", + "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router": "*" + } + }, "node_modules/@types/resolve": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", @@ -14672,6 +14706,36 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.16.0.tgz", + "integrity": "sha512-VT4Mmc4jj5YyjpOi5jOf0I+TYzGpvzERy4ckNSvSh2RArv8LLoCxlsZ2D+tc7zgjxcY34oTz2hZaeX5RVprKqA==", + "dependencies": { + "@remix-run/router": "1.9.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.16.0.tgz", + "integrity": "sha512-aTfBLv3mk/gaKLxgRDUPbPw+s4Y/O+ma3rEN1u8EgEpLpPe6gNjIsWt9rxushMHHMb7mSwxRGdGlGdvmFsyPIg==", + "dependencies": { + "@remix-run/router": "1.9.0", + "react-router": "6.16.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", diff --git a/package.json b/package.json index 538fb11..442bda1 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,10 @@ "@types/node": "^16.18.54", "@types/react": "^18.2.23", "@types/react-dom": "^18.2.8", + "@types/react-router-dom": "^5.3.3", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-router-dom": "^6.16.0", "react-scripts": "5.0.1", "styled-reset": "^4.5.1", "typescript": "^4.9.5", From 71cb2303a8a25330ef73a772b7c6f098fee5ce08 Mon Sep 17 00:00:00 2001 From: Jiseok Date: Fri, 29 Sep 2023 02:02:01 +0900 Subject: [PATCH 05/32] =?UTF-8?q?[feat]=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.css | 38 -------------------------------------- src/App.tsx | 30 ++++++++++-------------------- src/index.tsx | 3 ++- 3 files changed, 12 insertions(+), 59 deletions(-) delete mode 100644 src/App.css 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 a53698a..5a6ea86 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,26 +1,16 @@ -import React from 'react'; -import logo from './logo.svg'; -import './App.css'; - +import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; +// import Chat from "./pages/Chat/Chat"; +import ChatRoom from "./components/ChatRoom"; function App() { return ( -
-
- logo -

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

-
- Learn React - -
-
+ + + } /> {} + + ); } export default App; + + diff --git a/src/index.tsx b/src/index.tsx index 032464f..c48ee43 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -3,12 +3,13 @@ import ReactDOM from 'react-dom/client'; import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; - +import GlobalStyle from './styles/GlobalStyle'; const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement ); root.render( + ); From 3c3792f0e4debbfac62eb14ffe0efd9a959efb12 Mon Sep 17 00:00:00 2001 From: Jiseok Date: Fri, 29 Sep 2023 02:02:44 +0900 Subject: [PATCH 06/32] =?UTF-8?q?[feat]=20=EC=B4=88=EA=B8=B0=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/index.html b/public/index.html index aa069f2..f72d985 100644 --- a/public/index.html +++ b/public/index.html @@ -24,7 +24,7 @@ work correctly both with client-side routing and a non-root public URL. Learn how to configure a non-root public URL by running `npm run build`. --> - React App + CEOS CHAT From 61696bc1c2a9c7b9674a3e1ca22b8e40b2f23f24 Mon Sep 17 00:00:00 2001 From: Jiseok Date: Fri, 29 Sep 2023 02:03:57 +0900 Subject: [PATCH 07/32] =?UTF-8?q?[style]=20=EC=95=84=EC=9D=B4=EC=BD=98?= =?UTF-8?q?=EB=B0=B0=EC=B9=98=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ChatInput.tsx | 42 +++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/src/components/ChatInput.tsx b/src/components/ChatInput.tsx index 4abd155..f0d5aa1 100644 --- a/src/components/ChatInput.tsx +++ b/src/components/ChatInput.tsx @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import styled from 'styled-components'; -import Camera from '../assets/images/Camera.svg'; // Replace 'image1.svg' with the actual name of your image. +import Camera from '../assets/images/Camera.svg'; import Gallery from '../assets/images/Gallery.svg'; import Mic from '../assets/images/Microphone.svg'; import Smile from '../assets/images/Sticker-Smile.svg'; @@ -23,52 +23,51 @@ const ChatInput: React.FC = ({ onSend }) => { return ( - - - + + setInputValue(e.target.value)} - placeholder="Enter your message..." + /> + + - - - ); }; - + export default ChatInput; - -const StyledInputBar = styled.form` - display: flex; // set to flex - align-items: center; // vertically align items in the center - padding: 0 10px; // some padding to give some space + const StyledInputBar = styled.form` + display: flex; + justify-content: center; width: 100%; background: white; - gap: 10px; // space between items in the flex container `; const TextInputContainer = styled.div` - flex-grow: 1; // this makes sure it takes up all available space + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; height: 44px; + padding: 0 10px; background: #F1F1F1; border-radius: 22px; border: 1px #CCCCCC solid; `; const Input = styled.input` - width: 100%; + flex-grow: 1; + margin: 0 10px; height: 100%; border: none; outline: none; - padding-left: 15px; // Adjust padding so text doesn't start at the very edge border-radius: 22px; - background: #F1F1F1; + background: transparent; &:focus { box-shadow: none; } @@ -77,6 +76,7 @@ const Input = styled.input` const CameraIcon = styled.img.attrs({ src: Camera })` width: 24px; height: 24px; + margin-right:12px `; const GalleryIcon = styled.img.attrs({ src: Gallery })` @@ -92,6 +92,8 @@ const MicIcon = styled.img.attrs({ src: Mic })` const SmileIcon = styled.img.attrs({ src: Smile })` width: 24px; height: 24px; + margin-left:12px + `; -// ... Rest of the code remains unchanged. + From e553e3aa4918a1711f04845725557b214644e685 Mon Sep 17 00:00:00 2001 From: Jiseok Date: Fri, 29 Sep 2023 02:04:43 +0900 Subject: [PATCH 08/32] =?UTF-8?q?[feat]=EA=B8=B0=EB=8A=A5=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ChatHeader.tsx | 84 ++++++++++++++++ src/components/ChatRoom.tsx | 176 ++++++++++++++++++++++++++++++++++ 2 files changed, 260 insertions(+) create mode 100644 src/components/ChatHeader.tsx create mode 100644 src/components/ChatRoom.tsx diff --git a/src/components/ChatHeader.tsx b/src/components/ChatHeader.tsx new file mode 100644 index 0000000..5c7287c --- /dev/null +++ b/src/components/ChatHeader.tsx @@ -0,0 +1,84 @@ +// components/ChatHeader.tsx +import React from 'react'; +import styled from 'styled-components'; +import { ReactComponent as CallIcons } from '../assets/images/Phone.svg'; +import { ReactComponent as VideoIcons } from '../assets/images/Videocamera.svg'; +import { ReactComponent as LeftArrowIcon } from '../assets/images/Arrow-Left.svg'; +interface ChatHeaderProps { + chatName: string; + onSwitchPosition?: () => void; +} + + const ChatHeader: React.FC = ({ chatName, onSwitchPosition }) => { + return ( + + + + {chatName} + + + + + + + + + + + ); +}; +export default ChatHeader; +const ChatHeaderContainer = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + height: 100%; + padding: 10px 18px 10px 12px; + background: rgba(255, 255, 255, 0.90); + backdrop-filter: blur(4px); + gap: 12px; +`; + +const Wrapper = styled.div` + display: flex; + align-items: center; + gap: 4px; +`; + + +const UserName = styled.div` + color: #101010; + font-size: 18px; + font-family: 'Pretendard', sans-serif; + font-weight: 600; + line-height: 18px; + word-wrap: break-word; +`; + +const Icons = styled.div` + display: flex; + justify-content: flex-start; + align-items: center; + gap: 12px; +`; + +const IconWrapper = styled.div` + width: 24px; + height: 24px; + position: relative; +`; + +const CallIcon = styled(CallIcons)` + width: 24px; + height: 24px; +`; + +const VideoIcon = styled(VideoIcons)` + width: 24px; + height: 24px; +`; +const ArrowLeftIcon = styled(LeftArrowIcon)` + width: 24px; + height: 24px; +`; diff --git a/src/components/ChatRoom.tsx b/src/components/ChatRoom.tsx new file mode 100644 index 0000000..298792c --- /dev/null +++ b/src/components/ChatRoom.tsx @@ -0,0 +1,176 @@ +import React, { useState, useRef, useEffect } from 'react'; +import ChatHeader from './ChatHeader'; +import ChatInput from './ChatInput'; +import usersData from '../assets/datas/dummy.json'; +import styled from 'styled-components'; +import { ReactComponent as ProfileIcon } from '../assets/images/Profile-Icon.svg'; + +interface Message { + id: number; + sender: string; + content: string; + timestamp: string; + displayTime: boolean; +} + +const ChatRoom: React.FC = () => { + + const savedMessages = JSON.parse(localStorage.getItem('chatMessages') || '[]'); + const [messages, setMessages] = useState(savedMessages); + + const [currentUser, setCurrentUser] = useState(usersData.users[0]); + const messagesEndRef = useRef(null); + + useEffect(() => { + if (messagesEndRef.current) { + messagesEndRef.current.scrollTop = messagesEndRef.current.scrollHeight; + } + }, [messages]); + useEffect(() => { + localStorage.setItem('chatMessages', JSON.stringify(messages)); + }, [messages]); +//메시지별 id에 시간을 부여해, 기존의 시간과 같으면 시간표시와 프로필 사진을 보이지 않게 하고, 시간이 바뀔 때만 시간표시와 프로필 사진 출력 + const handleSendMessage = (content: string) => { + const newMessage: Message = { + id: Date.now(), + sender: currentUser.name, + content, + timestamp: new Date().toLocaleTimeString([], { hour12: false, hour: '2-digit', minute: '2-digit' }), + displayTime: true + }; + + if (messages.length > 0) { + const lastSenderMessage = messages[messages.length - 1]; + if (lastSenderMessage.sender === newMessage.sender) { + const lastMessageDate = new Date(lastSenderMessage.id); + const newMessageDate = new Date(newMessage.id); + if (lastMessageDate.getMinutes() === newMessageDate.getMinutes()) { + lastSenderMessage.displayTime = false; + } + } + } + + setMessages(prev => [...prev, newMessage]); + }; + const handleSwitchPosition = () => { + setCurrentUser(prev => (prev.name === usersData.users[0].name ? usersData.users[1] : usersData.users[0])); + }; + const oppositeUser = currentUser.name === usersData.users[0].name ? usersData.users[1] : usersData.users[0]; +return ( + + + + + {messages.map((msg, index) => { + const shouldDisplayProfile = index === 0 || messages[index - 1].timestamp !== msg.timestamp || messages[index - 1].sender !== msg.sender; + const isCurrentUser = msg.sender === currentUser.name; + + return ( + + {!isCurrentUser && shouldDisplayProfile && ( + +
+ {msg.sender} +
+ +
+ )} + + {msg.content} + + {msg.displayTime && } +
+ + ); + })} + +
+ + +
+); +}; +export default ChatRoom; + +const ProfileContainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; +`; + +const MessageList = styled.div` + overflow-y: auto; + flex: 1; + margin: 10px 0; + height: 60vh; + scrollbar-width: none; +`; + +const ChatRoomContainer = styled.div` + display: flex; + flex-direction: column; + width: 95%; + max-width: 50vh; + max-height: 90vh; + margin: 40px auto; + background-color: #ffffff; + border-radius: 8px; + padding: 25px; + min-height: 90vh; +`; + +const MessageWrapper = styled.div<{ sender: string; currentUser: string }>` + display: flex; + align-items: center; + flex-direction: ${props => (props.sender === props.currentUser ? "row-reverse" : "row")}; + gap: 12px; + margin: 12px 0; + max-width: 100%; + align-self: ${props => (props.sender === props.currentUser ? "flex-end" : "flex-start")}; +`; + +const MessageBubble = styled.div<{ sender: string; currentUser: string; shouldDisplayProfile: boolean; }>` + padding: 10px 16px; + border-radius: 20px; + background-color: ${props => (props.sender === props.currentUser ? "#101010" : "#F1F1F1")}; + color: ${props => (props.sender === props.currentUser ? "#F1F1F1" : "#101010")}; + margin-left: ${props => (!props.shouldDisplayProfile && props.sender !== props.currentUser) ? "41px" : "0"}; + font-size: 14px; + font-weight: 400; + line-height: 19.60px; + box-sizing: border-box; + max-width: fit-content; + word-break: break-all; +`; + +const ProfileImage = styled.div` + width: 32px; + height: 32px; + border-radius: 50%; + overflow: hidden; + + & > svg { + width: 100%; + height: 100%; + } +`; + +const Username = styled.div` + color: #101010; + font-size: 6px; + font-weight: 600; + line-height: 13px; +`; + +const Time = styled.div` + color: #909090; + font-size: 7px; + font-family: 'Pretendard'; + font-weight: 500; + line-height: 10px; + margin-top: 12px; +`; From d574c9fb35c6f988b41ad0565857ff25adc61dd9 Mon Sep 17 00:00:00 2001 From: Jiseok Date: Fri, 29 Sep 2023 02:05:44 +0900 Subject: [PATCH 09/32] Empty --- src/App.test.tsx | 2 +- src/styles/GlobalStyle.tsx | 29 +++++++++++++++++++++++++++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/App.test.tsx b/src/App.test.tsx index 2a68616..4dea19d 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React from 'react' import { render, screen } from '@testing-library/react'; import App from './App'; diff --git a/src/styles/GlobalStyle.tsx b/src/styles/GlobalStyle.tsx index e09b027..9da3345 100644 --- a/src/styles/GlobalStyle.tsx +++ b/src/styles/GlobalStyle.tsx @@ -2,7 +2,32 @@ import { createGlobalStyle } from "styled-components"; import reset from "styled-reset"; const GlobalStyle = createGlobalStyle` -${reset} + ${reset} + + * { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html, body { + width: 100%; + height: 100%; + font-family: 'Pretendard', sans-serif; /* or any font you prefer */ +} + +#app { /* Assuming your root div has an id of "app" */ + width: 375px; + height: 812px; + margin: 0 auto; /* Center the chatroom horizontally */ + background-color: white; + overflow: hidden; /* Ensures nothing overflows the specified dimensions */ +} + + + + + `; -export default GlobalStyle; \ No newline at end of file +export default GlobalStyle; From 1cc3d8701725aae267bc17672ad57ad78277ebf2 Mon Sep 17 00:00:00 2001 From: Jiseok Date: Fri, 29 Sep 2023 02:06:49 +0900 Subject: [PATCH 10/32] =?UTF-8?q?[chore]=EC=9E=84=EC=8B=9C=EC=9D=98=20?= =?UTF-8?q?=EC=9E=84=EC=8B=9C=EB=8D=B0=EC=9D=B4=ED=84=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/datas/dummy.json | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/assets/datas/dummy.json diff --git a/src/assets/datas/dummy.json b/src/assets/datas/dummy.json new file mode 100644 index 0000000..f309389 --- /dev/null +++ b/src/assets/datas/dummy.json @@ -0,0 +1,15 @@ +{ + "users": [ + { + "id": 1, + "name": "지석", + "profileImage": "ProfileIcon" + }, + { + "id": 2, + "name": "윤서", + "profileImage": "ProfileIcon" + } + ] + } + \ No newline at end of file From 812c8ca3029e390111bbb23b80049e0a10e24b92 Mon Sep 17 00:00:00 2001 From: Jiseok Date: Fri, 29 Sep 2023 02:08:31 +0900 Subject: [PATCH 11/32] Empty --- src/styles/GlobalStyle.tsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/styles/GlobalStyle.tsx b/src/styles/GlobalStyle.tsx index 9da3345..2e43a14 100644 --- a/src/styles/GlobalStyle.tsx +++ b/src/styles/GlobalStyle.tsx @@ -13,15 +13,13 @@ const GlobalStyle = createGlobalStyle` html, body { width: 100%; height: 100%; - font-family: 'Pretendard', sans-serif; /* or any font you prefer */ + font-family: 'Pretendard', sans-serif; } -#app { /* Assuming your root div has an id of "app" */ - width: 375px; - height: 812px; - margin: 0 auto; /* Center the chatroom horizontally */ +#app { + margin: 0 auto; background-color: white; - overflow: hidden; /* Ensures nothing overflows the specified dimensions */ + overflow: hidden; } From 6fd6964522bc2a86d563c2ad94d759d0822c53be Mon Sep 17 00:00:00 2001 From: Jiseok Date: Fri, 29 Sep 2023 02:31:35 +0900 Subject: [PATCH 12/32] =?UTF-8?q?[style]background=20color=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/index.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/index.css b/src/index.css index edcb1ba..94ff963 100644 --- a/src/index.css +++ b/src/index.css @@ -9,6 +9,9 @@ body { ::-webkit-scrollbar { display: none; } +html{ + background-color: gray; +} code { font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; From 91b82204cb43c4513d887340424ae02f692ee353 Mon Sep 17 00:00:00 2001 From: Jiseok Date: Fri, 29 Sep 2023 10:58:09 +0900 Subject: [PATCH 13/32] [style]add statusbar --- src/assets/images/Indicator_low.svg | 3 ++ src/assets/images/indicators.svg | 14 ++++++ src/components/ChatHeader.tsx | 6 ++- src/components/ChatInput.tsx | 31 +++++++++---- src/components/ChatRoom.tsx | 24 ++++------ src/components/StatusBar.tsx | 71 +++++++++++++++++++++++++++++ 6 files changed, 125 insertions(+), 24 deletions(-) create mode 100644 src/assets/images/Indicator_low.svg create mode 100644 src/assets/images/indicators.svg create mode 100644 src/components/StatusBar.tsx diff --git a/src/assets/images/Indicator_low.svg b/src/assets/images/Indicator_low.svg new file mode 100644 index 0000000..b3e0c65 --- /dev/null +++ b/src/assets/images/Indicator_low.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/indicators.svg b/src/assets/images/indicators.svg new file mode 100644 index 0000000..2e784c5 --- /dev/null +++ b/src/assets/images/indicators.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/components/ChatHeader.tsx b/src/components/ChatHeader.tsx index 5c7287c..53fef64 100644 --- a/src/components/ChatHeader.tsx +++ b/src/components/ChatHeader.tsx @@ -10,6 +10,7 @@ interface ChatHeaderProps { } const ChatHeader: React.FC = ({ chatName, onSwitchPosition }) => { + return ( @@ -28,6 +29,7 @@ interface ChatHeaderProps { ); }; export default ChatHeader; + const ChatHeaderContainer = styled.div` display: flex; justify-content: space-between; @@ -35,9 +37,10 @@ const ChatHeaderContainer = styled.div` width: 100%; height: 100%; padding: 10px 18px 10px 12px; - background: rgba(255, 255, 255, 0.90); + background: #ffffff backdrop-filter: blur(4px); gap: 12px; + `; const Wrapper = styled.div` @@ -50,7 +53,6 @@ const Wrapper = styled.div` const UserName = styled.div` color: #101010; font-size: 18px; - font-family: 'Pretendard', sans-serif; font-weight: 600; line-height: 18px; word-wrap: break-word; diff --git a/src/components/ChatInput.tsx b/src/components/ChatInput.tsx index f0d5aa1..9148a7f 100644 --- a/src/components/ChatInput.tsx +++ b/src/components/ChatInput.tsx @@ -1,10 +1,12 @@ import React, { useState } from 'react'; import styled from 'styled-components'; -import Camera from '../assets/images/Camera.svg'; -import Gallery from '../assets/images/Gallery.svg'; -import Mic from '../assets/images/Microphone.svg'; -import Smile from '../assets/images/Sticker-Smile.svg'; +import {ReactComponent as Camera} from '../assets/images/Camera.svg'; +import {ReactComponent as Gallery} from '../assets/images/Gallery.svg'; +import {ReactComponent as Mic} from '../assets/images/Microphone.svg'; +import {ReactComponent as Smile} from '../assets/images/Sticker-Smile.svg'; +import {ReactComponent as Low} from '../assets/images/Indicator_low.svg'; + interface ChatInputProps { onSend: (message: string) => void; @@ -22,6 +24,7 @@ const ChatInput: React.FC = ({ onSend }) => { }; return ( + @@ -34,13 +37,21 @@ const ChatInput: React.FC = ({ onSend }) => { + + + ); }; export default ChatInput; + const InputContainer = styled.div` + display: flex; + flex-direction: column; + width: 100%; +`; const StyledInputBar = styled.form` display: flex; justify-content: center; @@ -73,23 +84,23 @@ const Input = styled.input` } `; -const CameraIcon = styled.img.attrs({ src: Camera })` +const CameraIcon = styled(Camera)` width: 24px; height: 24px; margin-right:12px `; -const GalleryIcon = styled.img.attrs({ src: Gallery })` +const GalleryIcon = styled(Gallery)` width: 24px; height: 24px; `; -const MicIcon = styled.img.attrs({ src: Mic })` +const MicIcon = styled(Mic)` width: 24px; height: 24px; `; -const SmileIcon = styled.img.attrs({ src: Smile })` +const SmileIcon = styled(Smile)` width: 24px; height: 24px; margin-left:12px @@ -97,3 +108,7 @@ const SmileIcon = styled.img.attrs({ src: Smile })` `; +const LowIcon = styled(Low)` +width: 100%; +height: 21px; +`; \ No newline at end of file diff --git a/src/components/ChatRoom.tsx b/src/components/ChatRoom.tsx index 298792c..7ef02ac 100644 --- a/src/components/ChatRoom.tsx +++ b/src/components/ChatRoom.tsx @@ -2,6 +2,7 @@ import React, { useState, useRef, useEffect } from 'react'; import ChatHeader from './ChatHeader'; import ChatInput from './ChatInput'; import usersData from '../assets/datas/dummy.json'; +import StatusBar from './StatusBar' import styled from 'styled-components'; import { ReactComponent as ProfileIcon } from '../assets/images/Profile-Icon.svg'; @@ -18,17 +19,19 @@ const ChatRoom: React.FC = () => { const savedMessages = JSON.parse(localStorage.getItem('chatMessages') || '[]'); const [messages, setMessages] = useState(savedMessages); - const [currentUser, setCurrentUser] = useState(usersData.users[0]); + const [currentUser, setCurrentUser] = useState(usersData.users[0]);//초기엔 유저 1로 세팅 const messagesEndRef = useRef(null); - +//스크롤바 언제나 하단 고정 useEffect(() => { if (messagesEndRef.current) { messagesEndRef.current.scrollTop = messagesEndRef.current.scrollHeight; } }, [messages]); + useEffect(() => { localStorage.setItem('chatMessages', JSON.stringify(messages)); }, [messages]); + // localStorage.clear(); //메시지별 id에 시간을 부여해, 기존의 시간과 같으면 시간표시와 프로필 사진을 보이지 않게 하고, 시간이 바뀔 때만 시간표시와 프로필 사진 출력 const handleSendMessage = (content: string) => { const newMessage: Message = { @@ -58,6 +61,7 @@ const ChatRoom: React.FC = () => { const oppositeUser = currentUser.name === usersData.users[0].name ? usersData.users[1] : usersData.users[0]; return ( + @@ -69,9 +73,9 @@ return ( {!isCurrentUser && shouldDisplayProfile && ( -
+ {/*
{msg.sender} -
+
*/}
)} @@ -119,7 +123,7 @@ const ChatRoomContainer = styled.div` margin: 40px auto; background-color: #ffffff; border-radius: 8px; - padding: 25px; + padding: 5px; min-height: 90vh; `; @@ -138,7 +142,7 @@ const MessageBubble = styled.div<{ sender: string; currentUser: string; shouldDi border-radius: 20px; background-color: ${props => (props.sender === props.currentUser ? "#101010" : "#F1F1F1")}; color: ${props => (props.sender === props.currentUser ? "#F1F1F1" : "#101010")}; - margin-left: ${props => (!props.shouldDisplayProfile && props.sender !== props.currentUser) ? "41px" : "0"}; + margin-left: ${props => (!props.shouldDisplayProfile && props.sender !== props.currentUser) ? "44px" : "0"}; font-size: 14px; font-weight: 400; line-height: 19.60px; @@ -159,17 +163,9 @@ const ProfileImage = styled.div` } `; -const Username = styled.div` - color: #101010; - font-size: 6px; - font-weight: 600; - line-height: 13px; -`; - const Time = styled.div` color: #909090; font-size: 7px; - font-family: 'Pretendard'; font-weight: 500; line-height: 10px; margin-top: 12px; diff --git a/src/components/StatusBar.tsx b/src/components/StatusBar.tsx new file mode 100644 index 0000000..77ee633 --- /dev/null +++ b/src/components/StatusBar.tsx @@ -0,0 +1,71 @@ +import React, { useState, useEffect } from 'react'; +import styled from 'styled-components'; +import { ReactComponent as Indicators } from '../assets/images/indicators.svg'; + + +const StatusBar: React.FC = () => { + const [currentTime, setCurrentTime] = useState(getCurrentTime()); + + + useEffect(() => { + const interval = setInterval(() => { + setCurrentTime(getCurrentTime()); + }, 60000); + return () => clearInterval(interval); + }, []); + return ( + + + {currentTime} + + + + + + + + ); +}; + +export default StatusBar; +function getCurrentTime(): string { + return new Date().toLocaleTimeString([], { hour12: false, hour: '2-digit', minute: '2-digit' }); + } + +const StatusBarContainer = styled.div` + width: 100%; + height: 100%; + padding: 21px 18.70px 10px 40px; + background: #ffffff + backdrop-filter: blur(5px); + display: flex; + justify-content: space-between; + align-items: center; +`; +const TimeContainer = styled.div` + display: flex; + justify-content: center; + align-items: center; +`; + +const TimeText = styled.div` + text-align: center; + color: #101010; + font-size: 17px; + font-weight: 600; + line-height: 17px; + word-wrap: break-word; +`; + +const IconContainer = styled.div` + display: flex; + justify-content: flex-end; + align-items: center; + gap: 6px; +`; + +const Indicator = styled(Indicators)` + width: 76px; + height: 17px; +`; + From aefb41fc8d54cf7bc89343c21410e95553c5aab1 Mon Sep 17 00:00:00 2001 From: Jiseok Date: Fri, 29 Sep 2023 15:09:01 +0900 Subject: [PATCH 14/32] =?UTF-8?q?[style]=20style=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ChatHeader.tsx | 3 -- src/components/ChatInput.tsx | 77 +++++++++++++-------------- src/components/ChatRoom.tsx | 97 ++++++++++++++++------------------- src/components/StatusBar.tsx | 32 ++++++------ src/index.css | 1 + 5 files changed, 94 insertions(+), 116 deletions(-) diff --git a/src/components/ChatHeader.tsx b/src/components/ChatHeader.tsx index 53fef64..d8831db 100644 --- a/src/components/ChatHeader.tsx +++ b/src/components/ChatHeader.tsx @@ -1,4 +1,3 @@ -// components/ChatHeader.tsx import React from 'react'; import styled from 'styled-components'; import { ReactComponent as CallIcons } from '../assets/images/Phone.svg'; @@ -34,8 +33,6 @@ const ChatHeaderContainer = styled.div` display: flex; justify-content: space-between; align-items: center; - width: 100%; - height: 100%; padding: 10px 18px 10px 12px; background: #ffffff backdrop-filter: blur(4px); diff --git a/src/components/ChatInput.tsx b/src/components/ChatInput.tsx index 9148a7f..0b26099 100644 --- a/src/components/ChatInput.tsx +++ b/src/components/ChatInput.tsx @@ -1,30 +1,29 @@ import React, { useState } from 'react'; import styled from 'styled-components'; -import {ReactComponent as Camera} from '../assets/images/Camera.svg'; -import {ReactComponent as Gallery} from '../assets/images/Gallery.svg'; -import {ReactComponent as Mic} from '../assets/images/Microphone.svg'; -import {ReactComponent as Smile} from '../assets/images/Sticker-Smile.svg'; -import {ReactComponent as Low} from '../assets/images/Indicator_low.svg'; - +import { ReactComponent as Camera } from '../assets/images/Camera.svg'; +import { ReactComponent as Gallery } from '../assets/images/Gallery.svg'; +import { ReactComponent as Mic } from '../assets/images/Microphone.svg'; +import { ReactComponent as Smile } from '../assets/images/Sticker-Smile.svg'; +import { ReactComponent as Low } from '../assets/images/Indicator_low.svg'; interface ChatInputProps { onSend: (message: string) => void; } const ChatInput: React.FC = ({ onSend }) => { - const [inputValue, setInputValue] = useState(''); - - const handleSendMessage = (e: React.FormEvent) => { - e.preventDefault(); - if (inputValue.trim()) { - onSend(inputValue); - setInputValue(''); - } - }; - - return ( - + const [inputValue, setInputValue] = useState(''); + + const handleSendMessage = (e: React.FormEvent) => { + e.preventDefault(); + if (inputValue.trim()) { + onSend(inputValue); + setInputValue(''); + } + }; + + return ( + @@ -32,40 +31,36 @@ const ChatInput: React.FC = ({ onSend }) => { setInputValue(e.target.value)} - /> - - - ); - }; - - - export default ChatInput; - - const InputContainer = styled.div` + + ); +}; + +export default ChatInput; + +const InputContainer = styled.div` display: flex; flex-direction: column; - width: 100%; `; - const StyledInputBar = styled.form` + +const StyledInputBar = styled.form` display: flex; justify-content: center; - width: 100%; background: white; `; const TextInputContainer = styled.div` display: flex; align-items: center; - justify-content: space-between; - width: 100%; + justify-content: space-between; + width: 100%; height: 44px; - padding: 0 10px; + padding: 0 10px; background: #F1F1F1; border-radius: 22px; border: 1px #CCCCCC solid; @@ -78,7 +73,7 @@ const Input = styled.input` border: none; outline: none; border-radius: 22px; - background: transparent; + background: transparent; &:focus { box-shadow: none; } @@ -87,7 +82,7 @@ const Input = styled.input` const CameraIcon = styled(Camera)` width: 24px; height: 24px; - margin-right:12px + margin-right: 12px; `; const GalleryIcon = styled(Gallery)` @@ -103,12 +98,10 @@ const MicIcon = styled(Mic)` const SmileIcon = styled(Smile)` width: 24px; height: 24px; - margin-left:12px - + margin-left: 12px; `; - const LowIcon = styled(Low)` -width: 100%; -height: 21px; -`; \ No newline at end of file + width: 100%; + height: 21px; +`; diff --git a/src/components/ChatRoom.tsx b/src/components/ChatRoom.tsx index 7ef02ac..23514f4 100644 --- a/src/components/ChatRoom.tsx +++ b/src/components/ChatRoom.tsx @@ -2,7 +2,7 @@ import React, { useState, useRef, useEffect } from 'react'; import ChatHeader from './ChatHeader'; import ChatInput from './ChatInput'; import usersData from '../assets/datas/dummy.json'; -import StatusBar from './StatusBar' +import StatusBar from './StatusBar'; import styled from 'styled-components'; import { ReactComponent as ProfileIcon } from '../assets/images/Profile-Icon.svg'; @@ -15,13 +15,12 @@ interface Message { } const ChatRoom: React.FC = () => { - const savedMessages = JSON.parse(localStorage.getItem('chatMessages') || '[]'); const [messages, setMessages] = useState(savedMessages); + const [currentUser, setCurrentUser] = useState(usersData.users[0]); - const [currentUser, setCurrentUser] = useState(usersData.users[0]);//초기엔 유저 1로 세팅 const messagesEndRef = useRef(null); -//스크롤바 언제나 하단 고정 + useEffect(() => { if (messagesEndRef.current) { messagesEndRef.current.scrollTop = messagesEndRef.current.scrollHeight; @@ -31,8 +30,7 @@ const ChatRoom: React.FC = () => { useEffect(() => { localStorage.setItem('chatMessages', JSON.stringify(messages)); }, [messages]); - // localStorage.clear(); -//메시지별 id에 시간을 부여해, 기존의 시간과 같으면 시간표시와 프로필 사진을 보이지 않게 하고, 시간이 바뀔 때만 시간표시와 프로필 사진 출력 + const handleSendMessage = (content: string) => { const newMessage: Message = { id: Date.now(), @@ -55,48 +53,46 @@ const ChatRoom: React.FC = () => { setMessages(prev => [...prev, newMessage]); }; + const handleSwitchPosition = () => { setCurrentUser(prev => (prev.name === usersData.users[0].name ? usersData.users[1] : usersData.users[0])); }; + const oppositeUser = currentUser.name === usersData.users[0].name ? usersData.users[1] : usersData.users[0]; -return ( - - - - - - {messages.map((msg, index) => { - const shouldDisplayProfile = index === 0 || messages[index - 1].timestamp !== msg.timestamp || messages[index - 1].sender !== msg.sender; - const isCurrentUser = msg.sender === currentUser.name; - - return ( - - {!isCurrentUser && shouldDisplayProfile && ( - - {/*
- {msg.sender} -
*/} - -
- )} - - {msg.content} - - {msg.displayTime && } -
- - ); - })} - -
- - -
-); + + return ( + + + + + {messages.map((msg, index) => { + const shouldDisplayProfile = index === 0 || messages[index - 1].timestamp !== msg.timestamp || messages[index - 1].sender !== msg.sender; + const isCurrentUser = msg.sender === currentUser.name; + + return ( + + {!isCurrentUser && shouldDisplayProfile && ( + + + + )} + + {msg.content} + + {msg.displayTime && } + + ); + })} + + + + ); }; + export default ChatRoom; const ProfileContainer = styled.div` @@ -110,21 +106,19 @@ const MessageList = styled.div` overflow-y: auto; flex: 1; margin: 10px 0; - height: 60vh; scrollbar-width: none; `; const ChatRoomContainer = styled.div` display: flex; flex-direction: column; - width: 95%; max-width: 50vh; - max-height: 90vh; - margin: 40px auto; + max-height: 100vh; + margin: 0px auto; background-color: #ffffff; border-radius: 8px; padding: 5px; - min-height: 90vh; + min-height: 100vh; `; const MessageWrapper = styled.div<{ sender: string; currentUser: string }>` @@ -137,7 +131,7 @@ const MessageWrapper = styled.div<{ sender: string; currentUser: string }>` align-self: ${props => (props.sender === props.currentUser ? "flex-end" : "flex-start")}; `; -const MessageBubble = styled.div<{ sender: string; currentUser: string; shouldDisplayProfile: boolean; }>` +const MessageBubble = styled.div<{ sender: string; currentUser: string; shouldDisplayProfile: boolean }>` padding: 10px 16px; border-radius: 20px; background-color: ${props => (props.sender === props.currentUser ? "#101010" : "#F1F1F1")}; @@ -156,11 +150,6 @@ const ProfileImage = styled.div` height: 32px; border-radius: 50%; overflow: hidden; - - & > svg { - width: 100%; - height: 100%; - } `; const Time = styled.div` diff --git a/src/components/StatusBar.tsx b/src/components/StatusBar.tsx index 77ee633..4eb54bf 100644 --- a/src/components/StatusBar.tsx +++ b/src/components/StatusBar.tsx @@ -2,17 +2,16 @@ import React, { useState, useEffect } from 'react'; import styled from 'styled-components'; import { ReactComponent as Indicators } from '../assets/images/indicators.svg'; - const StatusBar: React.FC = () => { - const [currentTime, setCurrentTime] = useState(getCurrentTime()); + const [currentTime, setCurrentTime] = useState(getCurrentTime()); + + useEffect(() => { + const interval = setInterval(() => { + setCurrentTime(getCurrentTime()); + }, 60000); + return () => clearInterval(interval); + }, []); - - useEffect(() => { - const interval = setInterval(() => { - setCurrentTime(getCurrentTime()); - }, 60000); - return () => clearInterval(interval); - }, []); return ( @@ -21,31 +20,31 @@ const StatusBar: React.FC = () => { - ); }; export default StatusBar; + function getCurrentTime(): string { - return new Date().toLocaleTimeString([], { hour12: false, hour: '2-digit', minute: '2-digit' }); - } + return new Date().toLocaleTimeString([], { hour12: false, hour: '2-digit', minute: '2-digit' }); +} const StatusBarContainer = styled.div` - width: 100%; - height: 100%; - padding: 21px 18.70px 10px 40px; - background: #ffffff + padding: 10px 18px 10px 12px; + background: #ffffff; backdrop-filter: blur(5px); display: flex; justify-content: space-between; align-items: center; `; + const TimeContainer = styled.div` display: flex; justify-content: center; align-items: center; + margin-left: 15px; `; const TimeText = styled.div` @@ -68,4 +67,3 @@ const Indicator = styled(Indicators)` width: 76px; height: 17px; `; - diff --git a/src/index.css b/src/index.css index 94ff963..86d620e 100644 --- a/src/index.css +++ b/src/index.css @@ -5,6 +5,7 @@ body { sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; + height: 100%; } ::-webkit-scrollbar { display: none; From 10b0fb39800dfe81c6d88684533c3e1fd781e6c2 Mon Sep 17 00:00:00 2001 From: Jiseok Date: Fri, 27 Oct 2023 17:57:45 +0900 Subject: [PATCH 15/32] add nevigate --- src/components/ChatHeader.tsx | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/components/ChatHeader.tsx b/src/components/ChatHeader.tsx index d8831db..b9428f2 100644 --- a/src/components/ChatHeader.tsx +++ b/src/components/ChatHeader.tsx @@ -3,18 +3,24 @@ import styled from 'styled-components'; import { ReactComponent as CallIcons } from '../assets/images/Phone.svg'; import { ReactComponent as VideoIcons } from '../assets/images/Videocamera.svg'; import { ReactComponent as LeftArrowIcon } from '../assets/images/Arrow-Left.svg'; +import { useNavigate } from 'react-router-dom'; + interface ChatHeaderProps { chatName: string; onSwitchPosition?: () => void; } const ChatHeader: React.FC = ({ chatName, onSwitchPosition }) => { - + const navigate = useNavigate(); + + const navigateToFriendList = () => { + navigate("/friends"); + }; return ( - + - - {chatName} + + {chatName} @@ -33,11 +39,10 @@ const ChatHeaderContainer = styled.div` display: flex; justify-content: space-between; align-items: center; - padding: 10px 18px 10px 12px; - background: #ffffff + padding: 10px 0px 10px 3px; + background: rgba(255, 255, 255, 0.90); backdrop-filter: blur(4px); gap: 12px; - `; const Wrapper = styled.div` From ad162b2669f9b48f8c08968bfeaa85c377616269 Mon Sep 17 00:00:00 2001 From: Jiseok Date: Thu, 2 Nov 2023 12:26:22 +0900 Subject: [PATCH 16/32] [feat]Statusbar,Searchbar --- components/StatusBar.tsx | 69 ++++++++++++++++++++++++++++++++ src/App.tsx | 31 +++++++++++--- src/components/UserSearchBar.tsx | 63 +++++++++++++++++++++++++++++ 3 files changed, 158 insertions(+), 5 deletions(-) create mode 100644 components/StatusBar.tsx create mode 100644 src/components/UserSearchBar.tsx diff --git a/components/StatusBar.tsx b/components/StatusBar.tsx new file mode 100644 index 0000000..576b995 --- /dev/null +++ b/components/StatusBar.tsx @@ -0,0 +1,69 @@ +import React, { useState, useEffect } from 'react'; +import styled from 'styled-components'; +import { ReactComponent as Indicators } from '../assets/images/indicators.svg'; + +const StatusBar: React.FC = () => { + const [currentTime, setCurrentTime] = useState(getCurrentTime()); + + useEffect(() => { + const interval = setInterval(() => { + setCurrentTime(getCurrentTime()); + }, 60000); + return () => clearInterval(interval); + }, []); + + return ( + + + {currentTime} + + + + + + + ); +}; + +export default StatusBar; + +function getCurrentTime(): string { + return new Date().toLocaleTimeString([], { hour12: false, hour: '2-digit', minute: '2-digit' }); +} + +const StatusBarContainer = styled.div` + padding: 10px 0px 10px 1px; + background: #ffffff; + backdrop-filter: blur(5px); + display: flex; + justify-content: space-between; + align-items: center; +`; + +const TimeContainer = styled.div` + display: flex; + justify-content: center; + align-items: center; + margin-left: 15px; +`; + +const TimeText = styled.div` + text-align: center; + color: #101010; + font-size: 17px; + font-weight: 600; + line-height: 17px; + word-wrap: break-word; +`; + +const IconContainer = styled.div` + display: flex; + justify-content: flex-end; + align-items: center; + gap: 6px; +`; + +const Indicator = styled(Indicators)` + width: 76px; + height: 17px; +`; diff --git a/src/App.tsx b/src/App.tsx index 5a6ea86..7349fc0 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,16 +1,37 @@ -import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; -// import Chat from "./pages/Chat/Chat"; -import ChatRoom from "./components/ChatRoom"; +import { BrowserRouter as Router, Routes, Route,useNavigate } from "react-router-dom"; + +import UserProfile from "./pages/UserProfile"; +import ChatRoom from "./pages/ChatRoom"; +import FriendsList from "./pages/FriendsList"; +import { Friend } from './pages/FriendsList'; +import ChatList from "./pages/ChatList"; + function App() { + return ( - } /> {} + } /> + } /> + } /> + } /> + } /> + ); } -export default App; +function FriendsListWithNavigation() { + const navigate = useNavigate(); + + const handleSelectFriend = (friend: Friend) => { + navigate(`/chat/${friend.id}`); + }; + + return ; +} + +export default App; diff --git a/src/components/UserSearchBar.tsx b/src/components/UserSearchBar.tsx new file mode 100644 index 0000000..6540b4a --- /dev/null +++ b/src/components/UserSearchBar.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import styled from 'styled-components'; +import { ReactComponent as Magnifer } from '../assets/images/Magnifer.svg'; + +interface UserSearchBarProps { + searchTerm: string; + setSearchTerm: (term: string) => void; +} + +const UserSearchBar: React.FC = ({ searchTerm, setSearchTerm }) => { + return ( + + + + setSearchTerm(e.target.value)} + placeholder="검색" + /> + + + ); +}; + +const SearchContainer = styled.div` + width: 100%; + height: 100%; + padding-top: 8px; + padding-bottom: 8px; + background: white; + justify-content: center; + align-items: center; + display: inline-flex; + margin-bottom: 10px; +`; + +const SearchBarWrapper = styled.div` + width: 100%; + height: 35px; + background: #F1F1F1; + border-radius: 8px; + display: flex; + align-items: center; + padding-left: 0px; +`; + +const SearchInput = styled.input` + flex: 1; + height: 100%; + border: none; + outline: none; + background: transparent; + font-size: 14px; + color: #909090; +`; + +const SearchIcon = styled(Magnifer)` + width: 30px; + height: 17px; +`; + +export default UserSearchBar; From 9a5e0b4c510f7ec2698fd3b16be45f86172de4ab Mon Sep 17 00:00:00 2001 From: Jiseok Date: Thu, 2 Nov 2023 12:26:57 +0900 Subject: [PATCH 17/32] [chore] --- components/ChatHeader.tsx | 88 +++++++++++++++++++++++++++++++ components/ChatInput.tsx | 107 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 195 insertions(+) create mode 100644 components/ChatHeader.tsx create mode 100644 components/ChatInput.tsx diff --git a/components/ChatHeader.tsx b/components/ChatHeader.tsx new file mode 100644 index 0000000..b9428f2 --- /dev/null +++ b/components/ChatHeader.tsx @@ -0,0 +1,88 @@ +import React from 'react'; +import styled from 'styled-components'; +import { ReactComponent as CallIcons } from '../assets/images/Phone.svg'; +import { ReactComponent as VideoIcons } from '../assets/images/Videocamera.svg'; +import { ReactComponent as LeftArrowIcon } from '../assets/images/Arrow-Left.svg'; +import { useNavigate } from 'react-router-dom'; + +interface ChatHeaderProps { + chatName: string; + onSwitchPosition?: () => void; +} + + const ChatHeader: React.FC = ({ chatName, onSwitchPosition }) => { + const navigate = useNavigate(); + + const navigateToFriendList = () => { + navigate("/friends"); + }; + return ( + + + + {chatName} + + + + + + + + + + + ); +}; +export default ChatHeader; + +const ChatHeaderContainer = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px 0px 10px 3px; + background: rgba(255, 255, 255, 0.90); + backdrop-filter: blur(4px); + gap: 12px; +`; + +const Wrapper = styled.div` + display: flex; + align-items: center; + gap: 4px; +`; + + +const UserName = styled.div` + color: #101010; + font-size: 18px; + font-weight: 600; + line-height: 18px; + word-wrap: break-word; +`; + +const Icons = styled.div` + display: flex; + justify-content: flex-start; + align-items: center; + gap: 12px; +`; + +const IconWrapper = styled.div` + width: 24px; + height: 24px; + position: relative; +`; + +const CallIcon = styled(CallIcons)` + width: 24px; + height: 24px; +`; + +const VideoIcon = styled(VideoIcons)` + width: 24px; + height: 24px; +`; +const ArrowLeftIcon = styled(LeftArrowIcon)` + width: 24px; + height: 24px; +`; diff --git a/components/ChatInput.tsx b/components/ChatInput.tsx new file mode 100644 index 0000000..0b26099 --- /dev/null +++ b/components/ChatInput.tsx @@ -0,0 +1,107 @@ +import React, { useState } from 'react'; +import styled from 'styled-components'; + +import { ReactComponent as Camera } from '../assets/images/Camera.svg'; +import { ReactComponent as Gallery } from '../assets/images/Gallery.svg'; +import { ReactComponent as Mic } from '../assets/images/Microphone.svg'; +import { ReactComponent as Smile } from '../assets/images/Sticker-Smile.svg'; +import { ReactComponent as Low } from '../assets/images/Indicator_low.svg'; + +interface ChatInputProps { + onSend: (message: string) => void; +} + +const ChatInput: React.FC = ({ onSend }) => { + const [inputValue, setInputValue] = useState(''); + + const handleSendMessage = (e: React.FormEvent) => { + e.preventDefault(); + if (inputValue.trim()) { + onSend(inputValue); + setInputValue(''); + } + }; + + return ( + + + + + + setInputValue(e.target.value)} + /> + + + + + + + ); +}; + +export default ChatInput; + +const InputContainer = styled.div` + display: flex; + flex-direction: column; +`; + +const StyledInputBar = styled.form` + display: flex; + justify-content: center; + background: white; +`; + +const TextInputContainer = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + height: 44px; + padding: 0 10px; + background: #F1F1F1; + border-radius: 22px; + border: 1px #CCCCCC solid; +`; + +const Input = styled.input` + flex-grow: 1; + margin: 0 10px; + height: 100%; + border: none; + outline: none; + border-radius: 22px; + background: transparent; + &:focus { + box-shadow: none; + } +`; + +const CameraIcon = styled(Camera)` + width: 24px; + height: 24px; + margin-right: 12px; +`; + +const GalleryIcon = styled(Gallery)` + width: 24px; + height: 24px; +`; + +const MicIcon = styled(Mic)` + width: 24px; + height: 24px; +`; + +const SmileIcon = styled(Smile)` + width: 24px; + height: 24px; + margin-left: 12px; +`; + +const LowIcon = styled(Low)` + width: 100%; + height: 21px; +`; From 1bd632e3b735dd31d16f0424ff78f9558f44de66 Mon Sep 17 00:00:00 2001 From: Jiseok Date: Thu, 2 Nov 2023 12:27:56 +0900 Subject: [PATCH 18/32] [feat]statusbar --- 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 4eb54bf..de26982 100644 --- a/src/components/StatusBar.tsx +++ b/src/components/StatusBar.tsx @@ -32,7 +32,7 @@ function getCurrentTime(): string { } const StatusBarContainer = styled.div` - padding: 10px 18px 10px 12px; + padding: 10px 0px 10px 1px; background: #ffffff; backdrop-filter: blur(5px); display: flex; @@ -66,4 +66,4 @@ const IconContainer = styled.div` const Indicator = styled(Indicators)` width: 76px; height: 17px; -`; +`; \ No newline at end of file From 0292ffe9ec998c3b731747820e8375448fb48f02 Mon Sep 17 00:00:00 2001 From: Jiseok Date: Thu, 2 Nov 2023 12:29:19 +0900 Subject: [PATCH 19/32] [chore] --- src/components/IndicateBar.tsx | 91 ++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 src/components/IndicateBar.tsx diff --git a/src/components/IndicateBar.tsx b/src/components/IndicateBar.tsx new file mode 100644 index 0000000..bd0d7d0 --- /dev/null +++ b/src/components/IndicateBar.tsx @@ -0,0 +1,91 @@ +import React, { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { ReactComponent as Friends } from '../assets/images/Friends.svg'; +import { ReactComponent as Friends_black } from '../assets/images/Friends-black.svg'; +import { ReactComponent as Chat_black } from '../assets/images/Chat-black.svg'; +import { ReactComponent as Chat } from '../assets/images/Chat.svg'; +import { ReactComponent as User } from '../assets/images/User.svg'; +import { ReactComponent as User_black } from '../assets/images/User-black.svg'; +import { ReactComponent as Safari } from '../assets/images/Safari.svg'; +import { ReactComponent as Safari_black } from '../assets/images/Safari-Black.svg'; +import styled from 'styled-components'; + +interface IndicateBarProps { + activeIcon: 'friends' | 'chat' | 'safari' | 'user'; + } + +const IndicateBar: React.FC = ({ activeIcon }) => { + + const navigate = useNavigate(); + + const goToFriendList = () => { + navigate('/friends'); + }; + const goToChatList = () => { + navigate('/chatlist'); + } + const goToUserProfile = () => { + navigate('/userprofile') + } + const goToGoogle = () => { + if (window.confirm("구글 ㄱㄱ?")) { + window.open("https://www.google.com", "_blank"); + } + } + return ( + + {activeIcon === 'friends' ? + : + } + {activeIcon === 'chat' ? : } + {activeIcon === 'safari' ? : } + {activeIcon === 'user' ? : } + + + ); + } +export default IndicateBar; +const IconContainer = styled.div` +width: 100%; +height: 100%; +justify-content: space-between; +align-items: center; +display: flex; +gap: 10px; +padding: 10px 60px 10px 60px; +flex-direction: row; +border-top: 1px solid #e0e0e0; +`; + +const Friend = styled(Friends)` + width: 20px; + height: 20px; +`; +const Friend_black = styled(Friends_black)` + width: 20px; + height: 20px; +`; +const more = styled(User)` + width: 20px; + height: 20px; +`; +const more_black = styled(User_black)` + width: 20px; + height: 20px; +`; +const chat = styled(Chat)` + width: 20px; + height: 20px; +`; +const chat_black = styled(Chat_black)` + width: 20px; + height: 20px; +`; +const safari = styled(Safari)` + width: 20px; + height: 20px; +`; +const safari_black = styled(Safari_black)` + width: 20px; + height: 20px; +`; \ No newline at end of file From e95aff0e01b395be6328412ec693dd7b88242e9e Mon Sep 17 00:00:00 2001 From: Jiseok Date: Thu, 2 Nov 2023 12:30:45 +0900 Subject: [PATCH 20/32] [feat] --- pages/Chat/Chat.tsx | 5 +++ pages/FriendsList.tsx | 67 +++++++++++++++++++++++++++++++ pages/UserProfile.tsx | 91 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 163 insertions(+) create mode 100644 pages/Chat/Chat.tsx create mode 100644 pages/FriendsList.tsx create mode 100644 pages/UserProfile.tsx diff --git a/pages/Chat/Chat.tsx b/pages/Chat/Chat.tsx new file mode 100644 index 0000000..782be14 --- /dev/null +++ b/pages/Chat/Chat.tsx @@ -0,0 +1,5 @@ +const Chat = () =>{ + return
Chat
+}; + +export default Chat; \ No newline at end of file diff --git a/pages/FriendsList.tsx b/pages/FriendsList.tsx new file mode 100644 index 0000000..a37217b --- /dev/null +++ b/pages/FriendsList.tsx @@ -0,0 +1,67 @@ +import React, { useState, useEffect } from 'react'; +import styled from 'styled-components'; +import usersData from '../assets/datas/dummy.json'; +import { ReactComponent as ProfileIcon } from '../assets/images/Profile-Icon.svg'; +import { Link } from 'react-router-dom'; + +export interface Friend { + id: number; + name: string; + profileImage: string; +} + +interface FriendsListProps { + onSelectFriend: (friend: Friend) => void; +} + +const FriendsList: React.FC = ({ onSelectFriend }) => { + const [users, setUsers] = useState([]); + + useEffect(() => { + // 사용자 데이터를 설정 + setUsers(usersData.users); + }, []); + + return ( + + {users.map((friend) => ( + + onSelectFriend(friend)}> + {friend.profileImage === 'ProfileIcon' ? : } + {friend.name} + + + ))} + + ); +}; + +const FriendListContainer = styled.div` + width: 30%; // Adjust based on preference + padding: 20px; + overflow-y: auto; +`; + +const FriendItem = styled.div` + display: flex; + align-items: center; + margin: 10px 0; + cursor: pointer; +`; + +const ProfileImage = styled.div` + width: 32px; + height: 32px; + border-radius: 50%; + overflow: hidden; + margin-right: 10px; +`; + +const ProfileImageStyled = styled.img` + width: 32px; + height: 32px; + border-radius: 50%; + margin-right: 10px; +`; + +export default FriendsList; diff --git a/pages/UserProfile.tsx b/pages/UserProfile.tsx new file mode 100644 index 0000000..e54c351 --- /dev/null +++ b/pages/UserProfile.tsx @@ -0,0 +1,91 @@ +// src/pages/UserProfile.tsx +import React from 'react'; +import styled from 'styled-components'; +import StatusBar from '../components/StatusBar'; +import { ReactComponent as InstagramIcon } from '../assets/images/Instagram.svg'; // Assuming you have this icon +import { ReactComponent as GitHubIcon } from '../assets/images/Github.svg'; // Assuming you have this icon +import { ReactComponent as ProfileIcon } from '../assets/images/Profile-Icon.svg'; // Assuming you have this icon + +const UserProfile: React.FC = () => { + return ( + + + + + {/* Placeholder for user image */} + 최유선 + yoonseor724@gmail.com + + + + + + Instagram + + + + GitHub + + + + ); +}; + +export default UserProfile; + +const ProfileContainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; + gap: 16px; + background-color: #ffffff; + border-radius: 8px; + padding: 10px; + max-width: 300px; // Adjust based on your requirements + margin: 20px auto; + +`; + +const ProfileDetails = styled.div` + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; +`; + +const UserName = styled.span` + font-size: 16px; + color: #101010; + font-weight: bold; +`; + +const UserEmail = styled.span` + font-size: 14px; + color: #909090; +`; + +const ProfileImage = styled.div` + width: 50px; // Adjust based on your requirements + height: 50px; + border-radius: 50%; + background-color: #f1f1f1; // Placeholder color +`; + +const SocialLinks = styled.div` + display: flex; + flex-direction: column; + gap: 10px; + width: 100%; +`; + +const LinkItem = styled.div` + display: flex; + align-items: center; + gap: 8px; + font-size: 14px; + color: #101010; + cursor: pointer; + &:hover { + text-decoration: underline; + } +`; From dfb3ea1b224fb093234f3fcd2b6ac57f666910af Mon Sep 17 00:00:00 2001 From: Jiseok Date: Thu, 2 Nov 2023 12:32:18 +0900 Subject: [PATCH 21/32] [feat]Chatlist --- src/pages/ChatList.tsx | 139 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 src/pages/ChatList.tsx diff --git a/src/pages/ChatList.tsx b/src/pages/ChatList.tsx new file mode 100644 index 0000000..b835f0f --- /dev/null +++ b/src/pages/ChatList.tsx @@ -0,0 +1,139 @@ +import React, { useState, useEffect } from 'react'; +import styled from 'styled-components'; +import { Link } from 'react-router-dom'; +import usersData from '../assets/datas/dummy.json'; +import StatusBar from '../components/StatusBar'; +import IndicateBar from '../components/IndicateBar'; +import { ReactComponent as ProfileIcon } from '../assets/images/Profile-Icon.svg'; +import ChatHeader from '../components/ChatHeader'; +import UserSearchBar from '../components/UserSearchBar'; + +interface ChatSummary { + friendId: string; + lastMessage: string; + timestamp: string; +} + +const ChatList: React.FC = () => { + const [chatSummaries, setChatSummaries] = useState([]); + const [searchTerm, setSearchTerm] = useState(""); + + useEffect(() => { + const keys = Object.keys(localStorage).filter(key => key.startsWith('chatMessages_')); + const summaries = keys.map(key => { + const messages = JSON.parse(localStorage.getItem(key) || '[]'); + const lastMessage = messages[messages.length - 1]; + return { + friendId: key.split('_')[1], + lastMessage: lastMessage?.content || '', + timestamp: lastMessage?.timestamp || '', + }; + }) + .filter(summary => summary.lastMessage); + summaries.sort((a, b) => b.timestamp.localeCompare(a.timestamp)); + setChatSummaries(summaries); + }, []); + + + const getUserByFriendId = (friendId: string) => { + return usersData.users.find(user => user.id.toString() === friendId); + }; + + const filteredSummaries = chatSummaries.filter(summary => { + const user = getUserByFriendId(summary.friendId); + return user?.name.toLowerCase().includes(searchTerm.toLowerCase()); + }); + + return ( + + + + + + {filteredSummaries.map((summary) => { + const user = getUserByFriendId(summary.friendId); + return ( + + + {user?.profileImage === 'ProfileIcon' && } + + {user?.name} + {summary.lastMessage} + + {summary.timestamp} + + + ); + })} + + + + + ); +}; + +const ChatSummariesWrapper = styled.div` + flex-grow: 1; + overflow-y: auto; +`; +const StyledLink = styled(Link)` + text-decoration: none; +`; +const TextContent = styled.div` + display: flex; + flex-direction: column; + margin-right: auto; +`; + +const Message = styled.div` + color: #606060; + font-size: 12px; + line-height: 15px; + word-wrap: break-word; +`; +const UserName = styled.div` + color: #101010; + font-size: 13px; + font-weight: 600; + line-height: 13px; + word-wrap: break-word; +`; + +const ProfileImageIcon = styled(ProfileIcon)` + width: 40px; + height: 40px; + border-radius: 50%; + margin-right: 10px; +`; + +const ChatListContainer = styled.div` +display: flex; +flex-direction: column; +max-width: 50vh; +max-height: 100vh; +margin: 0px auto; +background-color: #ffffff; +border-radius: 8px; +padding: 12px; +min-height: 100vh; +overflow-y: auto; +`; + +const ChatListItem = styled.div` + display: flex; + align-items: center; + gap: 10px; + position: relative; + padding: 10px 0; + cursor: pointer; +`; + +const Timestamp = styled.div` + font-size: 10px; + color: #CCCCCC; + align-self: flex-end; + line-height: 10px; + margin-bottom: 20px; +`; + +export default ChatList; From 49ecf4f25e81565a8ad8526e3706aedba10cdbe5 Mon Sep 17 00:00:00 2001 From: Jiseok Date: Thu, 2 Nov 2023 12:33:13 +0900 Subject: [PATCH 22/32] [chore] add data --- src/assets/datas/dummy.json | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/assets/datas/dummy.json b/src/assets/datas/dummy.json index f309389..ea9c8db 100644 --- a/src/assets/datas/dummy.json +++ b/src/assets/datas/dummy.json @@ -1,5 +1,10 @@ { "users": [ + { + "id": 0, + "name": "최윤서", + "profileImage": "ProfileIcon" + }, { "id": 1, "name": "지석", @@ -7,9 +12,19 @@ }, { "id": 2, - "name": "윤서", + "name": "형찬", "profileImage": "ProfileIcon" - } + }, + { + "id": 3, + "name": "정민", + "profileImage": "ProfileIcon" + }, + { + "id": 4, + "name": "태영", + "profileImage": "ProfileIcon" + } ] } \ No newline at end of file From 43ee22116b48e3b8880d846845b8d471222ad4d8 Mon Sep 17 00:00:00 2001 From: Jiseok Date: Thu, 2 Nov 2023 12:34:35 +0900 Subject: [PATCH 23/32] =?UTF-8?q?[feat]=EC=9D=B4=EB=8F=99=EC=9C=84?= =?UTF-8?q?=EC=B9=98=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ChatHeader.tsx | 37 +++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/src/components/ChatHeader.tsx b/src/components/ChatHeader.tsx index b9428f2..962f3b6 100644 --- a/src/components/ChatHeader.tsx +++ b/src/components/ChatHeader.tsx @@ -3,25 +3,30 @@ import styled from 'styled-components'; import { ReactComponent as CallIcons } from '../assets/images/Phone.svg'; import { ReactComponent as VideoIcons } from '../assets/images/Videocamera.svg'; import { ReactComponent as LeftArrowIcon } from '../assets/images/Arrow-Left.svg'; -import { useNavigate } from 'react-router-dom'; +import { useNavigate, useLocation } from 'react-router-dom'; interface ChatHeaderProps { chatName: string; onSwitchPosition?: () => void; } - const ChatHeader: React.FC = ({ chatName, onSwitchPosition }) => { - const navigate = useNavigate(); +const ChatHeader: React.FC = ({ chatName, onSwitchPosition }) => { + + const navigate = useNavigate(); + const location = useLocation(); - const navigateToFriendList = () => { - navigate("/friends"); + const navigateToFriendList = () => { + // navigate("/chatlist"); + navigate(-1); }; - return ( - - - - {chatName} - + const isChatPage = location.pathname.startsWith('/chat/'); + return ( + + + {isChatPage && } + {chatName} + + {isChatPage && ( @@ -30,10 +35,10 @@ interface ChatHeaderProps { - - ); + )} + + ); }; -export default ChatHeader; const ChatHeaderContainer = styled.div` display: flex; @@ -51,7 +56,6 @@ const Wrapper = styled.div` gap: 4px; `; - const UserName = styled.div` color: #101010; font-size: 18px; @@ -82,7 +86,10 @@ const VideoIcon = styled(VideoIcons)` width: 24px; height: 24px; `; + const ArrowLeftIcon = styled(LeftArrowIcon)` width: 24px; height: 24px; `; + +export default ChatHeader; From ba284690c19ba8493926a319380cc6a51079525c Mon Sep 17 00:00:00 2001 From: Jiseok Date: Thu, 2 Nov 2023 12:35:43 +0900 Subject: [PATCH 24/32] =?UTF-8?q?[chore]=ED=94=84=EB=A1=9C=ED=95=84?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=EB=A7=81=ED=81=AC=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/UserProfile.tsx | 120 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 src/pages/UserProfile.tsx diff --git a/src/pages/UserProfile.tsx b/src/pages/UserProfile.tsx new file mode 100644 index 0000000..e9f15bb --- /dev/null +++ b/src/pages/UserProfile.tsx @@ -0,0 +1,120 @@ +import React from 'react'; +import styled from 'styled-components'; +import StatusBar from '../components/StatusBar'; +import IndicateBar from '../components/IndicateBar'; +import { ReactComponent as ProfileIconSrc } from '../assets/images/Profile-Icon.svg'; +import { ReactComponent as LinkIconSrc } from '../assets/images/Link.svg'; +import ChatHeader from '../components/ChatHeader'; +import instagramIconSrc from '../assets/images/Instagram1.svg'; +import githubIconSrc from '../assets/images/Github1.svg'; +const UserProfile: React.FC = () => { + const goToGithub = () => { + if (window.confirm("깃허브 ㄱㄱ?")) { + window.open("https://www.github.com", "_blank"); + } + } + const goToinsta = () => { + if (window.confirm("인스타 ㄱㄱ?")) { + window.open("https://www.instagram.com", "_blank"); + } + } + return ( + + + + + + 최윤서 + yoonseor724@gmail.com + + + + + Instagram + Instagram + + + + Github + + GitHub + + + + + + + + ); +}; + +export default UserProfile; + +const ProfileWrapper = styled.div` + flex-grow: 1; + overflow-y: auto; +`; + +const ProfileIconStyled = styled(ProfileIconSrc)` + width: 84px; + height: 84px; +`; + +const LinkIconStyled = styled(LinkIconSrc)` + width: 24px; + height: 24px; + cursor: pointer; +`; + +const ProfileContainer = styled.div` + display: flex; + flex-direction: column; + max-width: 50vh; + max-height: 100vh; + margin: 0px auto; + background-color: #ffffff; + border-radius: 8px; + padding: 12px; + min-height: 100vh; + overflow-y: auto; +`; + +const ProfileDetails = styled.div` + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + margin: 24px 0; +`; + +const UserName = styled.div` + font-size: 18px; + color: #101010; + font-weight: bold; +`; +const LinkName = styled.div` + font-size: 14px; + color: #101010; + font-weight: bold; +`; +const UserEmail = styled.span` + font-size: 14px; + color: #909090; +`; + +const SocialLinks = styled.div` + display: flex; + flex-direction: column; + gap: 10px; + width: 100%; +`; + +const LinkItem = styled.div` + display: flex; + align-items: center; + gap: 8px; + justify-content: space-between; + font-size: 14px; + margin: 14px 0; + color: #101010; +`; From 293790844081beb970bd3b612cac072fd4f65f3c Mon Sep 17 00:00:00 2001 From: Jiseok Date: Thu, 2 Nov 2023 12:36:12 +0900 Subject: [PATCH 25/32] [chore] --- src/components/ChatInput.tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/components/ChatInput.tsx b/src/components/ChatInput.tsx index 0b26099..58a8562 100644 --- a/src/components/ChatInput.tsx +++ b/src/components/ChatInput.tsx @@ -1,11 +1,9 @@ import React, { useState } from 'react'; import styled from 'styled-components'; - import { ReactComponent as Camera } from '../assets/images/Camera.svg'; import { ReactComponent as Gallery } from '../assets/images/Gallery.svg'; import { ReactComponent as Mic } from '../assets/images/Microphone.svg'; import { ReactComponent as Smile } from '../assets/images/Sticker-Smile.svg'; -import { ReactComponent as Low } from '../assets/images/Indicator_low.svg'; interface ChatInputProps { onSend: (message: string) => void; @@ -36,7 +34,6 @@ const ChatInput: React.FC = ({ onSend }) => { - ); }; @@ -101,7 +98,4 @@ const SmileIcon = styled(Smile)` margin-left: 12px; `; -const LowIcon = styled(Low)` - width: 100%; - height: 21px; -`; + From dbd26d930d8759417b35feaa12ccb497c753a915 Mon Sep 17 00:00:00 2001 From: Jiseok Date: Thu, 2 Nov 2023 12:39:24 +0900 Subject: [PATCH 26/32] =?UTF-8?q?[feat]=ED=8E=98=EC=9D=B4=EC=A7=80=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/{components => pages}/ChatRoom.tsx | 46 ++++++---- src/pages/FriendsList.tsx | 116 +++++++++++++++++++++++++ 2 files changed, 146 insertions(+), 16 deletions(-) rename src/{components => pages}/ChatRoom.tsx (79%) create mode 100644 src/pages/FriendsList.tsx diff --git a/src/components/ChatRoom.tsx b/src/pages/ChatRoom.tsx similarity index 79% rename from src/components/ChatRoom.tsx rename to src/pages/ChatRoom.tsx index 23514f4..461007d 100644 --- a/src/components/ChatRoom.tsx +++ b/src/pages/ChatRoom.tsx @@ -1,10 +1,11 @@ import React, { useState, useRef, useEffect } from 'react'; -import ChatHeader from './ChatHeader'; -import ChatInput from './ChatInput'; +import ChatHeader from '../components/ChatHeader'; +import ChatInput from '../components/ChatInput'; import usersData from '../assets/datas/dummy.json'; -import StatusBar from './StatusBar'; +import StatusBar from '../components/StatusBar'; import styled from 'styled-components'; import { ReactComponent as ProfileIcon } from '../assets/images/Profile-Icon.svg'; +import { useParams } from 'react-router-dom'; interface Message { id: number; @@ -13,11 +14,24 @@ interface Message { timestamp: string; displayTime: boolean; } +interface ChatRoomParams { + friendId: string; + [key: string]: string | undefined; +} const ChatRoom: React.FC = () => { - const savedMessages = JSON.parse(localStorage.getItem('chatMessages') || '[]'); - const [messages, setMessages] = useState(savedMessages); + + const getLocalStorageKey = () => { + return `chatMessages_${friendId}`; + } + + const { friendId } = useParams(); + const selectedFriend = usersData.users.find(user => user.id.toString() === friendId) || usersData.users[0]; const [currentUser, setCurrentUser] = useState(usersData.users[0]); + + const savedMessages = JSON.parse(localStorage.getItem(getLocalStorageKey()) || '[]'); + const friendMessages = savedMessages.filter((msg: Message) => msg.sender === selectedFriend.name || msg.sender === currentUser.name); + const [messages, setMessages] = useState(friendMessages); const messagesEndRef = useRef(null); @@ -28,8 +42,8 @@ const ChatRoom: React.FC = () => { }, [messages]); useEffect(() => { - localStorage.setItem('chatMessages', JSON.stringify(messages)); - }, [messages]); + localStorage.setItem(getLocalStorageKey(), JSON.stringify(messages)); + }, [messages, friendId]); const handleSendMessage = (content: string) => { const newMessage: Message = { @@ -55,10 +69,10 @@ const ChatRoom: React.FC = () => { }; const handleSwitchPosition = () => { - setCurrentUser(prev => (prev.name === usersData.users[0].name ? usersData.users[1] : usersData.users[0])); + setCurrentUser(prev => (prev.name === usersData.users[0].name ? selectedFriend : usersData.users[0])); }; - const oppositeUser = currentUser.name === usersData.users[0].name ? usersData.users[1] : usersData.users[0]; + const oppositeUser = currentUser.name === usersData.users[0].name ? selectedFriend : usersData.users[0]; return ( @@ -94,7 +108,6 @@ const ChatRoom: React.FC = () => { }; export default ChatRoom; - const ProfileContainer = styled.div` display: flex; flex-direction: column; @@ -117,7 +130,7 @@ const ChatRoomContainer = styled.div` margin: 0px auto; background-color: #ffffff; border-radius: 8px; - padding: 5px; + padding: 12px; min-height: 100vh; `; @@ -125,8 +138,8 @@ const MessageWrapper = styled.div<{ sender: string; currentUser: string }>` display: flex; align-items: center; flex-direction: ${props => (props.sender === props.currentUser ? "row-reverse" : "row")}; - gap: 12px; - margin: 12px 0; + gap: 4px; + margin: 4px 0; max-width: 100%; align-self: ${props => (props.sender === props.currentUser ? "flex-end" : "flex-start")}; `; @@ -136,13 +149,14 @@ const MessageBubble = styled.div<{ sender: string; currentUser: string; shouldDi border-radius: 20px; background-color: ${props => (props.sender === props.currentUser ? "#101010" : "#F1F1F1")}; color: ${props => (props.sender === props.currentUser ? "#F1F1F1" : "#101010")}; - margin-left: ${props => (!props.shouldDisplayProfile && props.sender !== props.currentUser) ? "44px" : "0"}; + margin-left: ${props => (!props.shouldDisplayProfile && props.sender !== props.currentUser) ? "36px" : "0"}; font-size: 14px; font-weight: 400; line-height: 19.60px; box-sizing: border-box; max-width: fit-content; word-break: break-all; + white-space: pre-wrap; `; const ProfileImage = styled.div` @@ -157,5 +171,5 @@ const Time = styled.div` font-size: 7px; font-weight: 500; line-height: 10px; - margin-top: 12px; -`; + margin-top: 30px; +`; \ No newline at end of file diff --git a/src/pages/FriendsList.tsx b/src/pages/FriendsList.tsx new file mode 100644 index 0000000..ac0819e --- /dev/null +++ b/src/pages/FriendsList.tsx @@ -0,0 +1,116 @@ +import React, { useState, useEffect } from 'react'; +import styled from 'styled-components'; +import usersData from '../assets/datas/dummy.json'; +import StatusBar from '../components/StatusBar'; +import { ReactComponent as ProfileIcon } from '../assets/images/Profile-Icon.svg'; +import IndicateBar from '../components/IndicateBar'; +import { Link } from 'react-router-dom'; +import ChatHeader from '../components/ChatHeader'; +import UserSearchBar from '../components/UserSearchBar'; + +export interface Friend { + id: number; + name: string; + profileImage: string; +} + +interface FriendsListProps { + onSelectFriend: (friend: Friend) => void; +} + +const FriendsList: React.FC = ({ onSelectFriend }) => { + const [users, setUsers] = useState([]); + const [searchTerm, setSearchTerm] = useState(""); + + useEffect(() => { + setUsers(usersData.users); + }, []); + + const filteredUsers = users.filter(user => user.name.toLowerCase().includes(searchTerm.toLowerCase())); + + return ( + + + + + + {filteredUsers.map((friend, index) => ( + + {index === 0 ? ( + onSelectFriend(friend)}> + {friend.profileImage === 'ProfileIcon' ? : } + {friend.name} + + ) : ( + onSelectFriend(friend)}> + {friend.profileImage === 'ProfileIcon' ? : } + {friend.name} + + )} + + ))} + + + + ); +}; + +const MyItem = styled.div` + display: flex; + align-items: center; + margin: 0px 0px 10px 0px; + cursor: pointer; + height: 60px; + border-bottom: 1px solid #e0e0e0; +`; +const FriendsWrapper = styled.div` + flex-grow: 1; + overflow-y: auto; +`; + +const StyledLink = styled(Link)` + text-decoration: none; + color: inherit; + &:visited { + color: inherit; +`; + +const FriendListContainer = styled.div` + display: flex; + flex-direction: column; + max-width: 50vh; + max-height: 100vh; + margin: 0px auto; + background-color: #ffffff; + border-radius: 8px; + padding: 12px; + min-height: 100vh; + overflow-y: auto; +`; + + +const FriendItem = styled.div` + display: flex; + align-items: center; + margin: 10px 0; + cursor: pointer; + height: 40px; +`; + +const ProfileImage = styled.div` + width: 32px; + height: 32px; + border-radius: 50%; + overflow: hidden; + margin-right: 10px; +`; + +const ProfileImageStyled = styled.img` + width: 32px; + height: 32px; + border-radius: 50%; + margin-right: 10px; +`; + + +export default FriendsList; From 2fa3bedc135170d498b039b113c2d2b28980d8a6 Mon Sep 17 00:00:00 2001 From: Jiseok Date: Thu, 2 Nov 2023 12:41:36 +0900 Subject: [PATCH 27/32] [Empty} --- README.md | 82 ++++++++++++++++++++++++------------------------------- 1 file changed, 36 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 3d3e0e4..d92fbbe 100644 --- a/README.md +++ b/README.md @@ -1,66 +1,56 @@ -# 서론 +# 4주차 미션: React-Messenger 💌 -안녕하세요 🙌🏻 18기 프론트 운영진 김문기입니다. 이번 미션에서는 드디어 투두리스트에서 벗어나 새로운 프로젝트인 **messenger** 만들기를 진행합니다. +## 서론 -이번주는 특별히 **디자이너와의 협업**으로 진행되는 미션입니다. 디자이너분께서 열심히 리디자인 한 메신저 프로젝트를 여러분들께서 구현해주시면 됩니다. +안녕하세요 🙌🏻 프론트엔드 운영진 김문기입니다. -동시에, 이번주부터는 새로 **TypeScript**를 적용해보려고 합니다. +다들 저번주 미션은 어떠셨나요? 이번주에는 저번 과제를 확장하여 보다 더 완성도 높은 메신저 서비스를 만들어 봅시다. -프로젝트의 규모가 커지게 될 수록, 컴포넌트가 가지는 props의 종류 또한 다양해지게 됩니다. 무지성 코딩을 하다보면 가끔 이 props의 구성과 이름, 어떤 타입이 들어가야 하는지 헷갈리기 마련이죠. 보통 그럴 때 다시 컴포넌트 정의 부분으로 돌아가 props의 구성을 보고 오곤 합니다. +이번주 과제의 목표는 React에서 **Routing**을 구현하는 방법과 **상태를 관리**하는 방법에 대해 익숙해지는 것입니다. 해당 부분을 잘 고려하시면서 미션을 수행해 주세요! -하지만 이럴 때, typescript를 적용한다면 컴포넌트의 구성과 그 이름, 심지어 타입까지 한번에 자동완성으로 편리하게 관리할 수 있고, 생산성이 증대되겠죠. +또한, 이번주에는 디자이너 측에서 QA를 전달해주실 예정입니다. 전달받은 QA에 대해 디자이너와 소통 후, 이를 고쳐보시는 과정도 수행해주세요! -또한, **React Hooks**에 조금 더 익숙해지는 것을 목표로 합니다. 여러 Hook들이 있지만 그 중에서도 `useState`, `useEffect`, `useRef`를 중점적으로 사용해 보는 미션인데요, React를 사용하면서 굉장히 자주 쓰이는 Hook들이기 때문에 이 부분을 집중적으로 공부해 보아요! +그럼 이번주도 파이팅입니다 😤 -그럼 이번 미션도 파이팅입니다!!🎉 +## 미션 -# 미션 +### 미션 목표 -## Key Questions - -- JavaScript를 사용할때에 비해 TypeScript를 사용할 때의 장점은 무엇인가요? -- 디자이너로부터 전달받은 피그마 링크 혹은, 피그마 캡처본 -- 컴포넌트를 분리한 기준은 무엇인가요? -- 디자인 시스템을 적용하면서 느낀 점은 무엇인가요? -- 디자이너와 소통하며 느낀점은 무엇인가요? +- SPA의 개념을 이해하고, 그에 따른 라우팅을 구현합니다. +- 디자이너로부터 QA를 전달받고, 이에 대한 대응합니다. +- React에서 사용하는 상태 관리 방법에 익숙해집니다. +- UI 컴포넌트의 중복을 줄여 봅니다. +- 코드를 확장/재사용/리팩토링하는 방법을 이해합니다. -## 미션 목표 +### 기한 -- TypeScript를 사용해봅시다. -- useState로 컴포넌트의 상태를 관리합니다. -- useEffect와 useRef의 사용법을 이해합니다. -- styled-components를 통한 CSS-in-JS 및 CSS Preprocessor의 사용법에 익숙해집니다. +2023년 11월 3일 금요일 (기한 엄수!) -## 기한 +### 필수 요건 -2023년 9월 29일 금요일 +- 친구 목록 페이지, 채팅 목록 페이지, 설정 페이지 세 부분으로 구성합니다. +- 채팅 목록을 누르면 3주차 미션에서 구현했던 채팅방으로 이동합니다. +- 검색 기능을 추가하여 검색한 내용과 일치하는 이름을 가진 사용자만 화면에 표시합니다. +- (선택) 각자 메신저에 추가하고 싶거나, 구현하고 싶은 기능 마음껏 구현합니다. ✨ +- Custom hooks를 통해 중복되는 로직을 줄입니다. -## 필수 구현 기능 +### 선택 사항 -- 피그마를 보고 [결과화면](https://3th-fb-messenger.netlify.app)과 같이 구현합니다. -- 디자인 시스템을 구축합니다. -- 채팅방 상단의 프로필을 클릭하면 사용자를 변경할 수 있습니다. -- 메세지를 보내면 채팅방 하단으로 스크롤을 이동시킵니다. -- 메세지에 유저 정보(프로필 사진, 이름)를 표시합니다. -- user와 message 데이터를 json 파일에 저장합니다. -- UI는 **반응형을 제외**하고 피그마파일을 따라서 진행합니다. +- Recoil, Redux 등의 상태 관리 라이브러리를 적용해 봅니다. +- Base UI component system을 통해 UI 컴포넌트의 코드 재사용성을 높입니다. -### 추가 구현 기능 - -- 더블 클릭 하면 감정표현을 추가합니다. -- 그 외 추가하고 싶은 기능이 있다면 마음껏 추가해 주세요! +## Key Questions -참고로 이번 과제는 다음주까지 이어지는 과제이므로 **확장성**을 충분히 고려해 주세요. 참고로 **4주차 과제에서는 유저 및 기능 추가와 Routing을** 진행합니다. 이를 위해 [recoil](https://recoiljs.org/ko/)이나 [redux](https://ko.redux.js.org/introduction/getting-started/)를 이용한 상태관리를 미리 해보시는 것을 추천합니다!! 모두 공식문서 많이 읽어보시고 자신만의 상태관리 조합도 찾아보면 재밌을 거에요 XD +- 디자이너로부터 받은 QA 목록 +- QA 반영한 커밋(or 브랜치) 링크 (커밋 분리 필수!!!) +- Routing +- SPA +- 상태관리 ## 링크 및 참고자료 -- [React docs - Hook](https://ko.reactjs.org/docs/hooks-intro.html) -- [React의 Hooks 완벽 정복하기](https://velog.io/@velopert/react-hooks#1-usestate) -- [useEffect 완벽 가이드](https://overreacted.io/ko/a-complete-guide-to-useeffect/) -- [코딩 컨벤션](https://ui.toast.com/fe-guide/ko_CODING-CONVENTION) -- [타입스크립트 핸드북](https://joshua1988.github.io/ts/intro.html) -- [리액트 프로젝트에서 타입스크립트 사용하기 (시리즈)](https://velog.io/@velopert/series/react-with-typescript) -- [디자인 시스템 구축기](https://yozm.wishket.com/magazine/detail/1830/) -- [[영상] : 컴포넌트에 대한 이해](https://www.youtube.com/watch?v=21eiJc90ggo) -- [Styled Component로 디자인 시스템 구축하기](https://zaat.dev/blog/building-a-design-system-in-react-with-styled-components/) -- [ts 절대경로 설정하기](https://tesseractjh.tistory.com/232) \ No newline at end of file +- [React Router v6 튜토리얼](https://velog.io/@velopert/react-router-v6-tutorial) +- [(선택) react-router v6에서는 어떤 것들이 변했을까?](https://blog.woolta.com/categories/1/posts/211) +- [React 상태 관리 가이드](https://www.stevy.dev/react-state-management-guide/) +- [Flux 패턴이란?](https://velog.io/@huurray/React%EC%9D%98-%ED%83%84%EC%83%9D%EA%B3%BC-Flux-%ED%8C%A8%ED%84%B4%EC%97%90-%EB%8C%80%ED%95%98%EC%97%AC) +- [useReducer](https://www.daleseo.com/react-hooks-use-reducer/) From fe9abe7e8e97616d7778193d3c9fa4802d274f03 Mon Sep 17 00:00:00 2001 From: Jiseok Date: Thu, 2 Nov 2023 12:42:12 +0900 Subject: [PATCH 28/32] [style] upload icons --- src/assets/images/Github1.svg | 9 +++++++++ src/assets/images/Instagram1.svg | 9 +++++++++ src/assets/images/Signal.svg | 3 +++ src/assets/images/User-black.svg | 4 ++++ src/assets/images/User.svg | 4 ++++ 5 files changed, 29 insertions(+) create mode 100644 src/assets/images/Github1.svg create mode 100644 src/assets/images/Instagram1.svg create mode 100644 src/assets/images/Signal.svg create mode 100644 src/assets/images/User-black.svg create mode 100644 src/assets/images/User.svg diff --git a/src/assets/images/Github1.svg b/src/assets/images/Github1.svg new file mode 100644 index 0000000..8652b95 --- /dev/null +++ b/src/assets/images/Github1.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/images/Instagram1.svg b/src/assets/images/Instagram1.svg new file mode 100644 index 0000000..d7de4ab --- /dev/null +++ b/src/assets/images/Instagram1.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/images/Signal.svg b/src/assets/images/Signal.svg new file mode 100644 index 0000000..a30aff8 --- /dev/null +++ b/src/assets/images/Signal.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/User-black.svg b/src/assets/images/User-black.svg new file mode 100644 index 0000000..9b20e4e --- /dev/null +++ b/src/assets/images/User-black.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/images/User.svg b/src/assets/images/User.svg new file mode 100644 index 0000000..1bd56cb --- /dev/null +++ b/src/assets/images/User.svg @@ -0,0 +1,4 @@ + + + + From b3c03d5d8160b333348c9319c2ad618877f70706 Mon Sep 17 00:00:00 2001 From: Jiseok Date: Thu, 2 Nov 2023 12:47:05 +0900 Subject: [PATCH 29/32] delete --- pages/Chat/Chat.tsx | 5 --- pages/FriendsList.tsx | 67 ------------------------------- pages/UserProfile.tsx | 91 ------------------------------------------- 3 files changed, 163 deletions(-) delete mode 100644 pages/Chat/Chat.tsx delete mode 100644 pages/FriendsList.tsx delete mode 100644 pages/UserProfile.tsx diff --git a/pages/Chat/Chat.tsx b/pages/Chat/Chat.tsx deleted file mode 100644 index 782be14..0000000 --- a/pages/Chat/Chat.tsx +++ /dev/null @@ -1,5 +0,0 @@ -const Chat = () =>{ - return
Chat
-}; - -export default Chat; \ No newline at end of file diff --git a/pages/FriendsList.tsx b/pages/FriendsList.tsx deleted file mode 100644 index a37217b..0000000 --- a/pages/FriendsList.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import styled from 'styled-components'; -import usersData from '../assets/datas/dummy.json'; -import { ReactComponent as ProfileIcon } from '../assets/images/Profile-Icon.svg'; -import { Link } from 'react-router-dom'; - -export interface Friend { - id: number; - name: string; - profileImage: string; -} - -interface FriendsListProps { - onSelectFriend: (friend: Friend) => void; -} - -const FriendsList: React.FC = ({ onSelectFriend }) => { - const [users, setUsers] = useState([]); - - useEffect(() => { - // 사용자 데이터를 설정 - setUsers(usersData.users); - }, []); - - return ( - - {users.map((friend) => ( - - onSelectFriend(friend)}> - {friend.profileImage === 'ProfileIcon' ? : } - {friend.name} - - - ))} - - ); -}; - -const FriendListContainer = styled.div` - width: 30%; // Adjust based on preference - padding: 20px; - overflow-y: auto; -`; - -const FriendItem = styled.div` - display: flex; - align-items: center; - margin: 10px 0; - cursor: pointer; -`; - -const ProfileImage = styled.div` - width: 32px; - height: 32px; - border-radius: 50%; - overflow: hidden; - margin-right: 10px; -`; - -const ProfileImageStyled = styled.img` - width: 32px; - height: 32px; - border-radius: 50%; - margin-right: 10px; -`; - -export default FriendsList; diff --git a/pages/UserProfile.tsx b/pages/UserProfile.tsx deleted file mode 100644 index e54c351..0000000 --- a/pages/UserProfile.tsx +++ /dev/null @@ -1,91 +0,0 @@ -// src/pages/UserProfile.tsx -import React from 'react'; -import styled from 'styled-components'; -import StatusBar from '../components/StatusBar'; -import { ReactComponent as InstagramIcon } from '../assets/images/Instagram.svg'; // Assuming you have this icon -import { ReactComponent as GitHubIcon } from '../assets/images/Github.svg'; // Assuming you have this icon -import { ReactComponent as ProfileIcon } from '../assets/images/Profile-Icon.svg'; // Assuming you have this icon - -const UserProfile: React.FC = () => { - return ( - - - - - {/* Placeholder for user image */} - 최유선 - yoonseor724@gmail.com - - - - - - Instagram - - - - GitHub - - - - ); -}; - -export default UserProfile; - -const ProfileContainer = styled.div` - display: flex; - flex-direction: column; - align-items: center; - gap: 16px; - background-color: #ffffff; - border-radius: 8px; - padding: 10px; - max-width: 300px; // Adjust based on your requirements - margin: 20px auto; - -`; - -const ProfileDetails = styled.div` - display: flex; - flex-direction: column; - align-items: center; - gap: 8px; -`; - -const UserName = styled.span` - font-size: 16px; - color: #101010; - font-weight: bold; -`; - -const UserEmail = styled.span` - font-size: 14px; - color: #909090; -`; - -const ProfileImage = styled.div` - width: 50px; // Adjust based on your requirements - height: 50px; - border-radius: 50%; - background-color: #f1f1f1; // Placeholder color -`; - -const SocialLinks = styled.div` - display: flex; - flex-direction: column; - gap: 10px; - width: 100%; -`; - -const LinkItem = styled.div` - display: flex; - align-items: center; - gap: 8px; - font-size: 14px; - color: #101010; - cursor: pointer; - &:hover { - text-decoration: underline; - } -`; From 07626bc0bb3487fcbc9cf68667b2ffe278b27d42 Mon Sep 17 00:00:00 2001 From: Jiseok Date: Thu, 2 Nov 2023 12:47:54 +0900 Subject: [PATCH 30/32] delete --- components/ChatHeader.tsx | 88 ------------------------------- components/ChatInput.tsx | 107 -------------------------------------- components/StatusBar.tsx | 69 ------------------------ 3 files changed, 264 deletions(-) delete mode 100644 components/ChatHeader.tsx delete mode 100644 components/ChatInput.tsx delete mode 100644 components/StatusBar.tsx diff --git a/components/ChatHeader.tsx b/components/ChatHeader.tsx deleted file mode 100644 index b9428f2..0000000 --- a/components/ChatHeader.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import React from 'react'; -import styled from 'styled-components'; -import { ReactComponent as CallIcons } from '../assets/images/Phone.svg'; -import { ReactComponent as VideoIcons } from '../assets/images/Videocamera.svg'; -import { ReactComponent as LeftArrowIcon } from '../assets/images/Arrow-Left.svg'; -import { useNavigate } from 'react-router-dom'; - -interface ChatHeaderProps { - chatName: string; - onSwitchPosition?: () => void; -} - - const ChatHeader: React.FC = ({ chatName, onSwitchPosition }) => { - const navigate = useNavigate(); - - const navigateToFriendList = () => { - navigate("/friends"); - }; - return ( - - - - {chatName} - - - - - - - - - - - ); -}; -export default ChatHeader; - -const ChatHeaderContainer = styled.div` - display: flex; - justify-content: space-between; - align-items: center; - padding: 10px 0px 10px 3px; - background: rgba(255, 255, 255, 0.90); - backdrop-filter: blur(4px); - gap: 12px; -`; - -const Wrapper = styled.div` - display: flex; - align-items: center; - gap: 4px; -`; - - -const UserName = styled.div` - color: #101010; - font-size: 18px; - font-weight: 600; - line-height: 18px; - word-wrap: break-word; -`; - -const Icons = styled.div` - display: flex; - justify-content: flex-start; - align-items: center; - gap: 12px; -`; - -const IconWrapper = styled.div` - width: 24px; - height: 24px; - position: relative; -`; - -const CallIcon = styled(CallIcons)` - width: 24px; - height: 24px; -`; - -const VideoIcon = styled(VideoIcons)` - width: 24px; - height: 24px; -`; -const ArrowLeftIcon = styled(LeftArrowIcon)` - width: 24px; - height: 24px; -`; diff --git a/components/ChatInput.tsx b/components/ChatInput.tsx deleted file mode 100644 index 0b26099..0000000 --- a/components/ChatInput.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import React, { useState } from 'react'; -import styled from 'styled-components'; - -import { ReactComponent as Camera } from '../assets/images/Camera.svg'; -import { ReactComponent as Gallery } from '../assets/images/Gallery.svg'; -import { ReactComponent as Mic } from '../assets/images/Microphone.svg'; -import { ReactComponent as Smile } from '../assets/images/Sticker-Smile.svg'; -import { ReactComponent as Low } from '../assets/images/Indicator_low.svg'; - -interface ChatInputProps { - onSend: (message: string) => void; -} - -const ChatInput: React.FC = ({ onSend }) => { - const [inputValue, setInputValue] = useState(''); - - const handleSendMessage = (e: React.FormEvent) => { - e.preventDefault(); - if (inputValue.trim()) { - onSend(inputValue); - setInputValue(''); - } - }; - - return ( - - - - - - setInputValue(e.target.value)} - /> - - - - - - - ); -}; - -export default ChatInput; - -const InputContainer = styled.div` - display: flex; - flex-direction: column; -`; - -const StyledInputBar = styled.form` - display: flex; - justify-content: center; - background: white; -`; - -const TextInputContainer = styled.div` - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; - height: 44px; - padding: 0 10px; - background: #F1F1F1; - border-radius: 22px; - border: 1px #CCCCCC solid; -`; - -const Input = styled.input` - flex-grow: 1; - margin: 0 10px; - height: 100%; - border: none; - outline: none; - border-radius: 22px; - background: transparent; - &:focus { - box-shadow: none; - } -`; - -const CameraIcon = styled(Camera)` - width: 24px; - height: 24px; - margin-right: 12px; -`; - -const GalleryIcon = styled(Gallery)` - width: 24px; - height: 24px; -`; - -const MicIcon = styled(Mic)` - width: 24px; - height: 24px; -`; - -const SmileIcon = styled(Smile)` - width: 24px; - height: 24px; - margin-left: 12px; -`; - -const LowIcon = styled(Low)` - width: 100%; - height: 21px; -`; diff --git a/components/StatusBar.tsx b/components/StatusBar.tsx deleted file mode 100644 index 576b995..0000000 --- a/components/StatusBar.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import styled from 'styled-components'; -import { ReactComponent as Indicators } from '../assets/images/indicators.svg'; - -const StatusBar: React.FC = () => { - const [currentTime, setCurrentTime] = useState(getCurrentTime()); - - useEffect(() => { - const interval = setInterval(() => { - setCurrentTime(getCurrentTime()); - }, 60000); - return () => clearInterval(interval); - }, []); - - return ( - - - {currentTime} - - - - - - - ); -}; - -export default StatusBar; - -function getCurrentTime(): string { - return new Date().toLocaleTimeString([], { hour12: false, hour: '2-digit', minute: '2-digit' }); -} - -const StatusBarContainer = styled.div` - padding: 10px 0px 10px 1px; - background: #ffffff; - backdrop-filter: blur(5px); - display: flex; - justify-content: space-between; - align-items: center; -`; - -const TimeContainer = styled.div` - display: flex; - justify-content: center; - align-items: center; - margin-left: 15px; -`; - -const TimeText = styled.div` - text-align: center; - color: #101010; - font-size: 17px; - font-weight: 600; - line-height: 17px; - word-wrap: break-word; -`; - -const IconContainer = styled.div` - display: flex; - justify-content: flex-end; - align-items: center; - gap: 6px; -`; - -const Indicator = styled(Indicators)` - width: 76px; - height: 17px; -`; From faaa51f87b3f575fdc5f54ed419d277fc5cb7fcc Mon Sep 17 00:00:00 2001 From: Jiseok Date: Fri, 3 Nov 2023 13:36:50 +0900 Subject: [PATCH 31/32] [chore] add data --- src/assets/datas/dummy.json | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/assets/datas/dummy.json b/src/assets/datas/dummy.json index ea9c8db..26694ba 100644 --- a/src/assets/datas/dummy.json +++ b/src/assets/datas/dummy.json @@ -7,22 +7,42 @@ }, { "id": 1, - "name": "지석", + "name": "김민지", "profileImage": "ProfileIcon" }, { "id": 2, - "name": "형찬", + "name": "배수연", "profileImage": "ProfileIcon" }, { "id": 3, - "name": "정민", + "name": "배은환", "profileImage": "ProfileIcon" }, { "id": 4, - "name": "태영", + "name": "신현재", + "profileImage": "ProfileIcon" + }, + { + "id": 5, + "name": "소혜린", + "profileImage": "ProfileIcon" + }, + { + "id": 6, + "name": "염혜인", + "profileImage": "ProfileIcon" + }, + { + "id": 7, + "name": "윤서윤", + "profileImage": "ProfileIcon" + }, + { + "id": 8, + "name": "이예진", "profileImage": "ProfileIcon" } ] From 64b8bb56a0212f198395f0bbea7ab868c938ce4f Mon Sep 17 00:00:00 2001 From: Jiseok Date: Fri, 3 Nov 2023 13:48:07 +0900 Subject: [PATCH 32/32] =?UTF-8?q?[chore]style=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/IndicateBar.tsx | 2 +- src/pages/UserProfile.tsx | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/components/IndicateBar.tsx b/src/components/IndicateBar.tsx index bd0d7d0..c38a380 100644 --- a/src/components/IndicateBar.tsx +++ b/src/components/IndicateBar.tsx @@ -28,7 +28,7 @@ const IndicateBar: React.FC = ({ activeIcon }) => { navigate('/userprofile') } const goToGoogle = () => { - if (window.confirm("구글 ㄱㄱ?")) { + if (window.confirm("구글")) { window.open("https://www.google.com", "_blank"); } } diff --git a/src/pages/UserProfile.tsx b/src/pages/UserProfile.tsx index e9f15bb..6bbdbeb 100644 --- a/src/pages/UserProfile.tsx +++ b/src/pages/UserProfile.tsx @@ -9,12 +9,12 @@ import instagramIconSrc from '../assets/images/Instagram1.svg'; import githubIconSrc from '../assets/images/Github1.svg'; const UserProfile: React.FC = () => { const goToGithub = () => { - if (window.confirm("깃허브 ㄱㄱ?")) { + if (window.confirm("깃허브")) { window.open("https://www.github.com", "_blank"); } } const goToinsta = () => { - if (window.confirm("인스타 ㄱㄱ?")) { + if (window.confirm("인스타")) { window.open("https://www.instagram.com", "_blank"); } } @@ -30,15 +30,19 @@ const UserProfile: React.FC = () => { + Instagram Instagram + + Github GitHub + @@ -64,6 +68,7 @@ const LinkIconStyled = styled(LinkIconSrc)` width: 24px; height: 24px; cursor: pointer; + color: #909090; `; const ProfileContainer = styled.div` @@ -118,3 +123,8 @@ const LinkItem = styled.div` margin: 14px 0; color: #101010; `; +const LinkContent = styled.div` + display: flex; + align-items: center; + gap: 20px; +`;