diff --git a/README.md b/README.md
index 6ab8e98..3582bf2 100644
--- a/README.md
+++ b/README.md
@@ -1,66 +1,56 @@
-# 서론
+# 4주차 미션: React-Messenger 💌
-안녕하세요 🙌🏻 18기 프론트 운영진 김문기입니다. 이번 미션에서는 드디어 투두리스트에서 벗어나 새로운 프로젝트인 **messenger** 만들기를 진행합니다.
+## 서론
-이번주는 특별히 **디자이너와의 협업**으로 진행되는 미션입니다. 디자이너분께서 열심히 리디자인 한 메신저 프로젝트를 여러분들께서 구현해주시면 됩니다.
+안녕하세요 🙌🏻 프론트엔드 18기 신동현입니다.
-동시에, 이번주부터는 새로 **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..3d83b6f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,9 +11,19 @@
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
+ "@types/jest": "^29.5.5",
+ "@types/node": "^20.7.1",
+ "@types/react": "^18.2.23",
+ "@types/react-dom": "^18.2.8",
+ "@types/react-router-dom": "^5.3.3",
+ "@types/styled-components": "^5.1.28",
"react": "^18.2.0",
"react-dom": "^18.2.0",
+ "react-router-dom": "^6.16.0",
"react-scripts": "5.0.1",
+ "styled-components": "^6.0.8",
+ "styled-reset": "^4.5.1",
+ "typescript": "^5.2.2",
"web-vitals": "^2.1.4"
}
},
@@ -53,6 +63,83 @@
"node": ">=6.0.0"
}
},
+ "node_modules/@babel/cli": {
+ "version": "7.23.0",
+ "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.23.0.tgz",
+ "integrity": "sha512-17E1oSkGk2IwNILM4jtfAvgjt+ohmpfBky8aLerUfYZhiPNg7ca+CRCxZn8QDxwNhV/upsc2VHBCqGFIR+iBfA==",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.17",
+ "commander": "^4.0.1",
+ "convert-source-map": "^2.0.0",
+ "fs-readdir-recursive": "^1.1.0",
+ "glob": "^7.2.0",
+ "make-dir": "^2.1.0",
+ "slash": "^2.0.0"
+ },
+ "bin": {
+ "babel": "bin/babel.js",
+ "babel-external-helpers": "bin/babel-external-helpers.js"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "optionalDependencies": {
+ "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.3",
+ "chokidar": "^3.4.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/cli/node_modules/commander": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
+ "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/@babel/cli/node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="
+ },
+ "node_modules/@babel/cli/node_modules/make-dir": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
+ "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
+ "dependencies": {
+ "pify": "^4.0.1",
+ "semver": "^5.6.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@babel/cli/node_modules/pify": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
+ "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@babel/cli/node_modules/semver": {
+ "version": "5.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
+ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
+ "bin": {
+ "semver": "bin/semver"
+ }
+ },
+ "node_modules/@babel/cli/node_modules/slash": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
+ "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/@babel/code-frame": {
"version": "7.22.13",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
@@ -530,6 +617,20 @@
"@babel/core": "^7.13.0"
}
},
+ "node_modules/@babel/plugin-external-helpers": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-external-helpers/-/plugin-external-helpers-7.22.5.tgz",
+ "integrity": "sha512-ngnNEWxmykPk82mH4ajZT0qTztr3Je6hrMuKAslZVM8G1YZTENJSYwrIGtt6KOtznug3exmAtF4so/nPqJuA4A==",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
"node_modules/@babel/plugin-proposal-class-properties": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz",
@@ -596,6 +697,25 @@
"@babel/core": "^7.0.0-0"
}
},
+ "node_modules/@babel/plugin-proposal-object-rest-spread": {
+ "version": "7.20.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz",
+ "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==",
+ "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead.",
+ "dependencies": {
+ "@babel/compat-data": "^7.20.5",
+ "@babel/helper-compilation-targets": "^7.20.7",
+ "@babel/helper-plugin-utils": "^7.20.2",
+ "@babel/plugin-syntax-object-rest-spread": "^7.8.3",
+ "@babel/plugin-transform-parameters": "^7.20.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
"node_modules/@babel/plugin-proposal-optional-chaining": {
"version": "7.21.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz",
@@ -2270,6 +2390,24 @@
"postcss-selector-parser": "^6.0.10"
}
},
+ "node_modules/@emotion/is-prop-valid": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz",
+ "integrity": "sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==",
+ "dependencies": {
+ "@emotion/memoize": "^0.8.1"
+ }
+ },
+ "node_modules/@emotion/memoize": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz",
+ "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA=="
+ },
+ "node_modules/@emotion/unitless": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz",
+ "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ=="
+ },
"node_modules/@eslint-community/eslint-utils": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@@ -3132,6 +3270,12 @@
"resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz",
"integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A=="
},
+ "node_modules/@nicolo-ribaudo/chokidar-2": {
+ "version": "2.1.8-no-fsevents.3",
+ "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz",
+ "integrity": "sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==",
+ "optional": true
+ },
"node_modules/@nicolo-ribaudo/eslint-scope-5-internals": {
"version": "5.1.1-v1",
"resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz",
@@ -3241,6 +3385,14 @@
}
}
},
+ "node_modules/@remix-run/router": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.9.0.tgz",
+ "integrity": "sha512-bV63itrKBC0zdT27qYm6SDZHlkXwFL1xMBuhkn+X7l0+IIhNaH5wuuvZKp6eKhCD4KFhujhfhCT1YxXW6esUIA==",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
"node_modules/@rollup/plugin-babel": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
@@ -3559,104 +3711,6 @@
"url": "https://github.com/sponsors/gregberge"
}
},
- "node_modules/@testing-library/dom": {
- "version": "9.3.3",
- "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.3.tgz",
- "integrity": "sha512-fB0R+fa3AUqbLHWyxXa2kGVtf1Fe1ZZFr0Zp6AIbIAzXb2mKbEXl+PCQNUOaq5lbTab5tfctfXRNsWXxa2f7Aw==",
- "peer": true,
- "dependencies": {
- "@babel/code-frame": "^7.10.4",
- "@babel/runtime": "^7.12.5",
- "@types/aria-query": "^5.0.1",
- "aria-query": "5.1.3",
- "chalk": "^4.1.0",
- "dom-accessibility-api": "^0.5.9",
- "lz-string": "^1.5.0",
- "pretty-format": "^27.0.2"
- },
- "engines": {
- "node": ">=14"
- }
- },
- "node_modules/@testing-library/dom/node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "peer": true,
- "dependencies": {
- "color-convert": "^2.0.1"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/@testing-library/dom/node_modules/aria-query": {
- "version": "5.1.3",
- "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz",
- "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==",
- "peer": true,
- "dependencies": {
- "deep-equal": "^2.0.5"
- }
- },
- "node_modules/@testing-library/dom/node_modules/chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
- "peer": true,
- "dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
- }
- },
- "node_modules/@testing-library/dom/node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "peer": true,
- "dependencies": {
- "color-name": "~1.1.4"
- },
- "engines": {
- "node": ">=7.0.0"
- }
- },
- "node_modules/@testing-library/dom/node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "peer": true
- },
- "node_modules/@testing-library/dom/node_modules/has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
- "peer": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/@testing-library/dom/node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
- "peer": true,
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/@testing-library/jest-dom": {
"version": "5.17.0",
"resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.17.0.tgz",
@@ -4006,6 +4060,20 @@
"@types/node": "*"
}
},
+ "node_modules/@types/history": {
+ "version": "4.7.11",
+ "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz",
+ "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA=="
+ },
+ "node_modules/@types/hoist-non-react-statics": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
+ "integrity": "sha512-YIQtIg4PKr7ZyqNPZObpxfHsHEmuB8dXCxd6qVcGuQVDK2bpsF7bYNnBJ4Nn7giuACZg+WewExgrtAJ3XnA4Xw==",
+ "dependencies": {
+ "@types/react": "*",
+ "hoist-non-react-statics": "^3.3.0"
+ }
+ },
"node_modules/@types/html-minifier-terser": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz",
@@ -4297,9 +4365,9 @@
"integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw=="
},
"node_modules/@types/node": {
- "version": "20.6.4",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-20.6.4.tgz",
- "integrity": "sha512-nU6d9MPY0NBUMiE/nXd2IIoC4OLvsLpwAjheoAeuzgvDZA1Cb10QYg+91AF6zQiKWRN5i1m07x6sMe0niBznoQ=="
+ "version": "20.7.1",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.7.1.tgz",
+ "integrity": "sha512-LT+OIXpp2kj4E2S/p91BMe+VgGX2+lfO+XTpfXhh+bCk2LkQtHZSub8ewFBMGP5ClysPjTDFa4sMI8Q3n4T0wg=="
},
"node_modules/@types/parse-json": {
"version": "4.0.0",
@@ -4332,9 +4400,9 @@
"integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw=="
},
"node_modules/@types/react": {
- "version": "18.2.22",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.22.tgz",
- "integrity": "sha512-60fLTOLqzarLED2O3UQImc/lsNRgG0jE/a1mPW9KjMemY0LMITWEsbS4VvZ4p6rorEHd5YKxxmMKSDK505GHpA==",
+ "version": "18.2.23",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.23.tgz",
+ "integrity": "sha512-qHLW6n1q2+7KyBEYnrZpcsAmU/iiCh9WGCKgXvMxx89+TYdJWRjZohVIo9XTcoLhfX3+/hP0Pbulu3bCZQ9PSA==",
"dependencies": {
"@types/prop-types": "*",
"@types/scheduler": "*",
@@ -4342,13 +4410,32 @@
}
},
"node_modules/@types/react-dom": {
- "version": "18.2.7",
- "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.7.tgz",
- "integrity": "sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==",
+ "version": "18.2.8",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.8.tgz",
+ "integrity": "sha512-bAIvO5lN/U8sPGvs1Xm61rlRHHaq5rp5N3kp9C+NJ/Q41P8iqjkXSu0+/qu8POsjH9pNWb0OYabFez7taP7omw==",
+ "dependencies": {
+ "@types/react": "*"
+ }
+ },
+ "node_modules/@types/react-router": {
+ "version": "5.1.20",
+ "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz",
+ "integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==",
"dependencies": {
+ "@types/history": "^4.7.11",
"@types/react": "*"
}
},
+ "node_modules/@types/react-router-dom": {
+ "version": "5.3.3",
+ "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz",
+ "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==",
+ "dependencies": {
+ "@types/history": "^4.7.11",
+ "@types/react": "*",
+ "@types/react-router": "*"
+ }
+ },
"node_modules/@types/resolve": {
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
@@ -4412,6 +4499,21 @@
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz",
"integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw=="
},
+ "node_modules/@types/styled-components": {
+ "version": "5.1.28",
+ "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.28.tgz",
+ "integrity": "sha512-nu0VKNybkjvUqJAXWtRqKd7j3iRUl8GbYSTvZNuIBJcw/HUp1Y4QUXNLlj7gcnRV/t784JnHAlvRnSnE3nPbJA==",
+ "dependencies": {
+ "@types/hoist-non-react-statics": "*",
+ "@types/react": "*",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@types/stylis": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.1.tgz",
+ "integrity": "sha512-OSaMrXUKxVigGlKRrET39V2xdhzlztQ9Aqumn1WbCBKHOi9ry7jKSd7rkyj0GzmWaU960Rd+LpOFpLfx5bMQAg=="
+ },
"node_modules/@types/testing-library__jest-dom": {
"version": "5.14.9",
"resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.9.tgz",
@@ -5826,6 +5928,14 @@
"node": ">= 6"
}
},
+ "node_modules/camelize": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
+ "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/caniuse-api": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz",
@@ -6261,6 +6371,14 @@
"postcss": "^8.4"
}
},
+ "node_modules/css-color-keywords": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
+ "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/css-declaration-sorter": {
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz",
@@ -6442,6 +6560,16 @@
"resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz",
"integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w=="
},
+ "node_modules/css-to-react-native": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
+ "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==",
+ "dependencies": {
+ "camelize": "^1.0.0",
+ "css-color-keywords": "^1.0.0",
+ "postcss-value-parser": "^4.0.2"
+ }
+ },
"node_modules/css-tree": {
"version": "1.0.0-alpha.37",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz",
@@ -8549,6 +8677,11 @@
"resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.4.tgz",
"integrity": "sha512-INM/fWAxMICjttnD0DX1rBvinKskj5G1w+oy/pnm9u/tSlnBrzFonJMcalKJ30P8RRsPzKcCG7Q8l0jx5Fh9YQ=="
},
+ "node_modules/fs-readdir-recursive": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz",
+ "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA=="
+ },
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -8904,6 +9037,19 @@
"he": "bin/he"
}
},
+ "node_modules/hoist-non-react-statics": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
+ "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
+ "dependencies": {
+ "react-is": "^16.7.0"
+ }
+ },
+ "node_modules/hoist-non-react-statics/node_modules/react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+ },
"node_modules/hoopy": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz",
@@ -14686,6 +14832,36 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-router": {
+ "version": "6.16.0",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.16.0.tgz",
+ "integrity": "sha512-VT4Mmc4jj5YyjpOi5jOf0I+TYzGpvzERy4ckNSvSh2RArv8LLoCxlsZ2D+tc7zgjxcY34oTz2hZaeX5RVprKqA==",
+ "dependencies": {
+ "@remix-run/router": "1.9.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8"
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "6.16.0",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.16.0.tgz",
+ "integrity": "sha512-aTfBLv3mk/gaKLxgRDUPbPw+s4Y/O+ma3rEN1u8EgEpLpPe6gNjIsWt9rxushMHHMb7mSwxRGdGlGdvmFsyPIg==",
+ "dependencies": {
+ "@remix-run/router": "1.9.0",
+ "react-router": "6.16.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8",
+ "react-dom": ">=16.8"
+ }
+ },
"node_modules/react-scripts": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz",
@@ -15513,6 +15689,11 @@
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
},
+ "node_modules/shallowequal": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
+ "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
+ },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -16009,6 +16190,60 @@
"webpack": "^5.0.0"
}
},
+ "node_modules/styled-components": {
+ "version": "6.0.8",
+ "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.0.8.tgz",
+ "integrity": "sha512-AwI02MTWZwqjzfXgR5QcbmcSn5xVjY4N2TLjSuYnmuBGF3y7GicHz3ysbpUq2EMJP5M8/Nc22vcmF3V3WNZDFA==",
+ "dependencies": {
+ "@babel/cli": "^7.21.0",
+ "@babel/core": "^7.21.0",
+ "@babel/helper-module-imports": "^7.18.6",
+ "@babel/plugin-external-helpers": "^7.18.6",
+ "@babel/plugin-proposal-class-properties": "^7.18.6",
+ "@babel/plugin-proposal-object-rest-spread": "^7.20.7",
+ "@babel/preset-env": "^7.20.2",
+ "@babel/preset-react": "^7.18.6",
+ "@babel/preset-typescript": "^7.21.0",
+ "@babel/traverse": "^7.21.2",
+ "@emotion/is-prop-valid": "^1.2.1",
+ "@emotion/unitless": "^0.8.0",
+ "@types/stylis": "^4.0.2",
+ "css-to-react-native": "^3.2.0",
+ "csstype": "^3.1.2",
+ "postcss": "^8.4.23",
+ "shallowequal": "^1.1.0",
+ "stylis": "^4.3.0",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/styled-components"
+ },
+ "peerDependencies": {
+ "babel-plugin-styled-components": ">= 2",
+ "react": ">= 16.8.0",
+ "react-dom": ">= 16.8.0"
+ },
+ "peerDependenciesMeta": {
+ "babel-plugin-styled-components": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/styled-reset": {
+ "version": "4.5.1",
+ "resolved": "https://registry.npmjs.org/styled-reset/-/styled-reset-4.5.1.tgz",
+ "integrity": "sha512-6EvFWZRwaFRFxiPYMwmnzOe33rDkw5r9jIU0eEi49bkt6VSrvjeMp2ZOw/YFbw5SVs81llIY+5fzHtR2/VBZfQ==",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "peerDependencies": {
+ "styled-components": ">=4.0.0 || >=5.0.0 || >=6.0.0"
+ }
+ },
"node_modules/stylehacks": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz",
@@ -16024,6 +16259,11 @@
"postcss": "^8.2.15"
}
},
+ "node_modules/stylis": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.0.tgz",
+ "integrity": "sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ=="
+ },
"node_modules/sucrase": {
"version": "3.34.0",
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz",
@@ -16653,16 +16893,15 @@
}
},
"node_modules/typescript": {
- "version": "4.9.5",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
- "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
- "peer": true,
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
+ "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
- "node": ">=4.2.0"
+ "node": ">=14.17"
}
},
"node_modules/unbox-primitive": {
diff --git a/package.json b/package.json
index 49b3308..3cb8e36 100644
--- a/package.json
+++ b/package.json
@@ -6,9 +6,19 @@
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
+ "@types/jest": "^29.5.5",
+ "@types/node": "^20.7.1",
+ "@types/react": "^18.2.23",
+ "@types/react-dom": "^18.2.8",
+ "@types/react-router-dom": "^5.3.3",
+ "@types/styled-components": "^5.1.28",
"react": "^18.2.0",
"react-dom": "^18.2.0",
+ "react-router-dom": "^6.16.0",
"react-scripts": "5.0.1",
+ "styled-components": "^6.0.8",
+ "styled-reset": "^4.5.1",
+ "typescript": "^5.2.2",
"web-vitals": "^2.1.4"
},
"scripts": {
diff --git a/public/index.html b/public/index.html
index aa069f2..9aaf4d4 100644
--- a/public/index.html
+++ b/public/index.html
@@ -24,7 +24,7 @@
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
-
React App
+ CEOS Messenger service
diff --git a/src/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 (
-
- );
-}
-
-export default App;
diff --git a/src/App.test.js b/src/App.test.tsx
similarity index 100%
rename from src/App.test.js
rename to src/App.test.tsx
diff --git a/src/App.tsx b/src/App.tsx
new file mode 100644
index 0000000..06e1004
--- /dev/null
+++ b/src/App.tsx
@@ -0,0 +1,29 @@
+import { BrowserRouter, Routes, Route } from "react-router-dom";
+import { useNavigate } from "react-router-dom";
+import Chat from "./pages/chat/chat";
+import ChatList from "./pages/chatlist/chatList";
+import Profile from "./pages/profile/profile";
+import Friends from "./pages/friends/friends";
+import StatusBar from "./components/StatusBar/statusbar";
+
+import chatData from "./assets/datas/chatdata.json";
+import userData from "./assets/datas/userdata.json";
+
+function App() {
+ return (
+
+
+ {/* */}
+
+ } />
+ } />
+ } />
+ } />
+ } />
+
+
+
+ );
+}
+
+export default App;
diff --git a/src/assets/datas/chatdata.json b/src/assets/datas/chatdata.json
new file mode 100644
index 0000000..be26c84
--- /dev/null
+++ b/src/assets/datas/chatdata.json
@@ -0,0 +1,118 @@
+{"2":[
+ {
+ "id": 0,
+ "sender": "신동현",
+ "content": "ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ",
+ "showIcon": false,
+ "timestamp": "2023-11-01T04:29:17.011Z"
+
+
+ },
+ {
+ "id": 1,
+ "sender": "신동현",
+ "content": "아니 나 졸작",
+ "showIcon": false,
+ "timestamp": "2023-11-01T04:29:17.011Z"
+ },
+ {
+ "id": 2,
+ "sender": "신동현",
+ "content": "작업하는데 천이 부족해서",
+ "showIcon": false,
+ "timestamp": "2023-11-01T04:29:17.011Z"
+ },
+ {
+ "id": 3,
+ "sender": "신동현",
+ "content": "동대문 들렀다가 집 감",
+ "showIcon": true,
+ "timestamp": "2023-11-01T04:29:17.011Z"
+ },
+ {
+ "id": 4,
+ "sender": "이예진",
+ "content": "그것 참 불쌍하게 되었군",
+ "showIcon": false,
+ "timestamp": "2023-11-01T04:29:17.011Z"
+ },
+ {
+ "id": 5,
+ "sender": "이예진",
+ "content": "동대문에서 집까지",
+ "showIcon": false,
+ "timestamp": "2023-11-01T04:29:17.011Z"
+ },
+ {
+ "id": 6,
+ "sender": "이예진",
+ "content": "한 시간 넘게 걸리지 않아?",
+ "showIcon": true,
+ "timestamp": "2023-11-01T04:29:17.011Z"
+ },
+ {
+ "id": 7,
+ "sender": "신동현",
+ "content": "한 시간 반? 정도",
+ "showIcon": false,
+ "timestamp": "2023-11-01T04:29:17.011Z"
+ },
+ {
+ "id": 8,
+ "sender": "신동현",
+ "content": "그렇게 멀지는 않은데",
+ "showIcon": false,
+ "timestamp": "2023-11-01T04:29:17.011Z"
+ },
+ {
+ "id": 9,
+ "sender": "신동현",
+ "content": "짐이 많아서 힘든 겨",
+ "showIcon": true,
+ "timestamp": "2023-11-01T04:29:17.011Z"
+ },
+ {
+ "id": 10,
+ "sender": "이예진",
+ "content": "이따가 갈 때",
+ "showIcon": false,
+ "timestamp": "2023-11-01T04:29:17.011Z"
+ },
+ {
+ "id": 11,
+ "sender": "이예진",
+ "content": "심심하면 영통하자",
+ "showIcon": true,
+ "timestamp": "2023-11-01T04:29:17.011Z"
+ },
+ {
+ "id": 12,
+ "sender": "신동현",
+ "content": "오키 알겠음\n그럼 이따가 연락할게",
+ "showIcon": false,
+ "timestamp": "2023-11-01T04:29:17.011Z"
+ }],
+"5":[
+ {
+ "id": 0,
+ "sender": "신동현",
+ "content": "안녕하세여",
+ "showIcon": false,
+ "timestamp": "2023-11-03T07:44:53.152Z"
+ },
+ {
+ "id": 1,
+ "sender": "신동현",
+ "content": "ㅎㅎㅎ",
+ "showIcon": false,
+ "timestamp": "2023-11-03T07:44:55.011Z"
+ },
+ {
+ "id": 2,
+ "sender": "고세희 선배님",
+ "content": "ㅎㅇㅎㅇ",
+ "showIcon": false,
+ "timestamp": "2023-11-03T07:45:45.536Z"
+ }
+]
+}
\ No newline at end of file
diff --git a/src/assets/datas/userdata.json b/src/assets/datas/userdata.json
new file mode 100644
index 0000000..6b86792
--- /dev/null
+++ b/src/assets/datas/userdata.json
@@ -0,0 +1,74 @@
+{
+ "users": [
+ {
+ "id": 1,
+ "name": "신동현",
+ "instagram":"@s_d0nghyun",
+ "status":""
+
+ },
+ {
+ "id": 2,
+ "name": "이예진",
+ "instagram":"@leebyvae",
+ "status":""
+
+ },
+ {
+ "id": 3,
+ "name": "강은정",
+ "instagram":"@0o0go",
+ "status":"👍"
+
+ },{
+ "id": 4,
+ "name": "강은비",
+ "instagram":"@siverrainy_",
+ "status":"날씨 완전 가을🍁"
+
+ },{
+ "id": 5,
+ "name": "고세희 선배님",
+ "instagram":"@saysehi",
+ "status":""
+
+ },{
+ "id": 6,
+ "name": "곽수연",
+ "instagram":"@jusdjkl",
+ "status":"우하하하"
+
+ },{
+ "id": 7,
+ "name": "권은수",
+ "instagram":"@jusdjkl",
+ "status":""
+
+ },{
+ "id": 8,
+ "name": "김진솔",
+ "instagram":"@jusdjkl",
+ "status":"가을가을가을"
+
+ },{
+ "id": 9,
+ "name": "김가영",
+ "instagram":"@odeegayo",
+ "status":""
+
+ },{
+ "id": 10,
+ "name": "김하은 오빠",
+ "instagram":"@ggksenvl",
+ "status":""
+
+ },{
+ "id":11,
+ "name": "문지영 언니",
+ "instagram":"@m00nzi0",
+ "status":"공부?"
+
+ }
+ ]
+ }
+
diff --git a/src/assets/images/App-Store.svg b/src/assets/images/App-Store.svg
new file mode 100644
index 0000000..c0855ad
--- /dev/null
+++ b/src/assets/images/App-Store.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/images/Arrow.svg b/src/assets/images/Arrow.svg
new file mode 100644
index 0000000..41cece8
--- /dev/null
+++ b/src/assets/images/Arrow.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/images/BackIcon.svg b/src/assets/images/BackIcon.svg
new file mode 100644
index 0000000..01ab4ed
--- /dev/null
+++ b/src/assets/images/BackIcon.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/images/Battery.svg b/src/assets/images/Battery.svg
new file mode 100644
index 0000000..4a45d70
--- /dev/null
+++ b/src/assets/images/Battery.svg
@@ -0,0 +1,5 @@
+
diff --git a/src/assets/images/BigIcon.svg b/src/assets/images/BigIcon.svg
new file mode 100644
index 0000000..06ac6c6
--- /dev/null
+++ b/src/assets/images/BigIcon.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/images/BigIconLogo.svg b/src/assets/images/BigIconLogo.svg
new file mode 100644
index 0000000..668378c
--- /dev/null
+++ b/src/assets/images/BigIconLogo.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/assets/images/Camera.svg b/src/assets/images/Camera.svg
new file mode 100644
index 0000000..916dbf3
--- /dev/null
+++ b/src/assets/images/Camera.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/images/Cellular Connection.svg b/src/assets/images/Cellular Connection.svg
new file mode 100644
index 0000000..ebe9186
--- /dev/null
+++ b/src/assets/images/Cellular Connection.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/images/Dictation.svg b/src/assets/images/Dictation.svg
new file mode 100644
index 0000000..7e30da1
--- /dev/null
+++ b/src/assets/images/Dictation.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/images/Edit.svg b/src/assets/images/Edit.svg
new file mode 100644
index 0000000..ed9701c
--- /dev/null
+++ b/src/assets/images/Edit.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/images/Facetime.svg b/src/assets/images/Facetime.svg
new file mode 100644
index 0000000..3a6feb9
--- /dev/null
+++ b/src/assets/images/Facetime.svg
@@ -0,0 +1,6 @@
+
diff --git a/src/assets/images/Friends.svg b/src/assets/images/Friends.svg
new file mode 100644
index 0000000..dc73793
--- /dev/null
+++ b/src/assets/images/Friends.svg
@@ -0,0 +1,7 @@
+
diff --git a/src/assets/images/Group.svg b/src/assets/images/Group.svg
new file mode 100644
index 0000000..f04ec64
--- /dev/null
+++ b/src/assets/images/Group.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/images/LeftArrow.svg b/src/assets/images/LeftArrow.svg
new file mode 100644
index 0000000..0c1506b
--- /dev/null
+++ b/src/assets/images/LeftArrow.svg
@@ -0,0 +1,5 @@
+
diff --git a/src/assets/images/LightBottomBar.svg b/src/assets/images/LightBottomBar.svg
new file mode 100644
index 0000000..2553c20
--- /dev/null
+++ b/src/assets/images/LightBottomBar.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/images/LightStatusBar.svg b/src/assets/images/LightStatusBar.svg
new file mode 100644
index 0000000..24e6034
--- /dev/null
+++ b/src/assets/images/LightStatusBar.svg
@@ -0,0 +1,8 @@
+
diff --git a/src/assets/images/Message Numbers.svg b/src/assets/images/Message Numbers.svg
new file mode 100644
index 0000000..6fe3f42
--- /dev/null
+++ b/src/assets/images/Message Numbers.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/assets/images/SearchIcon.svg b/src/assets/images/SearchIcon.svg
new file mode 100644
index 0000000..a8b2328
--- /dev/null
+++ b/src/assets/images/SearchIcon.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/images/Searchbars.svg b/src/assets/images/Searchbars.svg
new file mode 100644
index 0000000..e1f0272
--- /dev/null
+++ b/src/assets/images/Searchbars.svg
@@ -0,0 +1,7 @@
+
diff --git a/src/assets/images/Time Style.svg b/src/assets/images/Time Style.svg
new file mode 100644
index 0000000..4f4cd9d
--- /dev/null
+++ b/src/assets/images/Time Style.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/images/Time.svg b/src/assets/images/Time.svg
new file mode 100644
index 0000000..4c781ef
--- /dev/null
+++ b/src/assets/images/Time.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/images/User.svg b/src/assets/images/User.svg
new file mode 100644
index 0000000..f04ec64
--- /dev/null
+++ b/src/assets/images/User.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/images/Vector (1).svg b/src/assets/images/Vector (1).svg
new file mode 100644
index 0000000..d41faaf
--- /dev/null
+++ b/src/assets/images/Vector (1).svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/images/Vector.svg b/src/assets/images/Vector.svg
new file mode 100644
index 0000000..225d492
--- /dev/null
+++ b/src/assets/images/Vector.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/images/Wifi.svg b/src/assets/images/Wifi.svg
new file mode 100644
index 0000000..c00f1c7
--- /dev/null
+++ b/src/assets/images/Wifi.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/images/back.svg b/src/assets/images/back.svg
new file mode 100644
index 0000000..cd3366b
--- /dev/null
+++ b/src/assets/images/back.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/images/backArrow.svg b/src/assets/images/backArrow.svg
new file mode 100644
index 0000000..6ec2b83
--- /dev/null
+++ b/src/assets/images/backArrow.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/images/bubbleStatusImg.svg b/src/assets/images/bubbleStatusImg.svg
new file mode 100644
index 0000000..c0575ca
--- /dev/null
+++ b/src/assets/images/bubbleStatusImg.svg
@@ -0,0 +1,18 @@
+
diff --git a/src/assets/images/chatBackArrow.svg b/src/assets/images/chatBackArrow.svg
new file mode 100644
index 0000000..40a5259
--- /dev/null
+++ b/src/assets/images/chatBackArrow.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/images/github.svg b/src/assets/images/github.svg
new file mode 100644
index 0000000..dd1591b
--- /dev/null
+++ b/src/assets/images/github.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/images/instagram.svg b/src/assets/images/instagram.svg
new file mode 100644
index 0000000..4a05ea4
--- /dev/null
+++ b/src/assets/images/instagram.svg
@@ -0,0 +1,5 @@
+
diff --git a/src/assets/images/record.svg b/src/assets/images/record.svg
new file mode 100644
index 0000000..ea6718a
--- /dev/null
+++ b/src/assets/images/record.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/images/rightarrowIcon.svg b/src/assets/images/rightarrowIcon.svg
new file mode 100644
index 0000000..41cece8
--- /dev/null
+++ b/src/assets/images/rightarrowIcon.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/images/sendMessage.svg b/src/assets/images/sendMessage.svg
new file mode 100644
index 0000000..aff31f1
--- /dev/null
+++ b/src/assets/images/sendMessage.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/assets/images/video.svg b/src/assets/images/video.svg
new file mode 100644
index 0000000..0071623
--- /dev/null
+++ b/src/assets/images/video.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/images/write.svg b/src/assets/images/write.svg
new file mode 100644
index 0000000..2f6a79a
--- /dev/null
+++ b/src/assets/images/write.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/components/ChatInput/chatinput.tsx b/src/components/ChatInput/chatinput.tsx
new file mode 100644
index 0000000..d31b715
--- /dev/null
+++ b/src/components/ChatInput/chatinput.tsx
@@ -0,0 +1,153 @@
+import React, { useState } from "react";
+import styled from "styled-components";
+//images
+import cameraIcon from "../../assets/images/Camera.svg";
+import appStoreIcon from "../../assets/images/App-Store.svg";
+import dictationIcon from "../../assets/images/Dictation.svg";
+import sendIcon from "../../assets/images/sendMessage.svg";
+//채팅 입력받는 component
+
+interface ChatInputProps {
+ onSend: (message: string) => void;
+}
+
+const ChatInput: React.FC = ({ onSend }) => {
+ const [inputValue, setInputValue] = useState("");
+ const [isInputFocused, setIsInputFocused] = useState(false);
+
+ //여러줄 입력 위해 input=> textarea로 변경
+ const handleInputEnter = (e: React.KeyboardEvent) => {
+ if (e.key === "Enter" && !e.shiftKey) {
+ e.preventDefault();
+ if (inputValue.trim() !== "") {
+ // 입력값이 공백인 경우 예외 처리
+ onSend(inputValue);
+ }
+ // 입력값 초기화
+ setIsInputFocused(false);
+ setInputValue("");
+ }
+ if (e.key === "Enter" && e.shiftKey) {
+ e.preventDefault();
+ setInputValue((prev) => prev + "\n");
+ }
+ };
+
+ const handleSendClick = () => {
+ console.log("Send button clicked"); // Add this line for debugging
+ if (inputValue.trim() !== "") {
+ onSend(inputValue);
+ }
+ setInputValue("");
+ };
+
+ const handleInputChange = (e: React.ChangeEvent) => {
+ setInputValue(e.target.value);
+ setIsInputFocused(e.target.value !== ""); // isInputFocused true로 설정
+ };
+
+ return (
+
+ {!isInputFocused && }
+ {!isInputFocused && }
+
+
+ setIsInputFocused(true)}
+ onBlur={() => setIsInputFocused(false)}
+ required
+ />
+ {isInputFocused ? (
+ //handleSendClick 작동?? click 작동
+
+ ) : (
+
+ )}
+
+
+ );
+};
+
+const BottomBarContainer = styled.div`
+ display: flex;
+ align-items: center;
+ gap: 1.38rem;
+ position: relative;
+ width: 100%;
+ height: 2.5rem;
+ margin-left: 1.38rem;
+ /* margin-bottom: 0.81rem; */
+`;
+
+const Camera = styled.img`
+ width: 1.92713rem;
+ height: 1.51313rem;
+ /* margin-left: 1.06rem; */
+`;
+
+const AppStore = styled.img`
+ width: 2.18363rem;
+ height: 1.58331rem;
+`;
+
+const Input = styled.textarea`
+ width: 100%;
+ font-family: "SF Pro Text";
+ font-size: 0.9375rem;
+ margin-right: 1.03rem;
+ height: 2.125rem;
+ border: 1px solid var(--gray-3);
+ outline: none;
+ padding-left: 0.84rem;
+ border-radius: 1.875rem;
+ resize: none;
+ word-break: break-all;
+ overflow-y: auto; //스크롤바
+ padding-top: 0.5rem;
+ &:focus {
+ box-shadow: none;
+ }
+ &::placeholder {
+ display: flex;
+ align-items: center;
+ }
+
+ padding-right: 2rem; //input 길어질때 방지 하기 위해
+`;
+
+const InputContainer = styled.span`
+ display: flex;
+ align-items: center;
+ flex-grow: 1;
+ width: 90%;
+`;
+
+const Dictation = styled.img`
+ width: 1.6875rem;
+ height: 1.6875rem;
+ position: absolute; //input fiield 안에 위치
+ right: 1.25rem;
+`;
+
+const Send = styled.button`
+ width: 1.6875rem;
+ height: 1.6875rem;
+ background: url(${sendIcon}) no-repeat center center;
+ border: none;
+ position: absolute; //input fiield 안에 위치
+ z-index: 10;
+ right: 1.25rem;
+ cursor: pointer;
+
+ &:focus {
+ outline: none;
+ }
+`;
+
+export default ChatInput;
diff --git a/src/components/ChatTitle/chatTitle.tsx b/src/components/ChatTitle/chatTitle.tsx
new file mode 100644
index 0000000..848ca1d
--- /dev/null
+++ b/src/components/ChatTitle/chatTitle.tsx
@@ -0,0 +1,84 @@
+import styled from "styled-components";
+import arrowIcon from "../../assets/images/chatBackArrow.svg";
+import userIcon from "../../assets/images/User.svg";
+import facetime from "../../assets/images/Facetime.svg";
+import { useNavigate } from "react-router-dom";
+
+interface ChatHeaderProps {
+ chatName: string;
+ chatID: string;
+ changeUser?: () => void;
+}
+const ChatTitle: React.FC = ({
+ chatName,
+ chatID,
+ changeUser,
+}) => {
+ const navigate = useNavigate();
+ return (
+
+ navigate("/chatlist")}
+ />
+
+
+ {chatName}
+ {chatID}
+
+
+
+ );
+};
+
+const ChatTitleContainer = styled.div`
+ display: flex;
+ align-items: center;
+ position: relative;
+ width: 100%;
+ height: 2.5rem;
+ padding-bottom: 0.7rem;
+ border-bottom: solid rgba(144, 144, 147, 0.5);
+`;
+const UserSection = styled.div`
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ &:hover {
+ cursor: pointer;
+ }
+`;
+const UserName = styled.div`
+ color: var(--black);
+ font-size: 1.125rem;
+ font-style: normal;
+ font-weight: 600;
+ line-height: normal;
+ word-wrap: break-word;
+`;
+const InstagramID = styled.div`
+ color: var(--gray-1);
+ font-size: 0.75rem;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 125%; /* 0.9375rem */
+`;
+const ArrowIcon = styled.img`
+ width: 2.5rem;
+ height: 2.5rem;
+`;
+
+const UserIcon = styled.img`
+ width: 2.06244rem;
+ height: 2.06238rem;
+ margin-right: 0.64rem;
+ &:hover {
+ cursor: pointer;
+ }
+`;
+const Facetime = styled.img`
+ margin-right: 1.12rem;
+`;
+
+export default ChatTitle;
diff --git a/src/components/ChatlistItem/chatlistitem.tsx b/src/components/ChatlistItem/chatlistitem.tsx
new file mode 100644
index 0000000..7fdf814
--- /dev/null
+++ b/src/components/ChatlistItem/chatlistitem.tsx
@@ -0,0 +1,149 @@
+import styled from "styled-components";
+//images
+import groupIcon from "../../assets/images/Group.svg";
+import rightarrowIcon from "../../assets/images/rightarrowIcon.svg";
+interface Message {
+ id: number;
+ sender: string;
+ content: string;
+ showIcon: boolean;
+ timestamp: string; //메시지 보내는 시간 정보 추가
+ unread?: boolean; // 읽지 않은 메시지인지 여부를 나타내는 속성 추가
+}
+interface User {
+ id: number;
+ name: string;
+}
+
+interface ChatListItemProps {
+ key: number;
+ user: User;
+ lastMessage: Message | null;
+ onClick: () => void;
+}
+function formatTimestamp(isoString?: string) {
+ if (!isoString) return "No last message";
+
+ const date = new Date(isoString);
+ date.setHours(0, 0, 0, 0);
+
+ const now = new Date();
+ now.setHours(0, 0, 0, 0);
+
+ const diffTime = now.getTime() - date.getTime();
+ const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
+
+ // 날짜 차이에 따른 문자열 반환
+ //오늘은 시간 정보, 전날은 "어제", 그 전날은 '~일전'으로 반환해줌
+ if (diffDays === 0) {
+ const date = new Date(isoString);
+ let hours = date.getHours();
+ const minutes = date.getMinutes();
+ const ampm = hours >= 12 ? "PM" : "AM";
+ hours = hours % 12;
+ hours = hours ? hours : 12;
+ const strHours = hours < 10 ? `0${hours}` : `${hours}`;
+ const strMinutes = minutes < 10 ? `0${minutes}` : `${minutes}`;
+ return `${strHours}:${strMinutes} ${ampm}`;
+ } else if (diffDays === 1) {
+ return "어제";
+ } else {
+ return `${diffDays}일 전`;
+ }
+}
+
+const ChatListItem: React.FC = ({
+ user,
+ lastMessage,
+ onClick,
+}) => {
+ return (
+
+ {lastMessage?.unread && }
+
+
+
+ {user.name}
+
+
+
+
+
+ {lastMessage?.content || ""}
+
+
+ );
+};
+
+const ChatlistItem = styled.div`
+ width: 100%;
+ height: 5.25rem;
+ display: flex;
+ padding: 0.81rem 1.56rem;
+ position: relative;
+
+ &::after {
+ content: "";
+ position: absolute;
+ left: 5.1rem;
+ right: 0;
+ bottom: 0;
+ border-bottom: 1px solid var(--gray-1);
+ }
+`;
+
+const Time = styled.div`
+ font-family: "SF Pro Text";
+ font-size: 0.9375rem;
+ font-style: normal;
+ font-weight: 400;
+ line-height: normal;
+ color: var(--gray-1);
+`;
+const ChatlistData = styled.div`
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ padding-left: 0.87rem;
+`;
+const ChatlistDataTitle = styled.div`
+ width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 0.25rem;
+`;
+
+const TimeContainer = styled.div`
+ display: flex;
+`;
+const UnreadIndicator = styled.div`
+ position: absolute;
+ top: 50%;
+ left: 0.8rem;
+ width: 0.56rem;
+ height: 0.56rem;
+ border-radius: 50%;
+ background-color: var(--blue);
+`;
+const GroupIcon = styled.img`
+ margin-left: 0.2rem;
+`;
+const Name = styled.span`
+ font-family: "SF Pro Text";
+ font-size: 1.125rem;
+ font-style: normal;
+ font-weight: 600;
+ line-height: normal;
+`;
+const LastMessage = styled.div`
+ /* width: 17.25rem; */
+ height: 2.25rem;
+ color: var(--gray-1);
+ font-family: "SF Pro Text";
+ font-size: 0.9375rem;
+ font-style: normal;
+ font-weight: 400;
+ line-height: normal;
+`;
+export default ChatListItem;
diff --git a/src/components/Message/message.tsx b/src/components/Message/message.tsx
new file mode 100644
index 0000000..d9f189c
--- /dev/null
+++ b/src/components/Message/message.tsx
@@ -0,0 +1,75 @@
+import React from "react";
+import styled from "styled-components";
+import userIcon from "../../assets/images/User.svg";
+
+interface MessageProps {
+ sender: string;
+ content: string;
+ nowUser: string;
+ showIcon: boolean;
+}
+
+const Message: React.FC = ({
+ sender,
+ content,
+ nowUser,
+ showIcon,
+}) => {
+ return (
+
+ {showIcon && }
+
+ {content}
+
+
+ );
+};
+
+const MessageContainer = styled.div<{ sender: string; nowUser: string }>`
+ display: flex;
+ align-items: center;
+ margin: 0.44rem 0;
+ max-width: 100%;
+ flex-direction: ${(props) =>
+ props.sender === props.nowUser ? "row-reverse" : "row"};
+ //왼, 오른쪽 기준 나누기
+`;
+
+const MessageItem = styled.div<{
+ sender: string;
+ content: string;
+ nowUser: string;
+ showIcon: boolean;
+}>`
+ /* width: 100%; */
+ padding: 0.7rem;
+ border-radius: 1.25rem;
+ margin-right: 0.69rem;
+ background-color: ${({ sender, nowUser }) =>
+ sender === nowUser ? "var(--blue)" : "rgba(118, 118, 128, 0.12)"};
+ color: ${({ sender, nowUser }) =>
+ sender === nowUser ? "#FFFFFF" : "#000000"};
+ // showIcon가 false인 경우 margin-left를 한방에 설정
+ margin-left: ${(msg) =>
+ !msg.showIcon && msg.sender !== msg.nowUser ? "3.75rem" : "0"};
+ max-width: 90%; // 데이터가 길어질 경우
+ font-size: 0.9375rem;
+ font-weight: 400;
+ line-height: normal;
+ box-sizing: border-box;
+ word-break: break-all;
+ white-space: pre-line; //띄어쓰기도 인식할 수 있게
+`;
+
+const UserIconShow = styled.img`
+ width: 2.75rem;
+ height: 2.06238rem;
+ margin-left: 1rem;
+`;
+
+export default Message;
diff --git a/src/components/Message/messageList.tsx b/src/components/Message/messageList.tsx
new file mode 100644
index 0000000..778639d
--- /dev/null
+++ b/src/components/Message/messageList.tsx
@@ -0,0 +1,34 @@
+import React, { useRef, useEffect } from "react";
+import styled from "styled-components";
+
+interface MessageListProps {
+ children: React.ReactNode;
+}
+
+//메시지 리스트 스크롤 관리
+
+const MessageList: React.FC = ({ children }) => {
+ const scrollRef = useRef(null);
+
+ useEffect(() => {
+ // 현재 스크롤 위치 :scrollRef.current.scrollTop
+ // 스크롤 길이 : scrollRef.current.scrollHeight
+ if (scrollRef.current) {
+ scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
+ }
+ }, [children]);
+
+ return (
+ {children}
+ );
+};
+
+const MessageListContainer = styled.div`
+ overflow-y: auto;
+ width: 100%;
+ max-height: 100%;
+ flex: 1;
+ scrollbar-width: none;
+`;
+
+export default MessageList;
diff --git a/src/components/SearchBar/serachbar.tsx b/src/components/SearchBar/serachbar.tsx
new file mode 100644
index 0000000..1f75925
--- /dev/null
+++ b/src/components/SearchBar/serachbar.tsx
@@ -0,0 +1,68 @@
+// searchIcon & recording
+
+import styled from "styled-components";
+import { ChangeEvent } from "react";
+import searchIcon from "../../assets/images/SearchIcon.svg";
+import recordIcon from "../../assets/images/record.svg";
+
+interface SearchBarProps {
+ value: string;
+ onChange: (event: ChangeEvent) => void;
+}
+const SearchBar: React.FC = ({ value, onChange }) => {
+ return (
+
+
+
+
+
+ );
+};
+
+const InputContainer = styled.span`
+ display: flex;
+ align-items: center;
+ width: 100%;
+ /* height: 2.25rem; */
+ padding: 0 1rem;
+ font-family: "SF Pro Text";
+`;
+const Input = styled.input`
+ width: 100%;
+ font-family: "SF Pro Text";
+ height: 2.25rem;
+ padding-left: 2.87rem;
+ background: var(--gray-2);
+ &:focus {
+ box-shadow: none;
+ }
+ outline: none;
+ border: none;
+ border-radius: 0.5rem;
+`;
+
+const Search = styled.img`
+ position: absolute;
+ font-size: 1.0625rem;
+ font-style: normal;
+ font-weight: 400;
+ line-height: normal;
+ letter-spacing: -0.017rem;
+ left: 1.5rem;
+`;
+
+const Record = styled.img`
+ position: absolute;
+ font-size: 1.0625rem;
+ font-style: normal;
+ font-weight: 400;
+ line-height: normal;
+ letter-spacing: -0.017rem;
+ right: 1.63rem;
+`;
+export default SearchBar;
diff --git a/src/components/StatusBar/statusbar.tsx b/src/components/StatusBar/statusbar.tsx
new file mode 100644
index 0000000..15d2296
--- /dev/null
+++ b/src/components/StatusBar/statusbar.tsx
@@ -0,0 +1,58 @@
+import styled from "styled-components";
+import timeIcon from "../../assets/images/Time Style.svg";
+import cellularIcon from "../../assets/images/Cellular Connection.svg";
+import wifiIcon from "../../assets/images/Wifi.svg";
+import batteryIcon from "../../assets/images/Battery.svg";
+
+const StatusBar = () => {
+ return (
+
+
+
+
+
+
+
+
+ );
+};
+
+const StatusBarContainer = styled.div`
+ width: 100%;
+ height: 2.75rem;
+ position: relative;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+`;
+const TimeIcon = styled.img`
+ width: 3.375rem;
+ height: 2.3125rem;
+ padding-bottom: 0.6rem;
+ margin-left: 0.4rem;
+`;
+
+const CellularIcon = styled.img`
+ width: 1.0625rem;
+ height: 0.66669rem;
+`;
+const WifiIcon = styled.img`
+ width: 0.95831rem;
+ height: 0.6875rem;
+`;
+const BatteryIcon = styled.img`
+ width: 1.5205rem;
+ height: 0.70831rem;
+ margin-right: 1.12rem;
+`;
+
+const StatusBarRight = styled.div`
+ display: inline-flex;
+ align-items: center;
+ gap: 0.31rem;
+ left: 18.37rem;
+ right: 0.9rem;
+ /* top: 1.08rem;
+ bottom: 0.96rem; */
+`;
+export default StatusBar;
diff --git a/src/components/TopBar/topbar.tsx b/src/components/TopBar/topbar.tsx
new file mode 100644
index 0000000..4f3a437
--- /dev/null
+++ b/src/components/TopBar/topbar.tsx
@@ -0,0 +1,48 @@
+import styled from "styled-components";
+import StatusBar from "../StatusBar/statusbar";
+import ChatTitle from "../ChatTitle/chatTitle";
+import backArrow from "../../assets/images/backArrow.svg";
+import backText from "../../assets/images/BackIcon.svg";
+import editIcon from "../../assets/images/Edit.svg";
+import { useNavigate } from "react-router-dom";
+const TopBar = () => {
+ const navigate = useNavigate();
+ const handleBackClick = () => {
+ navigate("/profile");
+ };
+ return (
+
+
+
+
+
+
+
+ );
+};
+const TopBarContainer = styled.div`
+ width: 100%;
+ height: 1.25rem;
+ padding: 0rem 1.12rem;
+ margin-top: 0.81rem;
+ margin-bottom: 1rem;
+ position: relative;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+`;
+const BackArrow = styled.img`
+ width: 0.56088rem;
+ height: 0.9745rem;
+ margin-right: 0.31rem;
+`;
+const BackText = styled.img`
+ width: 2.1875rem;
+ height: 1.125rem;
+`;
+const EditIcon = styled.img`
+ width: 1.3125rem;
+ height: 1.25rem;
+`;
+
+export default TopBar;
diff --git a/src/custom.d.tsx b/src/custom.d.tsx
new file mode 100644
index 0000000..b528b80
--- /dev/null
+++ b/src/custom.d.tsx
@@ -0,0 +1,6 @@
+// 이미지 import 위한 확장자 설정
+declare module "*.jpg";
+declare module "*.png";
+declare module "*.jpeg";
+declare module "*.gif";
+declare module "*.svg";
diff --git a/src/index.css b/src/index.css
index ec2585e..206ffdc 100644
--- a/src/index.css
+++ b/src/index.css
@@ -1,10 +1,12 @@
+@import url('https://fonts.cdnfonts.com/css/sf-pro-display');
body {
margin: 0;
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
+ font-family: "SF Pro Text",-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
+ font-size: 1.125rem;
}
code {
diff --git a/src/index.js b/src/index.js
deleted file mode 100644
index d563c0f..0000000
--- a/src/index.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import React from 'react';
-import ReactDOM from 'react-dom/client';
-import './index.css';
-import App from './App';
-import reportWebVitals from './reportWebVitals';
-
-const root = ReactDOM.createRoot(document.getElementById('root'));
-root.render(
-
-
-
-);
-
-// If you want to start measuring performance in your app, pass a function
-// to log results (for example: reportWebVitals(console.log))
-// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
-reportWebVitals();
diff --git a/src/index.tsx b/src/index.tsx
new file mode 100644
index 0000000..e302672
--- /dev/null
+++ b/src/index.tsx
@@ -0,0 +1,22 @@
+import React from "react";
+import ReactDOM from "react-dom/client";
+import "./index.css";
+import App from "./App";
+import reportWebVitals from "./reportWebVitals";
+import GlobalStyle from "./styles/globalStyle";
+
+const root = ReactDOM.createRoot(
+ document.getElementById("root") as HTMLElement
+);
+
+root.render(
+
+
+
+
+);
+
+// If you want to start measuring performance in your app, pass a function
+// to log results (for example: reportWebVitals(console.log))
+// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
+reportWebVitals();
diff --git a/src/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/chat/chat.tsx b/src/pages/chat/chat.tsx
new file mode 100644
index 0000000..7b7d6dd
--- /dev/null
+++ b/src/pages/chat/chat.tsx
@@ -0,0 +1,197 @@
+import React, { useEffect, useRef, useState } from "react";
+import styled from "styled-components";
+import ChatInput from "../../components/ChatInput/chatinput";
+import userData from "../../assets/datas/userdata.json";
+import chatData from "../../assets/datas/chatdata.json";
+import ChatTitle from "../../components/ChatTitle/chatTitle";
+import MessageList from "../../components/Message/messageList";
+import Message from "../../components/Message/message";
+import "../../styles/colors.css";
+import bottomBar from "../../assets/images/LightBottomBar.svg";
+import StatusBar from "../../components/StatusBar/statusbar";
+import { useParams } from "react-router-dom";
+interface Message {
+ id: number;
+ sender: string;
+ content: string;
+ showIcon: boolean;
+ timestamp: Date; // message list 페이지에 시간 추가 필요헤서 필드 추가
+}
+
+interface ChatData {
+ [key: string]: Message[];
+}
+
+function Chat() {
+ // URL에서 채팅 ID를 추출, 예: /chat/1
+ const { chatId } = useParams<{ chatId: string }>();
+ const [messages, setMessages] = useState([]);
+ const [messageId, setMessageId] = useState(0);
+ const [nowUser, setNowUser] = useState(userData.users[0]);
+
+ useEffect(() => {
+ if (typeof chatId !== "undefined") {
+ const savedChats = JSON.parse(
+ localStorage.getItem("chatMessages") || JSON.stringify(chatData)
+ ) as ChatData;
+ setMessages(savedChats[chatId] || []);
+ setMessageId(savedChats[chatId]?.length || 0);
+ }
+ }, [chatId]);
+
+ // 사용자가 바뀔 때마다 실행
+ useEffect(() => {
+ setMessages((prevMessages) => {
+ const newMessages = [...prevMessages];
+ const lastMessageIndex = newMessages.length - 1;
+
+ if (lastMessageIndex >= 0) {
+ // 모든 메시지를 순회하며 showIcon을 설정
+ for (let i = 0; i < lastMessageIndex; i++) {
+ const currentSender = newMessages[i].sender;
+ const nextSender = newMessages[i + 1].sender;
+ newMessages[i].showIcon = currentSender !== nextSender;
+ }
+ // 마지막 메시지는 항상 showIcon을 true 로 설정
+ newMessages[lastMessageIndex].showIcon = true;
+ }
+
+ return newMessages;
+ });
+ }, [nowUser]);
+
+ if (!chatId) {
+ return 없는 친구 ID입니다.
;
+ }
+
+ const handleSendMessage = (content: string) => {
+ const newMessage: Message = {
+ id: messageId,
+ sender: nowUser.name,
+ content,
+ showIcon: false,
+ timestamp: new Date(), // 현재 시간 추가
+ };
+
+ // 메세지가 추가됐을 때 가장 마지막 index의 메세지와 비교하여 showIcon 업데이트
+ if (
+ messages.length > 0 &&
+ messages[messages.length - 1].sender !== nowUser.name
+ ) {
+ messages[messages.length - 1].showIcon = true;
+ }
+
+ const updatedMessages = [...messages, newMessage];
+ setMessages(updatedMessages);
+
+ // 다음 메시지를 위해 messageId 업데이트
+ setMessageId(messageId + 1);
+ // 로컬 스토리지에 채팅 데이터 저장
+ const savedChats = JSON.parse(
+ localStorage.getItem("chatMessages") || JSON.stringify(chatData)
+ ) as ChatData;
+
+ savedChats[chatId] = updatedMessages;
+ localStorage.setItem("chatMessages", JSON.stringify(savedChats));
+ };
+ const changeUser = () => {
+ setNowUser((prev) =>
+ prev.name === userData.users[0].name
+ ? userData.users[chatIdNumber - 1]
+ : userData.users[0]
+ );
+
+ setMessages((prevMessages) => {
+ const newMessages = [...prevMessages];
+ const lastMessageIndex = newMessages.length - 1;
+ //userChange 동시에 여러번 누르면 아이콘 동시에 뜨는것 방지하기 위해
+ if (lastMessageIndex >= 1) {
+ const lastMessageSender = newMessages[lastMessageIndex].sender;
+ const secondLastMessageSender =
+ newMessages[lastMessageIndex - 1].sender;
+
+ // 직전 메시지와 비교하여 showIcon을 설정합니다.
+ if (
+ secondLastMessageSender === lastMessageSender &&
+ newMessages[lastMessageIndex - 1].showIcon
+ ) {
+ newMessages[lastMessageIndex - 1].showIcon = false;
+ }
+ newMessages[lastMessageIndex].showIcon = true;
+ }
+
+ return newMessages;
+ });
+ };
+
+ //chatId를 integer 값으로 바꿔준 결과
+ const chatIdNumber = +chatId;
+ const partner =
+ nowUser.name === userData.users[0].name
+ ? userData.users[chatIdNumber - 1]
+ : userData.users[0];
+
+ const messageContainers = messages.map((message: Message, index) => {
+ const isCurrentUser = message.sender === nowUser.name;
+
+ return (
+
+ );
+ });
+
+ return (
+
+
+
+
+
+ {messageContainers}
+
+
+
+
+
+ );
+}
+
+const ChatContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ position: absolute;
+ border-radius: 1.5rem;
+ height: 100vh;
+ width: 100vw;
+ border: solid var(--gray-1);
+`;
+
+const MessageContainer = styled.div`
+ flex: 1;
+ overflow-y: auto;
+ width: 100%;
+ height: 100%;
+ /* padding: 2.75rem 0; //Topbar랑 안겹치게
+ margin-bottom: 2.5rem; //아래 부분이랑 안겹치게 */
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+`;
+
+const BottomBarIcon = styled.img`
+ width: 100%;
+ height: 2.125rem;
+ margin-bottom: 0;
+ position: relative;
+`;
+
+export default Chat;
diff --git a/src/pages/chatlist/chatList.tsx b/src/pages/chatlist/chatList.tsx
new file mode 100644
index 0000000..7035590
--- /dev/null
+++ b/src/pages/chatlist/chatList.tsx
@@ -0,0 +1,164 @@
+import styled from "styled-components";
+import { useState, useEffect } from "react";
+import { useNavigate } from "react-router-dom";
+//components
+import ChatListItem from "../../components/ChatlistItem/chatlistitem";
+import StatusBar from "../../components/StatusBar/statusbar";
+import TopBar from "../../components/TopBar/topbar";
+import SearchBar from "../../components/SearchBar/serachbar";
+//images
+import friendsIcon from "../../assets/images/Friends.svg";
+import userData from "../../assets/datas/userdata.json";
+import chatData from "../../assets/datas/chatdata.json";
+import bottomBar from "../../assets/images/LightBottomBar.svg";
+interface Message {
+ id: number;
+ sender: string;
+ content: string;
+ showIcon: boolean;
+ timestamp: string; //메시지 보내는 시간 정보 추가
+ unread?: boolean; // 읽지 않은 메시지인지 여부를 나타내는 속성 추가
+}
+interface User {
+ id: number;
+ name: string;
+}
+interface ChatData {
+ [key: string]: Message[];
+}
+
+export default function ChatList() {
+ const navigate = useNavigate();
+
+ const [lastMessages, setLastMessages] = useState<
+ Record
+ >({});
+
+ //마지막 메시지가 최근인 순서로 sorting
+ const [sortedUsers, setSortedUsers] = useState(userData.users);
+
+ useEffect(() => {
+ // localStorage에서 chatData 가져오기
+ const savedChats = JSON.parse(
+ localStorage.getItem("chatMessages") || JSON.stringify(chatData)
+ ) as ChatData;
+
+ // 모든 사용자의 마지막 메시지와 시간 가져오기
+ const lastMessage: Record = {};
+
+ //userMessage: Message기록이 있는 유저들 필터링 + 마지막 대화 시간 가져옴
+ const userMessage = userData.users
+ .filter((user) => savedChats[user.id.toString()]?.length > 0) // chatData가 있는 사용자만 가져옴
+ .map((user) => {
+ const userChat = savedChats[user.id.toString()];
+ const lastChat = userChat ? userChat[userChat.length - 1] : null;
+ lastMessage[user.id] = lastChat;
+ return {
+ ...user,
+ lastMessageTimestamp: lastChat
+ ? new Date(lastChat.timestamp) // 마지막 메시지가 있으면 해당 시간을 가져옴
+ : new Date(0), // 마지막 timestamp 없으면 기본 시간으로 예외처리
+ };
+ });
+
+ // 타임스탬프 기준으로 사용자 정렬: 가장 최근 메시지가 위에 오게 함
+ const sortedUsers = userMessage.sort(
+ (a, b) =>
+ b.lastMessageTimestamp.getTime() - a.lastMessageTimestamp.getTime()
+ );
+ setSortedUsers(sortedUsers);
+
+ const lastMessageUnread: Record = {};
+ // 모든 사용자의 마지막 메시지에 대한 unread 상태 업데이트 :
+ // 마지막 sender가 내(신동현)가 아닌 경우 unread 로 처리
+ userData.users.forEach((user) => {
+ const lastMsg = lastMessage[user.id];
+ if (lastMsg) {
+ lastMessageUnread[user.id] = {
+ ...lastMsg,
+ unread: lastMsg.sender !== "신동현",
+ };
+ }
+ });
+ //unread 업데이트 저장
+ setLastMessages(lastMessageUnread);
+ }, []);
+
+ //searchBar에서 검색하면, 필터링된 사용자만 뜨게 하는 부분
+ const [searchTerm, setSearchTerm] = useState("");
+ function getFilteredUsers(users: User[], searchTerm: string) {
+ if (!searchTerm) return users;
+ return users.filter((user) =>
+ user.name.toLowerCase().includes(searchTerm.toLowerCase())
+ );
+ }
+ const filteredUsers = getFilteredUsers(sortedUsers, searchTerm);
+
+ return (
+
+
+
+
+
+ Chat
+ navigate("/friends")} />
+
+ setSearchTerm(e.target.value)}
+ />
+
+ {filteredUsers.map((user) => (
+ navigate(`/chat/${user.id}`)}
+ />
+ ))}
+
+
+
+
+ );
+}
+
+const ChatRoomContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ position: absolute;
+ border-radius: 1.5rem;
+ height: 100vh;
+ width: 100vw;
+ border: solid rgba(144, 144, 147, 0.5);
+`;
+
+const Title = styled.span`
+ font-family: "SF Pro Text";
+ font-size: 2.125rem;
+ font-style: normal;
+ font-weight: 700;
+ line-height: 80%;
+ width: 100%;
+ padding-left: 1.37rem;
+ margin-bottom: 1.75rem;
+ display: flex;
+ justify-content: space-between;
+ padding-right: 1.12rem;
+`;
+const BottomBarIcon = styled.img`
+ width: 100%;
+ height: 2.125rem;
+ margin-bottom: 0;
+ /* position: relative; */
+ position: absolute;
+ bottom: 0;
+`;
+
+const ChatlistContainer = styled.div`
+ overflow-y: auto; // 세로 스크롤
+ width: 100%;
+ height: 100%;
+`;
diff --git a/src/pages/friends/friends.tsx b/src/pages/friends/friends.tsx
new file mode 100644
index 0000000..0433756
--- /dev/null
+++ b/src/pages/friends/friends.tsx
@@ -0,0 +1,192 @@
+import styled from "styled-components";
+import { useNavigate } from "react-router-dom";
+import { useState } from "react";
+import userData from "../../assets/datas/userdata.json";
+//component
+import SearchBar from "../../components/SearchBar/serachbar";
+//이미지들
+import bigIcon from "../../assets/images/BigIcon.svg";
+import bottomBar from "../../assets/images/LightBottomBar.svg";
+import StatusBar from "../../components/StatusBar/statusbar";
+import TopBar from "../../components/TopBar/topbar";
+import bubbleImg from "../../assets/images/bubbleStatusImg.svg";
+
+interface User {
+ id: number;
+ name: string;
+ instagram: string;
+ status: string;
+}
+//친구 목록에서 status가 존재하는지 여부
+interface FriendsListItemProps {
+ statusExist: boolean;
+}
+
+export default function Friends() {
+ const navigate = useNavigate();
+ const [searchTerm, setSearchTerm] = useState("");
+ const [friends, setFriends] = useState(userData.users);
+
+ function getFilteredUsers(users: User[], searchTerm: string) {
+ if (!searchTerm) return users;
+ const filteredUsers = users.filter((user) =>
+ user.name.toLowerCase().includes(searchTerm.toLowerCase())
+ );
+ return filteredUsers;
+ }
+ const filteredUsers = getFilteredUsers(friends, searchTerm);
+
+ return (
+
+
+
+
+ Friends
+ {
+ setSearchTerm(e.target.value);
+ }}
+ />
+
+ {filteredUsers.map((user, index) => (
+ navigate(`/chat/${user.id}`)}
+ statusExist={!!user.status} // user.status boolean 가져와서
+ >
+ {user.status && (
+ //상메가 설정된 경우 버블 안에 보여줌
+
+
+ {user.status}
+
+ )}
+
+
+ {user.name}
+ {user.instagram}
+
+ ))}
+
+
+
+
+ );
+}
+
+const ProfileContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ position: absolute;
+ border-radius: 1.5rem;
+ height: 100%;
+ width: 100vw;
+ border: solid var(--gray-1);
+`;
+
+const Title = styled.span`
+ font-family: "SF Pro Text";
+ font-size: 2.125rem;
+ font-style: normal;
+ font-weight: 700;
+ line-height: 80%;
+ width: 100%;
+ padding-left: 1.37rem;
+ margin-bottom: 1.75rem;
+ display: flex;
+ justify-content: space-between;
+ padding-right: 1.12rem;
+`;
+const BottomBarIcon = styled.img`
+ width: 100%;
+ height: 2.125rem;
+ margin-bottom: 0;
+ /* position: relative; */
+ position: absolute;
+ bottom: 0;
+`;
+
+const FriendsContainer = styled.div`
+ display: flex;
+ flex-flow: wrap; //여러줄에 아이템 표시
+ overflow-y: auto;
+ width: 100%;
+ padding: 0 1rem;
+ justify-content: flex-start;
+`;
+
+const FriendsListItem = styled.div`
+ position: relative; // 상메 올라가는 기준점
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+
+ padding-top: ${(props) =>
+ props.statusExist
+ ? "1.1rem"
+ : "2.1rem"}; // 상메 있는지 여부에 따라 margin-top을 변경
+
+ //default: 노트북 환경
+ width: 100%;
+ max-width: 10%; // item이 한줄에 10개씩 오도록
+
+ @media (max-width: 850px) {
+ // 화면 너비가 850px 이하인 경우 (ipad 환경)
+ width: 100%;
+ max-width: 20%; // item이 한줄에 5개씩 오도록
+ }
+
+ @media (max-width: 480px) {
+ // 화면 너비가 480px 이하인 경우 (모바일 환경)
+ width: 100%;
+ max-width: 33%; // item이 한줄에 3개씩 오도록
+ margin-right: 0;
+ }
+`;
+
+const StatusBubble = styled.div`
+ position: relative;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 10px;
+
+ img {
+ position: absolute;
+ border-radius: inherit;
+ }
+
+ span {
+ position: relative;
+ z-index: 2; // 이미지 위에 텍스트
+ overflow: hidden;
+ white-space: nowrap;
+ color: var(--black);
+ font-family: "SF Pro Text";
+ font-size: 0.625rem;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 150%;
+ }
+`;
+const Name = styled.div`
+ margin-top: 0.5rem;
+ color: var(--black);
+ text-align: center;
+ font-family: "SF Pro Text";
+ font-size: 1.125rem;
+ font-style: normal;
+ font-weight: 600;
+ line-height: normal;
+`;
+const Instagram = styled.div`
+ color: var(--gray-1);
+ text-align: center;
+ font-family: "SF Pro Text";
+ font-size: 0.75rem;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 125%;
+`;
diff --git a/src/pages/profile/profile.tsx b/src/pages/profile/profile.tsx
new file mode 100644
index 0000000..f22e25c
--- /dev/null
+++ b/src/pages/profile/profile.tsx
@@ -0,0 +1,168 @@
+import styled from "styled-components";
+import { useNavigate } from "react-router-dom";
+//이미지 & datas
+import bottomBar from "../../assets/images/LightBottomBar.svg";
+import StatusBar from "../../components/StatusBar/statusbar";
+import BigIconLogo from "../../assets/images/BigIconLogo.svg";
+import Github from "../../assets/images/github.svg";
+import RightArrow from "../../assets/images/Arrow.svg";
+import Instagram from "../../assets/images/instagram.svg";
+import userData from "../../assets/datas/userdata.json";
+
+export default function Profile() {
+ const navigate = useNavigate();
+
+ return (
+
+
+
+
+ My Profile
+
+
+ {userData.users[0].name}
+ donghyun98@gmail.com
+
+ window.open("https://github.com/dhshin98", "_blank")}
+ >
+
+
+ GitHub
+ https://github.com/dhshin98
+
+
+
+
+
+
+
+ Instagram
+ @인스타 아이디
+
+
+
+
+ navigate("/chatlist")}>
+
+
+ Instagram
+ {userData.users[0].instagram}
+
+
+
+
+
+
+ );
+}
+const ProfileContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ position: absolute;
+ border-radius: 1.5rem;
+ height: 100vh;
+ width: 100vw;
+ border: solid var(--gray-1);
+`;
+
+const Title = styled.text`
+ font-family: "SF Pro Text";
+ font-size: 2.125rem;
+ font-style: normal;
+ font-weight: 700;
+ line-height: 80%;
+ width: 100%;
+ padding-left: 1.37rem;
+ padding-top: 2.94rem;
+`;
+const BigIcon = styled.img`
+ width: 5.9375rem;
+ height: 5.93725rem;
+ margin-top: 2.62rem;
+`;
+const Rectangle = styled.div`
+ width: 90%;
+ height: 4.1875rem;
+ border-radius: 1.25rem;
+ display: flex;
+ align-items: center;
+ margin: 0rem 1rem;
+ box-shadow: 0px 0px 15px 0px rgba(0, 0, 0, 0.1);
+ margin-bottom: 0.69rem;
+`;
+const Icon = styled.img`
+ /* width: ${(props) =>
+ props.src === "Instagram" ? "2.1875rem" : "2.77813rem"};
+ height: ${(props) =>
+ props.src === "Instagram" ? "2.1875rem" : "2.77813rem"};
+ */
+ margin: 0.69rem 0.35rem 0.72rem 0.75rem;
+ width: ${(props) => props.width};
+ height: ${(props) => props.width};
+`;
+const RectangleInfo = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 0.06rem;
+ height: 100%;
+ width: 100%;
+ padding-left: 1rem;
+ padding-right: 0.44rem;
+ padding-bottom: 1.12rem;
+ padding-top: 0.94rem;
+`;
+
+const RightArrowIcon = styled.img`
+ width: 1rem;
+ height: 1rem;
+ position: relative;
+ margin-right: 1.44rem;
+`;
+const Subtitle = styled.text`
+ font-family: "SF Pro Text";
+ font-size: 0.9375rem;
+ font-style: normal;
+ font-weight: 400;
+ line-height: normal;
+`;
+const SubtitleInfo = styled.text`
+ color: var(--gray-1);
+ font-family: "SF Pro Text";
+ font-size: 0.75rem;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 125%;
+`;
+
+const BottomBarIcon = styled.img`
+ width: 100%;
+ height: 2.125rem;
+ margin-bottom: 0;
+ /* position: relative; */
+ position: absolute;
+ bottom: 0;
+`;
+
+const Name = styled.text`
+ color: var(--black);
+ font-family: "SF Pro Text";
+ font-size: 1.125rem;
+ font-style: normal;
+ font-weight: 600;
+ line-height: normal;
+ margin-top: 0.69rem;
+`;
+
+const Email = styled.text`
+ color: var(--gray-1);
+ text-align: right;
+ font-family: "SF Pro Text";
+ font-size: 0.75rem;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 0.9375rem;
+ text-decoration-line: underline;
+ margin-bottom: 3.25rem;
+`;
diff --git a/src/styles/colors.css b/src/styles/colors.css
new file mode 100644
index 0000000..8e4ea72
--- /dev/null
+++ b/src/styles/colors.css
@@ -0,0 +1,7 @@
+:root{
+ --black : #000;
+ --gray-1 : #909093;
+ --gray-2: rgba(118, 118, 128, 0.12);
+ --gray-3: rgba(60, 60, 67, 0.30);
+ --blue : #3478F6;
+}
\ No newline at end of file
diff --git a/src/styles/globalStyle.ts b/src/styles/globalStyle.ts
new file mode 100644
index 0000000..175472f
--- /dev/null
+++ b/src/styles/globalStyle.ts
@@ -0,0 +1,37 @@
+import { createGlobalStyle } from "styled-components";
+import reset from "styled-reset";
+import "../styles/colors.css"
+const GlobalStyle = createGlobalStyle`
+ ${reset}
+ *{
+ box-sizing:border-box;
+ padding: 0;
+ margin: 0;
+ font-family: "SF Pro", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
+
+ }
+ html,body{
+ height:100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ }
+ .pageWrapper{
+ margin: 0;
+ padding:0;
+ display: flex;
+ flex-direction:column;
+ justify-content: center;
+ height:100vh;
+ width:100vw;
+
+ }
+ :hover {
+ cursor: pointer;
+ }
+
+
+`;
+
+export default GlobalStyle;
\ No newline at end of file
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..18d86a1
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,26 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "lib": [
+ "dom",
+ "dom.iterable",
+ "esnext"
+ ],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "noFallthroughCasesInSwitch": true,
+ "module": "esnext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx"
+ },
+ "include": [
+ "src"
+ ]
+ }
\ No newline at end of file