diff --git a/README.md b/README.md index 6ab8e98..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) +- [React Router v6 튜토리얼](https://velog.io/@velopert/react-router-v6-tutorial) +- [(선택) react-router v6에서는 어떤 것들이 변했을까?](https://blog.woolta.com/categories/1/posts/211) +- [React 상태 관리 가이드](https://www.stevy.dev/react-state-management-guide/) +- [Flux 패턴이란?](https://velog.io/@huurray/React%EC%9D%98-%ED%83%84%EC%83%9D%EA%B3%BC-Flux-%ED%8C%A8%ED%84%B4%EC%97%90-%EB%8C%80%ED%95%98%EC%97%AC) +- [useReducer](https://www.daleseo.com/react-hooks-use-reducer/) diff --git a/package-lock.json b/package-lock.json index 82a715f..d99fc3f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,28 @@ { - "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-router-dom": "^6.17.0", "react-scripts": "5.0.1", + "recoil": "^0.7.7", + "styled-components": "^6.0.8", + "styled-reset": "^4.5.1", + "typescript": "^4.9.5", "web-vitals": "^2.1.4" } }, @@ -53,6 +62,78 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/cli": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.23.0.tgz", + "integrity": "sha512-17E1oSkGk2IwNILM4jtfAvgjt+ohmpfBky8aLerUfYZhiPNg7ca+CRCxZn8QDxwNhV/upsc2VHBCqGFIR+iBfA==", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.17", + "commander": "^4.0.1", + "convert-source-map": "^2.0.0", + "fs-readdir-recursive": "^1.1.0", + "glob": "^7.2.0", + "make-dir": "^2.1.0", + "slash": "^2.0.0" + }, + "bin": { + "babel": "bin/babel.js", + "babel-external-helpers": "bin/babel-external-helpers.js" + }, + "engines": { + "node": ">=6.9.0" + }, + "optionalDependencies": { + "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.3", + "chokidar": "^3.4.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/cli/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/@babel/cli/node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/cli/node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/cli/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/@babel/cli/node_modules/slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "engines": { + "node": ">=6" + } + }, "node_modules/@babel/code-frame": { "version": "7.22.13", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", @@ -74,21 +155,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 +225,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 +361,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 +384,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 +406,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 +545,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 +571,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 +611,20 @@ "@babel/core": "^7.13.0" } }, + "node_modules/@babel/plugin-external-helpers": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-external-helpers/-/plugin-external-helpers-7.22.5.tgz", + "integrity": "sha512-ngnNEWxmykPk82mH4ajZT0qTztr3Je6hrMuKAslZVM8G1YZTENJSYwrIGtt6KOtznug3exmAtF4so/nPqJuA4A==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-proposal-class-properties": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", @@ -547,13 +642,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 +691,25 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-proposal-object-rest-spread": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", + "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead.", + "dependencies": { + "@babel/compat-data": "^7.20.5", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-proposal-optional-chaining": { "version": "7.21.0", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz", @@ -986,9 +1100,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 +1182,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 +1373,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 +1388,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 +1404,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 +1543,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 +2030,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 +2053,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 +2077,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 +2097,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 +2384,24 @@ "postcss-selector-parser": "^6.0.10" } }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz", + "integrity": "sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==", + "dependencies": { + "@emotion/memoize": "^0.8.1" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + }, + "node_modules/@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -2285,9 +2417,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 +2808,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 +3082,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 +3250,12 @@ "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==" }, + "node_modules/@nicolo-ribaudo/chokidar-2": { + "version": "2.1.8-no-fsevents.3", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz", + "integrity": "sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==", + "optional": true + }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", @@ -3241,6 +3365,14 @@ } } }, + "node_modules/@remix-run/router": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.10.0.tgz", + "integrity": "sha512-Lm+fYpMfZoEucJ7cMxgt4dYt8jLfbpwRCzAjm9UgSLOkmlqo9gupxt6YX3DY0Fk155NT9l17d/ydi+964uS9Lw==", + "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", @@ -3316,9 +3448,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", @@ -4030,255 +4162,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==", + "version": "27.5.2", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.5.2.tgz", + "integrity": "sha512-mpT8LJJ4CMeeahobofYWIjFo0xonRS/HfxnVEPMPFSQdGUt1uHCnoPT7Zhb+sjDU2wz0oKV0OLUR0WzrHNgfeA==", "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==", - "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 +4197,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 +4232,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 +4247,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 +4268,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 +4305,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 +4317,11 @@ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==" }, + "node_modules/@types/stylis": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.1.tgz", + "integrity": "sha512-OSaMrXUKxVigGlKRrET39V2xdhzlztQ9Aqumn1WbCBKHOi9ry7jKSd7rkyj0GzmWaU960Rd+LpOFpLfx5bMQAg==" + }, "node_modules/@types/testing-library__jest-dom": { "version": "5.14.9", "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.9.tgz", @@ -4426,25 +4336,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 +5626,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.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", + "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", "funding": [ { "type": "opencollective", @@ -5734,8 +5644,8 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001538", - "electron-to-chromium": "^1.4.526", + "caniuse-lite": "^1.0.30001541", + "electron-to-chromium": "^1.4.535", "node-releases": "^2.0.13", "update-browserslist-db": "^1.0.13" }, @@ -5826,6 +5736,14 @@ "node": ">= 6" } }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", @@ -5838,9 +5756,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.30001541", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001541.tgz", + "integrity": "sha512-bLOsqxDgTqUBkzxbNlSBt8annkDpQB9NdzdTbO2ooJ+eC/IQcvDspDc058g84ejCelF7vHUx57KIOjEecOHXaw==", "funding": [ { "type": "opencollective", @@ -6154,9 +6072,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 +6179,14 @@ "postcss": "^8.4" } }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "engines": { + "node": ">=4" + } + }, "node_modules/css-declaration-sorter": { "version": "6.4.1", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz", @@ -6442,6 +6368,16 @@ "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "node_modules/css-tree": { "version": "1.0.0-alpha.37", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", @@ -7033,9 +6969,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.536", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.536.tgz", + "integrity": "sha512-L4VgC/76m6y8WVCgnw5kJy/xs7hXrViCFdNKVG8Y7B2isfwrFryFyJzumh3ugxhd/oB1uEaEEvRdmeLrnd7OFA==" }, "node_modules/emittery": { "version": "0.8.1", @@ -8545,9 +8481,14 @@ } }, "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==" }, "node_modules/fs.realpath": { "version": "1.0.0", @@ -8812,6 +8753,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/hamt_plus": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/hamt_plus/-/hamt_plus-1.0.2.tgz", + "integrity": "sha512-t2JXKaehnMb9paaYA7J0BX8QQAY8lwfQ9Gjf4pg/mk4krt+cmwmU652HOoWonf+7+EQV97ARPMhhVgU1ra2GhA==" + }, "node_modules/handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -11425,9 +11371,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.26", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.26.tgz", + "integrity": "sha512-Y3vDy2X6zw/ZCumcwLpdhM5L7jmyGpmBCTYMHDLqT2IKVMYRRLdv6ZakA+wxhra6Z/3bwhNbNl9bDGXaFU+6rw==", "dependencies": { "@types/yargs-parser": "*" } @@ -13100,9 +13046,9 @@ } }, "node_modules/postcss": { - "version": "8.4.30", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.30.tgz", - "integrity": "sha512-7ZEao1g4kd68l97aWG/etQKPKq07us0ieSZ2TnFDk11i0ZfDW2AwKHYU8qv4MZKqN2fdBfg+7q0ES06UA73C1g==", + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "funding": [ { "type": "opencollective", @@ -14686,6 +14632,36 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.17.0.tgz", + "integrity": "sha512-YJR3OTJzi3zhqeJYADHANCGPUu9J+6fT5GLv82UWRGSxu6oJYCKVmxUcaBQuGm9udpWmPsvpme/CdHumqgsoaA==", + "dependencies": { + "@remix-run/router": "1.10.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.17.0.tgz", + "integrity": "sha512-qWHkkbXQX+6li0COUUPKAUkxjNNqPJuiBd27dVwQGDNsuFBdMbrS6UZ0CLYc4CsbdLYTckn4oB4tGDuPZpPhaQ==", + "dependencies": { + "@remix-run/router": "1.10.0", + "react-router": "6.17.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", @@ -14790,6 +14766,25 @@ "node": ">=8.10.0" } }, + "node_modules/recoil": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/recoil/-/recoil-0.7.7.tgz", + "integrity": "sha512-8Og5KPQW9LwC577Vc7Ug2P0vQshkv1y3zG3tSSkWMqkWSwHmE+by06L8JtnGocjW6gcCvfwB3YtrJG6/tWivNQ==", + "dependencies": { + "hamt_plus": "1.0.2" + }, + "peerDependencies": { + "react": ">=16.13.1" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/recursive-readdir": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz", @@ -15020,6 +15015,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 +15513,11 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -16009,6 +16014,60 @@ "webpack": "^5.0.0" } }, + "node_modules/styled-components": { + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.0.8.tgz", + "integrity": "sha512-AwI02MTWZwqjzfXgR5QcbmcSn5xVjY4N2TLjSuYnmuBGF3y7GicHz3ysbpUq2EMJP5M8/Nc22vcmF3V3WNZDFA==", + "dependencies": { + "@babel/cli": "^7.21.0", + "@babel/core": "^7.21.0", + "@babel/helper-module-imports": "^7.18.6", + "@babel/plugin-external-helpers": "^7.18.6", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-proposal-object-rest-spread": "^7.20.7", + "@babel/preset-env": "^7.20.2", + "@babel/preset-react": "^7.18.6", + "@babel/preset-typescript": "^7.21.0", + "@babel/traverse": "^7.21.2", + "@emotion/is-prop-valid": "^1.2.1", + "@emotion/unitless": "^0.8.0", + "@types/stylis": "^4.0.2", + "css-to-react-native": "^3.2.0", + "csstype": "^3.1.2", + "postcss": "^8.4.23", + "shallowequal": "^1.1.0", + "stylis": "^4.3.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "babel-plugin-styled-components": ">= 2", + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0" + }, + "peerDependenciesMeta": { + "babel-plugin-styled-components": { + "optional": true + } + } + }, + "node_modules/styled-reset": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/styled-reset/-/styled-reset-4.5.1.tgz", + "integrity": "sha512-6EvFWZRwaFRFxiPYMwmnzOe33rDkw5r9jIU0eEi49bkt6VSrvjeMp2ZOw/YFbw5SVs81llIY+5fzHtR2/VBZfQ==", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "styled-components": ">=4.0.0 || >=5.0.0 || >=6.0.0" + } + }, "node_modules/stylehacks": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", @@ -16024,6 +16083,11 @@ "postcss": "^8.2.15" } }, + "node_modules/stylis": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.0.tgz", + "integrity": "sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ==" + }, "node_modules/sucrase": { "version": "3.34.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz", @@ -16656,7 +16720,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 +16923,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..d0c6138 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,23 @@ { - "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-router-dom": "^6.17.0", "react-scripts": "5.0.1", + "recoil": "^0.7.7", + "styled-components": "^6.0.8", + "styled-reset": "^4.5.1", + "typescript": "^4.9.5", "web-vitals": "^2.1.4" }, "scripts": { diff --git a/public/index.html b/public/index.html index aa069f2..57d1f21 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 + Direct Messager diff --git a/public/logo192.png b/public/logo192.png deleted file mode 100644 index fc44b0a..0000000 Binary files a/public/logo192.png and /dev/null differ diff --git a/public/logo512.png b/public/logo512.png deleted file mode 100644 index a4e47a6..0000000 Binary files a/public/logo512.png and /dev/null differ diff --git a/public/manifest.json b/public/manifest.json deleted file mode 100644 index 080d6c7..0000000 --- a/public/manifest.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "short_name": "React App", - "name": "Create React App Sample", - "icons": [ - { - "src": "favicon.ico", - "sizes": "64x64 32x32 24x24 16x16", - "type": "image/x-icon" - }, - { - "src": "logo192.png", - "type": "image/png", - "sizes": "192x192" - }, - { - "src": "logo512.png", - "type": "image/png", - "sizes": "512x512" - } - ], - "start_url": ".", - "display": "standalone", - "theme_color": "#000000", - "background_color": "#ffffff" -} diff --git a/public/robots.txt b/public/robots.txt deleted file mode 100644 index e9e57dc..0000000 --- a/public/robots.txt +++ /dev/null @@ -1,3 +0,0 @@ -# https://www.robotstxt.org/robotstxt.html -User-agent: * -Disallow: 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.js b/src/App.js deleted file mode 100644 index 3784575..0000000 --- a/src/App.js +++ /dev/null @@ -1,25 +0,0 @@ -import logo from './logo.svg'; -import './App.css'; - -function App() { - return ( -
-
- logo -

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

- - Learn React - -
-
- ); -} - -export default App; diff --git a/src/App.test.js b/src/App.test.js deleted file mode 100644 index 1f03afe..0000000 --- a/src/App.test.js +++ /dev/null @@ -1,8 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import App from './App'; - -test('renders learn react link', () => { - render(); - const linkElement = screen.getByText(/learn react/i); - expect(linkElement).toBeInTheDocument(); -}); diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..b5f7326 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,56 @@ +import React from "react"; +import { createGlobalStyle } from "styled-components"; +import reset from "styled-reset"; +import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; + +//pages +import ChattingRoom from "./pages/ChattingRoom"; +import ChattingListPage from "./pages/ChattingListPage"; +import FriendsListPage from "./pages/FriendsListPage"; +import ProfilePage from "./pages/ProfilePage"; + +//context +import { SenderProvider } from "./assets/SenderContext"; + +//recoil +import { RecoilRoot } from "recoil"; + +function App() { + return ( + + + + + + }> + }> + } + > + }> + + + + + ); +} + +export default App; + +const GlobalStyle = createGlobalStyle` + + ${reset} + *, *::before, *::after{ + box-sizing: border-box; + } + body{ + display: flex; + padding: 0; + margin: 0; + justify-content: center; + font-family: "Pretendard-Regular"; + + background-color: #E2E2e2; + }; +`; diff --git a/src/assets/SenderContext.js b/src/assets/SenderContext.js new file mode 100644 index 0000000..d3f2ce3 --- /dev/null +++ b/src/assets/SenderContext.js @@ -0,0 +1,17 @@ +import { createContext, useContext, useState } from "react"; + +const SenderContext = createContext(); + +export function SenderProvider({ children }) { + const [sender, setSender] = useState(0); + + return ( + + {children} + + ); +} + +export function useSender() { + return useContext(SenderContext); +} diff --git a/src/assets/data/chattingdata.json b/src/assets/data/chattingdata.json new file mode 100644 index 0000000..17c01bd --- /dev/null +++ b/src/assets/data/chattingdata.json @@ -0,0 +1,41 @@ +{ + "chattings": [ + { + "chattingId": 0, + "users": [0, 1], + "chatList": [ + { + "text": "안녕하세요!", + "sender": 0, + "timestamp": "2023-10-31T08:00:00" + }, + { + "text": "안녕하세요! 반가워요!", + "sender": 1, + "timestamp": "2023-10-31T08:15:00" + }, + { + "text": "어떻게 지내세요?", + "sender": 0, + "timestamp": "2023-10-31T08:30:00" + } + ] + }, + { + "chattingId": 1, + "users": [0, 2], + "chatList": [ + { "text": "멍멍", "sender": 0, "timestamp": "2023-10-31T09:00:00" }, + { "text": "왈왈", "sender": 2, "timestamp": "2023-10-31T09:15:00" } + ] + }, + { + "chattingId": 2, + "users": [0, 3], + "chatList": [ + { "text": "야옹", "sender": 0, "timestamp": "2023-10-31T10:00:00" }, + { "text": "meow", "sender": 3, "timestamp": "2023-10-31T10:15:00" } + ] + } + ] +} diff --git a/src/assets/data/userdata.json b/src/assets/data/userdata.json new file mode 100644 index 0000000..cda65ca --- /dev/null +++ b/src/assets/data/userdata.json @@ -0,0 +1,45 @@ +{ + "users": [ + { + "id": 0, + "userName": "변지혜", + "userID": "mod-siw", + "profileImage": "data:image/jpeg;base64,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", + "memo": "", + "email": "jihye@ceos.com" + }, + { + "id": 1, + "userName": "Yeom", + "userID": "hi.yeomm", + "profileImage": "data:image/jpeg;base64,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", + "memo": "", + "email": "hyein@ceos.com" + }, + { + "id": 2, + "userName": "강아지", + "userID": "dogdog", + "profileImage": "data:image/jpeg;base64,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", + + "memo": "woof!", + "email": "dog@ceos.com" + }, + { + "id": 3, + "userName": "고양이", + "userID": "catcat", + "profileImage": "https://i.pinimg.com/736x/c6/23/30/c623305b017609ee53d39226ea84159f.jpg", + "memo": "meow", + "email": "cat@ceos.com" + }, + { + "id": 4, + "userName": "토끼", + "userID": "rabbit", + "profileImage": "https://www.hapt.co.kr/news/photo/202301/157681_27857_45.jpg", + "memo": "🍀🍀", + "email": "clober@ceos.com" + } + ] +} diff --git a/src/assets/fonts/Pretendard-Regular.ttf b/src/assets/fonts/Pretendard-Regular.ttf new file mode 100644 index 0000000..5017814 Binary files /dev/null and b/src/assets/fonts/Pretendard-Regular.ttf differ diff --git a/src/assets/images/Profile1.svg b/src/assets/images/Profile1.svg new file mode 100644 index 0000000..fa7db9d --- /dev/null +++ b/src/assets/images/Profile1.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/images/Profile2.svg b/src/assets/images/Profile2.svg new file mode 100644 index 0000000..ae4cd14 --- /dev/null +++ b/src/assets/images/Profile2.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/images/back.svg b/src/assets/images/back.svg new file mode 100644 index 0000000..709b85e --- /dev/null +++ b/src/assets/images/back.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/bars.svg b/src/assets/images/bars.svg new file mode 100644 index 0000000..2553c20 --- /dev/null +++ b/src/assets/images/bars.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/behance.svg b/src/assets/images/behance.svg new file mode 100644 index 0000000..e0a6ec7 --- /dev/null +++ b/src/assets/images/behance.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/images/camera-line.svg b/src/assets/images/camera-line.svg new file mode 100644 index 0000000..05067f0 --- /dev/null +++ b/src/assets/images/camera-line.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/images/camera.svg b/src/assets/images/camera.svg new file mode 100644 index 0000000..3d8cfe9 --- /dev/null +++ b/src/assets/images/camera.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/assets/images/check.svg b/src/assets/images/check.svg new file mode 100644 index 0000000..8b70341 --- /dev/null +++ b/src/assets/images/check.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/checked.svg b/src/assets/images/checked.svg new file mode 100644 index 0000000..d1a03be --- /dev/null +++ b/src/assets/images/checked.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/images/github.svg b/src/assets/images/github.svg new file mode 100644 index 0000000..5991367 --- /dev/null +++ b/src/assets/images/github.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/images/insta.svg b/src/assets/images/insta.svg new file mode 100644 index 0000000..3a36bfd --- /dev/null +++ b/src/assets/images/insta.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..d694600 --- /dev/null +++ b/src/assets/images/link.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/mic.svg b/src/assets/images/mic.svg new file mode 100644 index 0000000..bba6396 --- /dev/null +++ b/src/assets/images/mic.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/images/mymemo.svg b/src/assets/images/mymemo.svg new file mode 100644 index 0000000..503f445 --- /dev/null +++ b/src/assets/images/mymemo.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/images/othermemo-ex.svg b/src/assets/images/othermemo-ex.svg new file mode 100644 index 0000000..dea611d --- /dev/null +++ b/src/assets/images/othermemo-ex.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/images/othermemo.svg b/src/assets/images/othermemo.svg new file mode 100644 index 0000000..7c30567 --- /dev/null +++ b/src/assets/images/othermemo.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/assets/images/phone.svg b/src/assets/images/phone.svg new file mode 100644 index 0000000..4fc37f0 --- /dev/null +++ b/src/assets/images/phone.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/images/photo.svg b/src/assets/images/photo.svg new file mode 100644 index 0000000..f23d3ff --- /dev/null +++ b/src/assets/images/photo.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/images/search.svg b/src/assets/images/search.svg new file mode 100644 index 0000000..77793d0 --- /dev/null +++ b/src/assets/images/search.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/images/sendingbtn.svg b/src/assets/images/sendingbtn.svg new file mode 100644 index 0000000..ae02b4c --- /dev/null +++ b/src/assets/images/sendingbtn.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/images/share.svg b/src/assets/images/share.svg new file mode 100644 index 0000000..d5f9fbf --- /dev/null +++ b/src/assets/images/share.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/status.svg b/src/assets/images/status.svg new file mode 100644 index 0000000..24e6034 --- /dev/null +++ b/src/assets/images/status.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/assets/images/sticker.svg b/src/assets/images/sticker.svg new file mode 100644 index 0000000..455f824 --- /dev/null +++ b/src/assets/images/sticker.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/images/video.svg b/src/assets/images/video.svg new file mode 100644 index 0000000..ac8c0f9 --- /dev/null +++ b/src/assets/images/video.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/assets/images/write.svg b/src/assets/images/write.svg new file mode 100644 index 0000000..8e25a09 --- /dev/null +++ b/src/assets/images/write.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/recoil/recoil.ts b/src/assets/recoil/recoil.ts new file mode 100644 index 0000000..ec3078b --- /dev/null +++ b/src/assets/recoil/recoil.ts @@ -0,0 +1,25 @@ +import { atom } from "recoil"; +import userData from "../data/userdata.json"; +import chattingdata from "../data/chattingdata.json"; + +import { chattingInterface, userInterface } from "../types/interfaces"; + +export const meAtom = atom({ + key: "meAtom", + default: userData.users[0], +}); + +export const friendsListAtom = atom({ + key: "friendsListAtom", + default: userData.users.slice(1), +}); + +export const userArrayState = atom({ + key: "userArrayState", + default: userData.users, +}); + +export const chatArrayState = atom({ + key: "dataArrayState", + default: chattingdata.chattings, +}); diff --git a/src/assets/styles/color.tsx b/src/assets/styles/color.tsx new file mode 100644 index 0000000..c724655 --- /dev/null +++ b/src/assets/styles/color.tsx @@ -0,0 +1,12 @@ +export const color = { + black: "#000", + white: "#FFF", + + grey1: "#F2F2F2", + grey2: "#D9D9D9", + grey3: "#808080", + + blue1: "#0095F7", + blue2: "#0095F7", + blue3: "#90CEF6", +}; diff --git a/src/assets/types/interfaces.ts b/src/assets/types/interfaces.ts new file mode 100644 index 0000000..ff0f2dd --- /dev/null +++ b/src/assets/types/interfaces.ts @@ -0,0 +1,19 @@ +export interface userInterface { + id: number; + userName: string; + userID: string; + profileImage: string; + memo: string; + email: string; +} + +export interface chatInterface { + text: string; + sender: number; +} + +export interface chattingInterface { + chattingId: number; + users: number[]; + chatList: chatInterface[]; +} diff --git a/src/components/@types/global/index.d.ts b/src/components/@types/global/index.d.ts new file mode 100644 index 0000000..8478821 --- /dev/null +++ b/src/components/@types/global/index.d.ts @@ -0,0 +1,3 @@ +declare module "*.png"; +declare module "*.svg"; +declare module "*.gif"; diff --git a/src/components/ChattingList/ChatListItem.tsx b/src/components/ChattingList/ChatListItem.tsx new file mode 100644 index 0000000..38827fe --- /dev/null +++ b/src/components/ChattingList/ChatListItem.tsx @@ -0,0 +1,95 @@ +import styled from "styled-components"; +import { useNavigate } from "react-router-dom"; + +import { chattingInterface } from "../../assets/types/interfaces"; + +import { useRecoilValue } from "recoil"; +import { userArrayState, chatArrayState } from "../../assets/recoil/recoil"; +//img +import camera from "../../assets/images/camera-line.svg"; + +interface ChattingItemProps { + chatting: chattingInterface; +} + +const ChatListItem = ({ chatting }: ChattingItemProps) => { + const navigate = useNavigate(); + + const userArray = useRecoilValue(userArrayState); + const friendId = chatting.users.filter((id) => id !== 0)[0]; + const friendInfoById = userArray.filter((user) => user.id === friendId)[0]; + const lastChat = chatting.chatList.at(-1); + return ( + navigate(`/chatting/${friendInfoById.id}`)}> + + +
{friendInfoById.userName}
+
+ {lastChat?.text} + {/* • 0시간 */} +
+
+ +
+ ); +}; + +export default ChatListItem; + +const Box = styled.div` + display: flex; + width: 375px; + height: 52px; + padding: 0px 12px; + justify-content: center; + align-items: center; + + padding: 0 12px; + gap: 8px; +`; + +const UserImg = styled.img` + display: flex; + width: 52px; + height: 52px; + justify-content: center; + align-items: center; + flex-shrink: 0; + object-fit: cover; + + border-radius: 50%; + border: 0.7px solid rgba(252, 252, 252, 0.3); +`; + +const UserInfo = styled.div` + display: flex; + width: 283px; + flex-direction: column; + justify-content: flex-start; + align-items: flex-start; + gap: 4px; + + .UserName { + } + color: var(--gray-black, #000); + + font-size: 13px; + font-style: normal; + font-weight: 400; + line-height: 130%; + + .ChattingInfo { + color: var(--gray-gray-3, #808080); + + font-size: 13px; + font-style: normal; + font-weight: 400; + line-height: 130%; + } +`; + +const CameraIcon = styled.img` + width: 24px; + height: 24px; + padding-bottom: 0px; +`; diff --git a/src/components/ChattingList/MemoBox.tsx b/src/components/ChattingList/MemoBox.tsx new file mode 100644 index 0000000..bfa27b7 --- /dev/null +++ b/src/components/ChattingList/MemoBox.tsx @@ -0,0 +1,123 @@ +import React, { useState } from "react"; +import styled from "styled-components"; +import { color } from "../../assets/styles/color"; + +//img +import memocreate from "../../assets/images/mymemo.svg"; +import memobubble from "../../assets/images/othermemo.svg"; + +interface MemoBoxProps { + user: { + id: number; + userID: string; + profileImage: string; + memo: string; + userName: string; + }; +} + +const MemoBox = ({ user }: MemoBoxProps) => { + const [isEditing, setIsEditing] = useState(false); + const [inputMemo, setInputMemo] = useState(user.memo || ""); + + const isMe: boolean = user.id == 0; + + const handleBubbleClick = () => { + setIsEditing(!isEditing); + }; + + if (!user.memo && !isMe) { + return null; + } + + return ( + + {isMe ? ( + user.memo ? ( + + ) : isEditing ? ( +
+ + ••• +
+ ) : ( + + ) + ) : ( + + )} + + {user.memo && ( +
+ {user.memo} +
+ )} + + + {isMe ? "내 메모" : user.userName} +
+ ); +}; + +export default MemoBox; + +const Container = styled.div` + position: relative; + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + + .memo { + position: absolute; + width: 70px; + height: 20px; + top: 20px; + left: 0; + z-index: 5; + } +`; + +const MemoBubble = styled.img` + position: absolute; + width: 100px; + flex-shrink: 0; + top: 0px; + left: -14px; + z-index: 3; +`; + +const MyMemoBubble = styled(MemoBubble)` + top: 0px; + left: -6px; + width: 55px; +`; + +const MemoContent = styled.span``; + +const Profile = styled.img` + display: flex; + width: 70px; + height: 70px; + justify-content: center; + align-items: center; + flex-shrink: 0; + object-fit: cover; + + margin: 38px 0 0; + + border-radius: 50%; + border: 0.7px solid ${color.grey3}; +`; + +const Nicname = styled.div` + margin-top: 4px; + + color: #000; + text-align: center; + + font-size: 10px; + font-style: normal; + font-weight: 400; + line-height: 130%; +`; diff --git a/src/components/ChattingRoom/ChatInput.tsx b/src/components/ChattingRoom/ChatInput.tsx new file mode 100644 index 0000000..f9f7dbd --- /dev/null +++ b/src/components/ChattingRoom/ChatInput.tsx @@ -0,0 +1,292 @@ +import React, { useState, useEffect, useRef } from "react"; +import styled from "styled-components"; +import { color } from "../../assets/styles/color"; +import { useSender } from "../../assets/SenderContext"; +//data +import { useRecoilValue } from "recoil"; +import { chatArrayState } from "../../assets/recoil/recoil"; +//components +import ChattingItem from "./ChattingItem"; + +//img +import mic from "../../assets/images/mic.svg"; +import photo from "../../assets/images/photo.svg"; +import sticker from "../../assets/images/sticker.svg"; + +import camera from "../../assets/images/camera.svg"; +import sendingbtn from "../../assets/images/sendingbtn.svg"; + +function ChattingInput({ friendId }: { friendId: number }) { + const { sender, setSender } = useSender(); + const [inputMessage, setInputMessage] = useState(""); + + const chatArray = useRecoilValue(chatArrayState); + const chattingId: number = friendId - 1; + + const [messages, setMessages] = useState< + { text: string; sender: number; timestamp: string }[] + >(() => { + const storedMessages = localStorage.getItem(`chatMessages${friendId}`); + return storedMessages + ? JSON.parse(storedMessages) + : chatArray[chattingId].chatList; + }); + + // 제출시 실행되는 함수 + const containerRef = useRef(null); + const inputBoxRef = useRef(null); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (inputMessage.trim() !== "") { + const newMessage = { + text: inputMessage, + sender: sender, + timestamp: new Date().toString(), + }; + if (containerRef.current) { + containerRef.current.scrollTop = containerRef.current.scrollHeight; + } + + setMessages((prevMessages) => [...prevMessages, newMessage]); + setInputMessage(""); + } + }; + + const handleSendButtonClick = (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + handleSubmit(e); + }; + + useEffect(() => { + if (containerRef.current) { + containerRef.current.scrollTop = containerRef.current.scrollHeight; + } + localStorage.setItem(`chatMessages${friendId}`, JSON.stringify(messages)); + }, [messages, friendId]); + + // 전송버튼 팝업 + const [isFocused, setIsFocused] = useState(false); + const handleInputFocus = () => { + setIsFocused(true); + }; + + const handleInputBlur = () => { + setIsFocused(false); + }; + + // 엔터로 전송, shift+엔터로 줄바꿈 + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Enter" && !e.shiftKey) { + e.preventDefault(); + handleSubmit(e); + } + }; + + //줄 바뀌면 textarea 높이 같이 증가 + const autoAdjustTextarea = (element: HTMLTextAreaElement) => { + element.style.height = "auto"; + element.style.height = element.scrollHeight + "px"; + }; + + // //시간 계산 관련 코드 + const [isDateMessage, setIsDateMessage] = useState(false); + + const handleIsDate = ( + message: { text: string; sender: number; timestamp: string }, + index: number + ) => { + const timestamp = new Date(message.timestamp); + if (index === 0) { + setIsDateMessage(true); + } else if (index > 0) { + const prevMessage = messages[index - 1]; + const prevTimestamp = new Date(prevMessage.timestamp); + + const timeDifference = timestamp.getTime() - prevTimestamp.getTime(); + + if (timeDifference >= 60 * 60 * 1000) { + // 1시간 이상 차이 + const hours = timestamp.getHours(); + const minutes = timestamp.getMinutes(); + const amOrPm = hours < 12 ? "오전" : "오후"; + const newDate = `${amOrPm} ${hours % 12}:${minutes}`; + setIsDateMessage(true); + return newDate; + } else if (timeDifference >= 24 * 60 * 60 * 1000) { + // 1일 이상 차이 + const month = timestamp.getMonth() + 1; + const day = timestamp.getDate(); + const hours = timestamp.getHours(); + const minutes = timestamp.getMinutes(); + const amOrPm = hours < 12 ? "오전" : "오후"; + const newDate = `${month}월 ${day}일 ${amOrPm} ${ + hours % 12 + }:${minutes}`; + setIsDateMessage(true); + return newDate; + } else { + setIsDateMessage(false); + return null; // 시간 차이가 1시간 미만, 1일 미만인 경우 + } + } + }; + + return ( + <> + + {messages.map((message, index) => ( + + ))} + + + + + { + handleInputFocus(); + autoAdjustTextarea(e.target); + }} + onBlur={handleInputBlur} + value={inputMessage} + onChange={(e) => { + setInputMessage(e.target.value); + autoAdjustTextarea(e.target); + }} + onKeyDown={handleKeyDown} + ref={inputBoxRef} + /> + {isFocused ? ( + + + + ) : ( + + + + + + )} + + + + ); +} + +export default ChattingInput; + +const Container = styled.div` + display: flex; + flex-direction: column; + + width: 375px; + height: 672px; + flex-shrink: 0; + gap: 8px; + padding: 16px 0; + + overflow-y: scroll; + + &::-webkit-scrollbar { + display: none; + } +`; + +const InputContainer = styled.form` + position: absolute; + bottom: 8px; + display: flex; + align-items: flex-end; + justify-content: space-between; + background-color: #fff; + + width: 352px; + max-height: 110px; + flex-shrink: 0; + + border-radius: 20px; + padding: 4px; + border: 1px solid ${color.grey2}; +`; + +const CameraIcon = styled.img` + width: 34px; + height: 34px; + flex-shrink: 0; + margin: 0 4px; +`; + +const InputDiv = styled.form` + display: flex; + align-items: flex-end; + width: 100%; +`; + +const InputBox = styled.textarea` + color: ${color.black}; + width: 100%; + min-height: 42px; + max-height: 102px; + resize: none; + border: none; + outline: none; + word-break: break-all; + + font-family: "Pretendard-Regular"; + font-size: 13px; + font-style: normal; + font-weight: 400; + line-height: 130%; + + &::placeholder { + color: ${color.grey3}; + } + + &::-webkit-scrollbar { + display: none; + } +`; + +const IconDiv = styled.div` + display: flex; +`; + +const Icon = styled.img` + width: 20px; + height: 20px; + flex-shrink: 0; + + margin: 0 8px; +`; + +const SendButton = styled.button` + display: flex; + align-items: center; + background: none; + border: none; + padding: 0; + cursor: pointer; + + &:focus { + outline: none; + } +`; + +const SendBtnIcon = styled.img` + width: 59px; + height: 34px; + flex-shrink: 0; +`; diff --git a/src/components/ChattingRoom/ChattingDate.tsx b/src/components/ChattingRoom/ChattingDate.tsx new file mode 100644 index 0000000..5ed7182 --- /dev/null +++ b/src/components/ChattingRoom/ChattingDate.tsx @@ -0,0 +1,26 @@ +import React from "react"; +import styled from "styled-components"; +import { color } from "../../assets/styles/color"; + +interface DateItemProps { + date: string; + isDateMessage: boolean; +} + +function ChattingDate({ date, isDateMessage }: DateItemProps) { + if (isDateMessage) { + return {date}; + } + return null; +} + +export default ChattingDate; + +const DateContainer = styled.div` + display: flex; + justify-content: center; + align-items: center; + padding: 8px; + font-size: 12px; + color: ${color.grey3}; +`; diff --git a/src/components/ChattingRoom/ChattingItem.tsx b/src/components/ChattingRoom/ChattingItem.tsx new file mode 100644 index 0000000..950848c --- /dev/null +++ b/src/components/ChattingRoom/ChattingItem.tsx @@ -0,0 +1,99 @@ +import React from "react"; +import styled, { css } from "styled-components"; +import { color } from "../../assets/styles/color"; +import { useSender } from "../../assets/SenderContext"; + +//data +import { useRecoilValue } from "recoil"; +import { userArrayState } from "../../assets/recoil/recoil"; + +interface ChattingItemProps { + message: string; + sender: number; + isLastItem: boolean; + + timestamp: string; + isDateMessage: boolean; +} + +function ChattingItem({ + message, + sender, + isLastItem, + timestamp, + isDateMessage, +}: ChattingItemProps) { + const userArray = useRecoilValue(userArrayState); + const currentUser = userArray[sender === 0 ? 0 : sender]; + const { sender: contextSender } = useSender(); + const isMe = sender === contextSender; + + return ( + <> + {isDateMessage && {timestamp}} + + {isMe ? ( + <> + ) : isLastItem ? ( + + ) : ( + + )} + {message} + + + ); +} + +export default ChattingItem; + +const DateContainer = styled.div` + display: flex; + justify-content: center; + align-items: center; + padding: 8px; + font-size: 12px; + color: ${color.grey3}; +`; + +const ChattingItemContainer = styled.div<{ isMe: boolean }>` + display: flex; + align-items: flex-end; + justify-content: ${(props) => (props.isMe ? "flex-end" : "flex-start")}; +`; + +const ChattingItemContent = styled.div<{ isMe: boolean }>` + display: flex; + padding: 12px; + align-items: center; + max-width: 70%; + margin: 0 12px; + + border-radius: 20px; + background: ${(props) => (props.isMe ? color.grey1 : color.white)}; + + ${(props) => + !props.isMe && + css` + border: 1px solid ${color.grey2}; + `} +`; + +const UserImg = styled.img` + display: flex; + width: 26px; + height: 26px; + border-radius: 50%; + justify-content: center; + align-items: center; + + margin: 0 0 4px 15px; +`; + +const NoneImg = styled.div` + display: flex; + width: 26px; + height: 26px; + + margin: 0 0 4px 15px; +`; diff --git a/src/components/ChattingRoom/TopContainer.tsx b/src/components/ChattingRoom/TopContainer.tsx new file mode 100644 index 0000000..1f008fc --- /dev/null +++ b/src/components/ChattingRoom/TopContainer.tsx @@ -0,0 +1,109 @@ +import React from "react"; +import { useNavigate } from "react-router-dom"; +import styled from "styled-components"; +import { color } from "../../assets/styles/color"; +import { useSender } from "../../assets/SenderContext"; +//data +import { useRecoilValue } from "recoil"; +import { userArrayState } from "../../assets/recoil/recoil"; + +//img +import back from "../../assets/images/back.svg"; +import call from "../../assets/images/phone.svg"; +import video from "../../assets/images/video.svg"; + +function TopContainer({ friendId }: { friendId: number }) { + const navigate = useNavigate(); + const { sender, setSender } = useSender(); + const userArray = useRecoilValue(userArrayState); + const currentUser = userArray[sender === 0 ? friendId : 0]; + + const handleUserInfoClick = () => { + setSender(sender === 0 ? friendId : 0); + }; + + const handleBackClick = () => { + setSender(0); + navigate(-1); + }; + return ( + <> + + + + + +
+ {currentUser.userName} + {currentUser.userID} +
+
+
+ + + + +
+ + ); +} + +export default TopContainer; + +const Container = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + + width: 375px; + height: 48px; + flex-shrink: 0; + padding: 0 8px; + border-bottom: 1px solid ${color.grey1}; +`; + +const Box = styled.div` + display: flex; + align-items: center; + cursor: pointer; +`; + +const UserInfo = styled.div` + display: flex; +`; + +const Profile = styled.img` + display: flex; + width: 36px; + height: 36px; + border-radius: 50%; + justify-content: center; + align-items: center; + flex-shrink: 0; + + margin-right: 8px; +`; + +const UserName = styled.div` + color: ${color.black}; + font-size: 16px; + font-style: normal; + font-weight: 500; + line-height: 130%; +`; + +const UserID = styled.div` + color: ${color.grey3}; + + font-size: 13px; + font-style: normal; + font-weight: 400; +`; + +const Icon = styled.img` + width: 24px; + height: 24px; + flex-shrink: 0; + + margin: 0 12px 0 10px; +`; diff --git a/src/components/common/FriendItem.tsx b/src/components/common/FriendItem.tsx new file mode 100644 index 0000000..65f6a93 --- /dev/null +++ b/src/components/common/FriendItem.tsx @@ -0,0 +1,98 @@ +import React, { useState } from "react"; +import { useNavigate } from "react-router-dom"; +import styled from "styled-components"; +import { color } from "../../assets/styles/color"; + +//img +import check from "../../assets/images/check.svg"; +import checked from "../../assets/images/checked.svg"; + +interface FriendItemProps { + user: { + id: number; + userID: string; + profileImage: string; + userName: string; + }; + emp: boolean; +} + +const FriendItem = ({ user, emp = false }: FriendItemProps) => { + const navigate = useNavigate(); + const [isChecked, setIsChecked] = useState(false); + const [isSearching, setIsSearching] = useState(false); + // 검색 페이지일 때만 체크 표시 뜨게 하는 state + const handleToggleCheck = () => { + if (!isSearching) { + navigate(`/profile/${user.id}`); + } else { + setIsChecked(!isChecked); + } + }; + + return ( + + + +
{user.userName}
+ {user.userID} +
+ {isSearching ? : <>} +
+ ); +}; + +export default FriendItem; + +const Box = styled.div` + display: flex; + width: 375px; + height: 54px; + padding: 0px 12px; + justify-content: center; + align-items: center; + + padding: 0 12px; + gap: 8px; +`; + +const UserImg = styled.img` + display: flex; + width: 54px; + height: 54px; + justify-content: center; + align-items: center; + flex-shrink: 0; + object-fit: cover; + + border-radius: 50%; + border: 0.7px solid rgba(252, 252, 252, 0.3); +`; + +const UserInfo = styled.div<{ emp: boolean }>` + display: flex; + width: 283px; + flex-direction: column; + justify-content: center; + align-items: flex-start; + gap: ${(props) => (props.emp ? "1px" : "4px")}; + + font-size: ${(props) => (props.emp ? "0.9rem" : "0.6rem")}; + font-weight: ${(props) => (props.emp ? 600 : 500)}; + font-style: normal; + line-height: 130%; + + .UserName { + } + color: ${color.black}; + + .ChattingInfo { + color: ${color.black}; + } +`; + +const CameraIcon = styled.img` + width: 24px; + height: 24px; + padding-bottom: 0px; +`; diff --git a/src/components/common/SearchingBar.tsx b/src/components/common/SearchingBar.tsx new file mode 100644 index 0000000..a6bc27f --- /dev/null +++ b/src/components/common/SearchingBar.tsx @@ -0,0 +1,149 @@ +import React, { useState } from "react"; +import { useParams, useNavigate, useLocation } from "react-router-dom"; +import styled from "styled-components"; +import { color } from "../../assets/styles/color"; + +import { useRecoilValue } from "recoil"; +import { meAtom } from "../../assets/recoil/recoil"; + +//img +import back from "../../assets/images/back.svg"; +import search from "../../assets/images/search.svg"; +import write from "../../assets/images/write.svg"; +import share from "../../assets/images/share.svg"; + +interface SearchingBarProps { + showSearch: boolean; + keyword: string; + setKeyword: (term: string) => void; +} + +function SearchingBar({ + showSearch = true, + keyword, + setKeyword, +}: SearchingBarProps) { + const meUser = useRecoilValue(meAtom); + + const { chat_id } = useParams(); + const navigate = useNavigate(); + const location = useLocation(); + const isChatting = location.pathname === "/chatting"; + + const handleIconClick = () => { + if (chat_id) { + navigate("/chatting"); + } else { + navigate("/"); + } + }; + return ( +
+ + + {showSearch ? ( + + ) : ( + + )} + {meUser.userID} + + + {isChatting ? ( + navigate("/chatting")} /> + ) : ( + navigate("/chatting")} /> + //새 채팅 만드는 페이지로 이동해야함 + )} + + + + + setKeyword(e.target.value)} + > + +
+ ); +} + +export default SearchingBar; + +const TopContainer = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + + width: 100%; + height: 48px; + flex-shrink: 0; + padding: 0 4px; +`; + +const Box = styled.div` + display: flex; + align-items: center; + cursor: pointer; +`; + +const UserID = styled.div` + color: ${color.black}; + + font-size: 20px; + font-style: normal; + font-weight: 700; + line-height: 130%; +`; + +const Icon = styled.img` + width: 24px; + height: 24px; + flex-shrink: 0; + + margin: 0 12px 0 10px; +`; + +const IconDiv = styled.div` + width: 24px; + height: 24px; + margin: 0 12px 0 10px; +`; + +const SearchingContainer = styled.div` + display: flex; + justify-content: flex-start; + align-items: center; + width: 93%; + height: 6%; + padding: 6px 4px; + margin: 6px 0 8px; + gap: 4px; + + border-radius: 8px; + background: ${color.grey1}; + + input { + width: 321px; + border: none; + outline: none; + background-color: transparent; + + &::placeholder { + color: ${color.grey3}; + + font-size: 13px; + font-style: normal; + font-weight: 500; + line-height: 130%; + } + } +`; diff --git a/src/index.css b/src/index.css index ec2585e..137045c 100644 --- a/src/index.css +++ b/src/index.css @@ -1,13 +1,18 @@ +@font-face { + font-family: "Pretendard-Regular"; + src: url("./assets/fonts/Pretendard-Regular.ttf"); +} + body { margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; + font-family: -apple-system, BlinkMacSystemFont, "Pretendard-Regular", + "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", + "Droid Sans", "Helvetica Neue", sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; } diff --git a/src/index.js b/src/index.js deleted file mode 100644 index d563c0f..0000000 --- a/src/index.js +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import './index.css'; -import App from './App'; -import reportWebVitals from './reportWebVitals'; - -const root = ReactDOM.createRoot(document.getElementById('root')); -root.render( - - - -); - -// If you want to start measuring performance in your app, pass a function -// to log results (for example: reportWebVitals(console.log)) -// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals -reportWebVitals(); diff --git a/src/index.tsx b/src/index.tsx new file mode 100644 index 0000000..cfd7784 --- /dev/null +++ b/src/index.tsx @@ -0,0 +1,13 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import "./index.css"; +import App from "./App"; + +const root = ReactDOM.createRoot( + document.getElementById("root") as HTMLElement +); +root.render( + + + +); diff --git a/src/logo.svg b/src/logo.svg deleted file mode 100644 index 9dfc1c0..0000000 --- a/src/logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/pages/ChattingListPage.tsx b/src/pages/ChattingListPage.tsx new file mode 100644 index 0000000..3c2b17e --- /dev/null +++ b/src/pages/ChattingListPage.tsx @@ -0,0 +1,108 @@ +import React, { useState, useEffect } from "react"; +import styled from "styled-components"; + +//data +import { useRecoilValue } from "recoil"; +import { userArrayState, chatArrayState } from "../assets/recoil/recoil"; +import { color } from "../assets/styles/color"; + +//components +import SearchingBar from "../components/common/SearchingBar"; +import MemoBox from "../components/ChattingList/MemoBox"; +import ChatListItem from "../components/ChattingList/ChatListItem"; + +//bar +import status from "../assets/images/status.svg"; + +function ChattingListPage() { + const userArray = useRecoilValue(userArrayState); + const chatArray = useRecoilValue(chatArrayState); + const [keyword, setKeyword] = useState(""); + + const filteredUsers = userArray.filter((user) => + user.userName.toLowerCase().includes(keyword.toLowerCase()) + ); + const filteredUserId = filteredUsers.map((user) => user.id - 1); + + const filteredChats = chatArray.filter((chatting) => + filteredUserId.includes(chatting.chattingId) + ); + + return ( + + + + + {userArray.map((user) => ( + + ))} + + + 메세지 + 요청 + + + {filteredChats.map((chatting) => ( + + ))} + + + ); +} + +export default ChattingListPage; + +const Container = styled.div` + background-color: #fff; + width: 375px; + height: 812px; +`; + +const StatusBar = styled.img` + width: 375px; + height: 44px; +`; + +const MemoContainer = styled.div` + display: flex; + margin-bottom: 16px; + margin-left: 6px; + gap: 27px; + + overflow-x: scroll; + white-space: nowrap; + &::-webkit-scrollbar { + display: none; + } +`; + +const Guide = styled.div` + display: flex; + justify-content: space-between; + padding: 0 18px 0 15px; + + .message { + color: ${color.black}; + font-size: 13px; + font-weight: 600; + } + + .request { + color: ${color.grey3}; + font-size: 13px; + font-weight: 500; + + cursor: pointer; + } +`; + +const ChatListContainer = styled.div` + display: flex; + flex-direction: column; + gap: 16px; + margin-top: 24px; +`; diff --git a/src/pages/ChattingRoom.tsx b/src/pages/ChattingRoom.tsx new file mode 100644 index 0000000..c31f351 --- /dev/null +++ b/src/pages/ChattingRoom.tsx @@ -0,0 +1,45 @@ +import React, { useState, useEffect } from "react"; +import { useParams } from "react-router-dom"; +import styled from "styled-components"; + +//components +import TopContainer from "../components/ChattingRoom/TopContainer"; +import ChatInput from "../components/ChattingRoom/ChatInput"; + +//bar +import status from "../assets/images/status.svg"; + +function ChattingRoom() { + const { chat_id } = useParams(); + const friendId: number = parseInt(chat_id || "1", 10); + return ( + + + + + + ); +} + +export default ChattingRoom; + +const Container = styled.div` + position: relative; + display: flex; + flex-direction: column; + align-items: center; + + background-color: #fff; + width: 375px; + height: 812px; +`; + +const StatusBar = styled.img` + width: 375px; + height: 44px; +`; + +const Bar = styled.img` + width: 375px; + height: 34px; +`; diff --git a/src/pages/FriendsListPage.tsx b/src/pages/FriendsListPage.tsx new file mode 100644 index 0000000..55bacf4 --- /dev/null +++ b/src/pages/FriendsListPage.tsx @@ -0,0 +1,84 @@ +import React, { useState, useEffect } from "react"; +import styled from "styled-components"; + +//data +import { useRecoilValue } from "recoil"; +import { meAtom, friendsListAtom } from "../assets/recoil/recoil"; +import { color } from "../assets/styles/color"; + +//components +import SearchingBar from "../components/common/SearchingBar"; +import FriendItem from "../components/common/FriendItem"; + +//bar +import status from "../assets/images/status.svg"; + +function FriendsListPage() { + const meUser = useRecoilValue(meAtom); + const otherUsers = useRecoilValue(friendsListAtom); + const friendCount = otherUsers.length; + + const [keyword, setKeyword] = useState(""); + + const filteredUsers = otherUsers.filter((user) => + user.userName.toLowerCase().includes(keyword.toLowerCase()) + ); + + return ( + + + + + +
+ 친구 {friendCount} +
+ + {filteredUsers.map((user) => ( + + ))} + +
+ ); +} + +export default FriendsListPage; + +const Container = styled.div` + background-color: #fff; + width: 375px; + height: 812px; +`; + +const StatusBar = styled.img` + width: 375px; + height: 44px; +`; + +const Guide = styled.div` + display: flex; + flex-direction: column; + padding: 0 18px 0 15px; + margin: 16px 0; + + .line { + border-bottom: 1px solid ${color.grey1}; + margin-bottom: 16px; + } +`; + +const FriendListContainer = styled.div` + display: flex; + flex-direction: column; + gap: 16px; + margin-top: 24px; +`; + +const Bar = styled.img` + width: 375px; + height: 34px; +`; diff --git a/src/pages/ProfilePage.tsx b/src/pages/ProfilePage.tsx new file mode 100644 index 0000000..280813a --- /dev/null +++ b/src/pages/ProfilePage.tsx @@ -0,0 +1,174 @@ +import React, { useState, useEffect } from "react"; +import { useParams, useNavigate } from "react-router-dom"; +import styled from "styled-components"; + +import { color } from "../assets/styles/color"; + +import { useRecoilValue } from "recoil"; +import { userArrayState } from "../assets/recoil/recoil"; + +//img +import back from "../assets/images/back.svg"; +import link from "../assets/images/link.svg"; + +import insta from "../assets/images/insta.svg"; +import behance from "../assets/images/behance.svg"; +import github from "../assets/images/github.svg"; + +//bar + +import status from "../assets/images/status.svg"; + +function ProfilePage() { + const navigate = useNavigate(); + const { user_id } = useParams(); + const userArray = useRecoilValue(userArrayState); + const userInfo = userArray.find( + (user) => user.id === parseInt(user_id || "0", 10) + ); + + return ( + + + navigate(-1)} /> + + + {userInfo?.userName} + {userInfo?.email} + + + 다른 사람이 나와 소통할 수 있는 방법 + +
+ + instagram +
+ + + +
+ + github +
+ + + + +
+ + Behance +
+ + +
+
+ ); +} + +export default ProfilePage; + +const Container = styled.div` + display: flex; + flex-direction: column; + background-color: #fff; + width: 375px; + height: 812px; + + .back { + width: 24px; + height: 24px; + flex-shrink: 0; + margin: 16px 0 0 12px; + } +`; + +const UserInfo = styled.div` + display: flex; + flex-direction: column; + align-items: center; + + border-bottom: 1px solid ${color.grey1}; + + .username { + font-size: 24px; + font-style: normal; + font-weight: 500; + } + + .usermail { + color: ${color.grey3}; + text-align: center; + font-size: 13px; + font-weight: 400; + line-height: 130%; + + margin: 4px 0 24px; + } +`; + +const Profile = styled.img` + display: flex; + width: 160px; + height: 160px; + margin: 16px; + border-radius: 50%; + border: 1px solid ${color.grey2}; + justify-content: center; + align-items: center; + flex-shrink: 0; + object-fit: cover; +`; + +const LinkContainer = styled.div` + margin-top: 24px; + padding-left: 12px; + + .link-title { + color: ${color.grey3}; + font-size: 13px; + font-style: normal; + font-weight: 400; + } +`; + +const Link = styled.a.attrs({ target: "_blank" })` + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 16px; + padding-right: 6px; + + cursor: pointer; + text-decoration: none; + + .box { + display: flex; + align-items: center; + } + + .link-icon { + width: 32px; + height: 32px; + border-radius: 50%; + justify-content: center; + align-items: center; + margin-right: 8px; + } + + span { + color: black; + font-size: 16px; + font-style: normal; + font-weight: 500; + } +`; + +const StatusBar = styled.img` + width: 375px; + height: 44px; +`; + +const Bar = styled.img` + width: 375px; + height: 34px; +`; diff --git a/src/reportWebVitals.js b/src/reportWebVitals.js deleted file mode 100644 index 5253d3a..0000000 --- a/src/reportWebVitals.js +++ /dev/null @@ -1,13 +0,0 @@ -const reportWebVitals = onPerfEntry => { - if (onPerfEntry && onPerfEntry instanceof Function) { - import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { - getCLS(onPerfEntry); - getFID(onPerfEntry); - getFCP(onPerfEntry); - getLCP(onPerfEntry); - getTTFB(onPerfEntry); - }); - } -}; - -export default reportWebVitals; diff --git a/src/setupTests.js b/src/setupTests.js deleted file mode 100644 index 8f2609b..0000000 --- a/src/setupTests.js +++ /dev/null @@ -1,5 +0,0 @@ -// jest-dom adds custom jest matchers for asserting on DOM nodes. -// allows you to do things like: -// expect(element).toHaveTextContent(/react/i) -// learn more: https://github.com/testing-library/jest-dom -import '@testing-library/jest-dom'; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..9d379a3 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,20 @@ +{ + "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"] +}