저희 참교6조는 Toy Project2 프로젝트에서 CHWIMIMATE
라는 사이트를 개발하였으며 본 사이트는 취미가 맞는사람들끼리 채팅을 하여 친목을 쌓는 프로그램입니다.
id: admin / pw: test1234
✅ useState 또는 useReducer를 활용한 상태 관리 구현
✅ Sass, styled-component, emotion, Chakra UI, tailwind CSS 등을 활용한 스타일 구현
✅ react 상태를 통한 CRUD 구현
✅ 상태에 따라 달라지는 스타일 구현
✅ custom hook을 통한 비동기 처리 구현
✅ 유저인증 시스템(로그인, 회원가입) 구현
✅ jwt등의 유저 인증 시스템 (로그인, 회원가입 기능)
✅ 소켓을 이용한 채팅 구현
✅ typescript를 활용한 앱 구현
토이2 프로젝트 설명
주어진 API와 소켓을 분석해 어떤 프로젝트를 진행/완성할 것인지 팀 단위로 자유롭게 결정하고 만들어보세요.
과제 수행 및 리뷰 기간은 별도 공지를 참고하세요!
Y_FE_Toy2_{팀명}
E.g, Y_FE_Toy2_GYOHEON
- 현재 저장소를 로컬에 클론(Clone)합니다.
- 자신의 팀명으로 브랜치를 생성합니다.(구분 가능하도록 팀명을 꼭 파스칼케이스로 표시하세요,
git branch Y_FE_Toy2_Team13
) - 자신의 팀명 브랜치에서 과제를 수행합니다.
- 과제 수행이 완료되면, 자신의 팀명 브랜치를 원격 저장소에 푸시(Push)합니다.(
main
브랜치에 푸시하지 않도록 꼭 주의하세요,git push origin Y_FE_Toy2_Team13
) - 저장소에서
main
브랜치를 대상으로 Pull Request 생성하면, 과제 제출이 완료됩니다!(E.g,main
<==Y_FE_Toy2_Team13
) - Pull Request 링크를 LMS로도 제출해 주셔야 합니다.
- main 혹은 다른 사람의 브랜치로 절대 병합하지 않도록 주의하세요!
- Pull Request에서 보이는 설명을 다른 사람들이 이해하기 쉽도록 꼼꼼하게 작성하세요!
-
과제 수행 및 제출 과정에서 문제가 발생한 경우, 바로 담당 멘토나 강사에게 얘기하세요!
-
백엔드 서버에 문제가 생겼을 경우, 바로 슬랙의 GyoHeon Lee에게 연락하세요!
-
useState
또는useReducer
를 활용한 상태 관리 구현 -
Sass
,styled-component
,emotion
,Chakra UI
,tailwind CSS
등을 활용한 스타일 구현 -
react
상태를 통한 CRUD 구현 - 상태에 따라 달라지는 스타일 구현
-
custom hook
을 통한 비동기 처리 구현 - 유저인증 시스템(로그인, 회원가입) 구현
-
jwt
등의 유저 인증 시스템 (로그인, 회원가입 기능) - 소켓을 이용한 채팅 구현
-
Next.js
를 활용한 서버 사이드 렌더링 구현 -
typescript
를 활용한 앱 구현 -
storybook
을 활용한 디자인 시스템 구현 -
jest
를 활용한 단위 테스트 구현
- api들의 응답 데이터들을 일부러 파편화 해두었습니다!
- api들 간의 데이터를 조합하여 이상적인 구조를 만들어보세요.
- 모든 network 요청(Request)
headers
에 아래 정보가 꼭 포함돼야 합니다! - serverId는 팀마다 개별 전달됩니다.
- 확인할 수 없는 사용자나 팀의 DB 정보는 임의로 삭제될 수 있습니다!
{
"content-type": "application/json",
"serverId": "nREmPe9B"
}
interface User {
id: string;
password: string;
name: string;
picture: string;
chats: string[]; // chat id만 속합니다.
}
interface Chat {
id: string;
name: string;
isPrivate: boolean;
users: string[];
messages: Message[]; // message 객체가 속합니다.
updatedAt: Date;
}
interface Message {
id: string;
text: string;
userId: string;
createdAt: Date;
}
사용자가 id
에 종속되어 회원가입합니다.
- 사용자 비밀번호는 암호화해 저장합니다.
- 프로필 이미지는 url or base64 형식이어야 합니다.
- 프로필 이미지는 1MB 이하여야 합니다.
curl https://fastcampus-chat.net/signup
\ -X 'POST'
요청 데이터 타입 및 예시:
interface RequestBody {
id: string; // 사용자 아이디 (필수!, 영어와 숫자만)
password: string; // 사용자 비밀번호, 5자 이상 (필수!)
name: string; // 사용자 이름, 20자 이하 (필수!)
picture?: string; // 사용자 이미지(url or base64, under 1MB)
}
{
"id": "abcd",
"password": "********",
"name": "GyoHeon",
"picture": "https://avatars.githubusercontent.com/u/66263916?v=4"
}
응답 데이터 타입 및 예시:
interface ResponseValue {
message: title;
}
{
"message": "User created"
}
id
중복 체크를 합니다.
curl https://fastcampus-chat.net/check/id
\ -X 'POST'
요청 데이터 타입 및 예시:
interface RequestBody {
id: string; // 사용자 아이디 (필수!, 영어와 숫자만)
}
{
"id": "abcd"
}
응답 데이터 타입 및 예시:
interface ResponseValue {
isDuplicated: boolean;
}
{
"isDuplicated": false
}
- 발급된
accessToken
은 7일 후 만료됩니다.
curl https://fastcampus-chat.net/login
\ -X 'POST'
요청 데이터 타입 및 예시:
interface RequestBody {
id: string; // 사용자 아이디 (필수!)
password: string; // 사용자 비밀번호 (필수!)
}
{
"id": "abcd",
"password": "********"
}
응답 데이터 타입 및 예시:
interface ResponseValue {
accessToken: string; // 사용자 접근 토큰
refreshToken: string; // access token 발급용 토큰
}
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjlQS3I...(생략)",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjlQS3I...(생략)"
}
id
중복 체크를 합니다.
curl https://fastcampus-chat.net/auth/me
\ -X 'GET'
\ -H 'Authorization: Bearer <accessToken>'
요청 데이터 타입 및 예시:
- 없음
응답 데이터 타입 및 예시:
interface ResponseValue {
auth: boolean;
user?: User;
}
interface User {
id: string;
name: string;
picture: string;
}
{
"auth": true,
"user": {
"id": "test1",
"name": "abcde",
"picture": "https://avatars.githubusercontent.com/u/42333366?v=4"
}
}
curl https://fastcampus-chat.net/refresh
\ -X 'POST'
요청 데이터 타입 및 예시:
interface RequestBody {
refreshToken: string; // access token 발급용 토큰
}
{
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjlQS3I...(생략)"
}
응답 데이터 타입 및 예시:
interface ResponseValue {
accessToken: string; // 사용자 접근 토큰
}
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjlQS3I...(생략)"
}
- 프로필 이미지는 url or base64 형식이어야 합니다.
- 프로필 이미지는 1MB 이하여야 합니다.
curl https://fastcampus-chat.net/user
\ -X 'PATCH'
\ -H 'Authorization: Bearer <accessToken>'
요청 데이터 타입 및 예시:
interface RequestBody {
name?: string; // 새로운 표시 이름
picture?: string; // 사용자 프로필 이미지(url or base64)
}
{
"name": "abcde",
"picture": "https://avatars.githubusercontent.com/u/42333366?v=4"
}
응답 데이터 타입 및 예시:
interface ResponseValue {
messgae: string;
}
{
"message": "User updated"
}
- 특정 유저를 조회합니다.
curl https://fastcampus-chat.net/user?userId=${userId}
\ -X 'GET'
\ -H 'Authorization: Bearer <accessToken>'
요청 데이터 타입 및 예시:
- 없음
- 조회하고 싶은 id는 query string으로 사용합니다.
응답 데이터 타입 및 예시:
type ResponseValue = {
user: User;
};
interface User {
id: string;
name: string;
picture: string;
}
{
"user": {
"id": "user1",
"name": "lgh",
"picture": "https://gravatar.com/avatar/c274467c5ef4fe381b154a20c5e7ce26?s=200&d=retro"
}
}
- 현재 존재하는 모든 유저를 조회합니다.
curl https://fastcampus-chat.net/users
\ -X 'GET'
\ -H 'Authorization: Bearer <accessToken>'
요청 데이터 타입 및 예시:
- 없음
응답 데이터 타입 및 예시:
type ResponseValue = User[];
interface User {
id: string;
name: string;
picture: string;
}
[
{
"id": "user1",
"name": "lgh",
"picture": "https://gravatar.com/avatar/c274467c5ef4fe381b154a20c5e7ce26?s=200&d=retro"
},
{
"id": "user2",
"name": "ldj",
"picture": "https://gravatar.com/avatar/d94869409b4e94903723612a4f93a6f9?s=200&d=retro"
}
]
curl https://fastcampus-chat.net/chat
\ -X 'POST'
\ -H 'Authorization: Bearer <accessToken>'
요청 데이터 타입 및 예시:
interface RequestBody {
name: string; // chat 이름
users: string[]; // 참가자들 id(자신 미포함)
isPrivate?: boolean; // 공개 비공개
}
{
"name": "test chat",
"users": ["user1", "user2"]
}
응답 데이터 타입 및 예시:
interface ResponseValue {
id: string;
name: string;
users: User[]; // 자신을 포함한 참가자들 정보
isPrivate: boolean;
updatedAt: Date;
}
interface User {
id: string;
name: string;
picture: string;
}
{
"id": "fasgadsfdsghssdlsdafasd",
"name": "test chat",
"users": [
{
"id": "user1",
"name": "lgh",
"picture": "https://gravatar.com/avatar/c274467c5ef4fe381b154a20c5e7ce26?s=200&d=retro"
},
{
"id": "user2",
"name": "ldj",
"picture": "https://gravatar.com/avatar/d94869409b4e94903723612a4f93a6f9?s=200&d=retro"
}
],
"isPrivate": false,
"updatedAt": "2023-11-01T08:23:39.850Z"
}
- 특정 id의 채팅을 조회합니다.
- isPrivate: true인 채팅방은 해당 채팅방 참가자만 볼 수 있습니다.
curl https://fastcampus-chat.net/chat/only?chatId=${chatId}
\ -X 'GET'
\ -H 'Authorization: Bearer <accessToken>'
요청 데이터 타입 및 예시:
- 없음
응답 데이터 타입 및 예시:
interface ResponseValue {
chat: Chat;
}
interface Chat {
id: string;
name: string;
users: User[]; // 속한 유저 정보
isPrivate: boolean;
latestMessage: Message | null;
updatedAt: Date;
}
interface User {
id: string;
name: string;
picture: string;
}
interface Message {
id: string;
text: string;
userId: string;
createAt: Date;
}
{
"chat": {
"id": "f189ab25-5644-4d72-bd7c-0170ee9c8ede",
"name": "chat room 1",
"users": [
{
"id": "user1",
"name": "lgh",
"picture": "https://gravatar.com/avatar/c274467c5ef4fe381b154a20c5e7ce26?s=200&d=retro"
},
{
"id": "user2",
"name": "ldj",
"picture": "https://gravatar.com/avatar/d94869409b4e94903723612a4f93a6f9?s=200&d=retro"
}
],
"isPrivate": false,
"updatedAt": "2023-10-31T13:18:38.216Z",
"latestMessage": null
}
}
- 현재 존재하는 모든 채팅을 조회합니다.
- isPrivate: true인 채팅방은 보이지 않습니다.
curl https://fastcampus-chat.net/chat/all
\ -X 'GET'
\ -H 'Authorization: Bearer <accessToken>'
요청 데이터 타입 및 예시:
- 없음
응답 데이터 타입 및 예시:
type ResponseValue = Chat[];
interface Chat {
id: string;
name: string;
users: User[]; // 속한 유저 정보
isPrivate: boolean;
latestMessage: Message | null;
updatedAt: Date;
}
interface User {
id: string;
name: string;
picture: string;
}
interface Message {
id: string;
text: string;
userId: string;
createAt: Date;
}
[
{
"id": "f189ab25-5644-4d72-bd7c-0170ee9c8ede",
"name": "chat room 1",
"users": [
{
"id": "user1",
"name": "lgh",
"picture": "https://gravatar.com/avatar/c274467c5ef4fe381b154a20c5e7ce26?s=200&d=retro"
},
{
"id": "user2",
"name": "ldj",
"picture": "https://gravatar.com/avatar/d94869409b4e94903723612a4f93a6f9?s=200&d=retro"
}
],
"isPrivate": false,
"updatedAt": "2023-10-31T13:18:38.216Z",
"latestMessage": null
},
{
"id": "f189ab25-5644-4d72-bd7c-0170ee9c8edj",
"name": "chat room 2",
"users": [
{
"id": "user1",
"name": "lgh",
"picture": "https://gravatar.com/avatar/c274467c5ef4fe381b154a20c5e7ce26?s=200&d=retro"
},
{
"id": "user2",
"name": "ldj",
"picture": "https://gravatar.com/avatar/d94869409b4e94903723612a4f93a6f9?s=200&d=retro"
}
],
"isPrivate": false,
"updatedAt": "2023-10-31T15:18:38.216Z",
"latestMessage": {
"id": "8f7f67bb-f1ab-4792-9678-0b8546adcb6f",
"text": "testtest444",
"userId": "test:test6",
"createdAt": "2023-11-06T11:15:50.588+00:00"
}
}
]
curl https://fastcampus-chat.net/chat
\ -X 'GET'
\ -H 'Authorization: Bearer <accessToken>'
- 내가 속한 모든 채팅을 조회합니다.
- isPrivate: true인 채팅방도 모두 보이게 됩니다.
요청 데이터 타입 및 예시:
- 없음
응답 데이터 타입 및 예시:
type ResponseValue = Chat[];
interface Chat {
id: string;
name: string;
users: User[]; // 속한 유저 id
isPrivate: boolean;
latestMessage: Message | null;
updatedAt: Date;
}
interface User {
id: string;
name: string;
picture: string;
}
interface Message {
id: string;
text: string;
userId: string;
createAt: Date;
}
[
{
"id": "f189ab25-5644-4d72-bd7c-0170ee9c8ede",
"name": "chat room 1",
"users": [
{
"id": "user1",
"name": "lgh",
"picture": "https://gravatar.com/avatar/c274467c5ef4fe381b154a20c5e7ce26?s=200&d=retro"
},
{
"id": "user2",
"name": "ldj",
"picture": "https://gravatar.com/avatar/d94869409b4e94903723612a4f93a6f9?s=200&d=retro"
}
],
"isPrivate": true,
"updatedAt": "2023-10-31T13:18:38.216Z",
"latestMessage": null
},
{
"id": "f189ab25-5644-4d72-bd7c-0170ee9c8edj",
"name": "chat room 2",
"users": [
{
"id": "user1",
"name": "lgh",
"picture": "https://gravatar.com/avatar/c274467c5ef4fe381b154a20c5e7ce26?s=200&d=retro"
},
{
"id": "user2",
"name": "ldj",
"picture": "https://gravatar.com/avatar/d94869409b4e94903723612a4f93a6f9?s=200&d=retro"
}
],
"isPrivate": false,
"updatedAt": "2023-10-31T15:18:38.216Z",
"latestMessage": {
"id": "8f7f67bb-f1ab-4792-9678-0b8546adcb6f",
"text": "testtest444",
"userId": "test:test6",
"createdAt": "2023-11-06T11:15:50.588+00:00"
}
}
]
curl https://fastcampus-chat.net/chat/participate
\ -X 'PATCH'
\ -H 'Authorization: Bearer <accessToken>'
요청 데이터 타입 및 예시:
interface RequestBody {
chatId: string;
}
{
"chatId": "f189ab25-5644-4d72-bd7c-0170ee9c8ede"
}
응답 데이터 타입 및 예시:
interface ResponseValue {
id: string;
name: string;
users: User[]; // 속한 유저 id
isPrivate: boolean;
updatedAt: Date;
}
interface User {
id: string;
name: string;
picture: string;
}
{
"id": "f189ab25-5644-4d72-bd7c-0170ee9c8ede",
"name": "chat room 1",
"users": [
{
"id": "user1",
"name": "lgh",
"picture": "https://gravatar.com/avatar/c274467c5ef4fe381b154a20c5e7ce26?s=200&d=retro"
},
{
"id": "user2",
"name": "ldj",
"picture": "https://gravatar.com/avatar/d94869409b4e94903723612a4f93a6f9?s=200&d=retro"
}
],
"isPrivate": true,
"updatedAt": "2023-10-31T13:18:38.216Z"
}
curl https://fastcampus-chat.net/chat/leave
\ -X 'PATCH'
\ -H 'Authorization: Bearer <accessToken>'
요청 데이터 타입 및 예시:
interface RequestBody {
chatId: string;
}
{
"chatId": "f189ab25-5644-4d72-bd7c-0170ee9c8ede"
}
응답 데이터 타입 및 예시:
interface ResponseValue {
message: string;
}
{
"message": "Leave success"
}
curl https://fastcampus-chat.net/chat/invite
\ -X 'PATCH'
\ -H 'Authorization: Bearer <accessToken>'
요청 데이터 타입 및 예시:
interface RequestBody {
chatId: string;
users: string[]; // 초대할 유저 id
}
{
"chatId": "f189ab25-5644-4d72-bd7c-0170ee9c8ede",
"users": ["user1", "user2"]
}
응답 데이터 타입 및 예시:
interface ResponseValue {
id: string;
name: string;
users: User[]; // 속한 유저 정보
isPrivate: boolean;
updatedAt: Date;
}
interface User {
id: string;
name: string;
picture: string;
}
{
"id": "f189ab25-5644-4d72-bd7c-0170ee9c8ede",
"name": "chat room 1",
"users": [
{
"id": "user1",
"name": "lgh",
"picture": "https://gravatar.com/avatar/c274467c5ef4fe381b154a20c5e7ce26?s=200&d=retro"
},
{
"id": "user2",
"name": "ldj",
"picture": "https://gravatar.com/avatar/d94869409b4e94903723612a4f93a6f9?s=200&d=retro"
}
],
"isPrivate": true,
"updatedAt": "2023-10-31T13:18:38.216Z"
}
- socket.io 의 사용을 추천드립니다.
- Socket 연결시에도 headers는 유지해야 합니다.
io(`https://fastcampus-chat.net/chat?chatId=${chatId}`, {
extraHeaders: {
Authorization: "Bearer <accessToken>",
serverId: "test"
}
});
socket.emit("message-to-server", text);
- 같은 방에 있는 사람들에게 메세지를 전달합니다.
요청 데이터
type RequestData: string;
- 이전 대화 목록을 불러옵니다.
messages-to-client
로 데이터를 받을 수 있습니다.
요청 데이터
- 없음
- 접속 상태인 유저 목록을 불러옵니다.
users-to-client
로 데이터를 받을 수 있습니다.
요청 데이터
- 없음
socket.on("message-to-client", (messageObject) => {
console.log(messageObject);
});
- 같은 방에 있는 사람들에게 메세지를 전달합니다.
응답 데이터
interface ResponseData {
id: string;
text: string;
userId: string; // 메세지를 보낸 사람의 id
createdAt: Date;
}
- 이전 대화 목록을 불러옵니다.
응답 데이터
interface Message {
id: string;
text: string;
userId: string; // 메세지를 보낸 사람의 id
createdAt: Date;
}
interface ResponseData {
messages: Message[];
}
- 같은 방에 새로운 사람이 들어오면 모든 유저의 정보를 다시 받습니다.
응답 데이터
interface ResponseData {
users: string[]; // 참여자들 id
joiners: string[]; // 새로운 참여자 id
}
- 같은 방에 사람이 나가면 모든 유저의 정보를 다시 받습니다.
응답 데이터
interface ResponseData {
users: string[]; // 참여자들 id
leaver: string; // 나간 사용자 id
}
- 접속 상태인 유저 목록을 불러옵니다.
응답 데이터
interface ResponseData {
user: string[]; // 참가자들 id
}
io(`https://fastcampus-chat.net/server`, {
extraHeaders: {
Authorization: "Bearer <accessToken>",
serverId: "test"
}
});
socket.emit("users-server");
- 같은 serverId를 사용하는 online 사용자를 불러옵니다.
users-server-to-client
로 데이터를 받을 수 있습니다.
요청 데이터
- 없음
socket.on("message-to-client", (messageObject) => {
console.log(messageObject);
});
- 같은 serverId를 사용하는 접속 상태인 유저 목록을 불러옵니다.
응답 데이터
interface ResponseData {
user: string[]; // 참가자들 id
}
- 새로운 채팅방 생성시 해당 채팅방 유저에게 채팅방 정보를 전송합니다.
- 기존 채팅방에 유저 초대시 초대된 유저에게 채팅방 정보를 전송합니다.
응답 데이터
interface ResponseData {
id: string;
name: string;
users: string[]; // 참여자들 id
isPrivate: boolean;
updatedAt: Date;
}
- 새로운 대화방이 생긴 경우 (not private) 서버(팀에서 사용하는 serverId)의 참여자들에게 이를 전달합니다.
응답 데이터
interface ResponseData {
id: string;
name: string;
users: string[]; // 참여자들 id
isPrivate: boolean;
updatedAt: Date;
}
@YongYong21 (박용희) : 프로필 레이아웃+파이어베이스설계
@KittelLee (이진욱) : 모달+레이아웃, 채팅
@tkyoun0421 (윤태관) : 채팅, 로그인, 회원가입
@furaha707 (이예인) : 레이아웃, 채팅
@mysdpy30s (김미정) : 컴포넌트 레이아웃, 채팅
CHWIMIMATE
├── src/
│ ├── assets/
| | ├── fonts/
| | └── images/
| |
│ ├── components/
│ │ ├── Chat/
│ │ ├── FormInputBtn/
│ │ ├── Loader/
│ │ ├── Login/
│ │ ├── Main/
│ │ ├── Profile/
│ │ ├── SearchInput/
│ │ ├── SignUp/
| | ├── ModalPlus.tsx
| | ├── ModalHamburger.tsx
| | ├── PageNotFount.tsx
| | ├── Logout.tsx
| | ├── Layout.tsx
│ │ ├── Header.tsx
│ │ └── Footer.tsx
│ │
| ├── firebase/
| ├── hooks/
│ ├── pages/
│ ├── style/
│ ├── utils/
│ ├── App.tsx
│ └── index.tsx
│
├── node_modules/
├── .eslintrc.cjs
├── .gitignore
├── .prettierrc
├── index.html
├── package-lock.json
├── package.json
├── README.md
├── tsconfig.json
├── tsconfig.node.json
├── vercel.json
└── vite.config.ts
박용희 Frontend |
이예인 Frontend |
김미정 Frontend |
이진욱 Frontend |
윤태관 Frontend |
구영표 멘토님 멘토링 |