Skip to content

MOVIEJOJO7/cat-talk

Repository files navigation

Cat Talk


Cat Talk 배포 레포

Test ID : cat
Test PW : 00000

🧑🏻‍💻 프로젝트 소개

Cat Talk는 반려묘를 기르고 있는 집사들을 위한 채팅 웹 서비스 입니다.

[필수 구현 사항]

useState를 활용한 상태 관리 구현
tailwind CSS를 활용한 스타일 구현
react 상태를 통한 CRUD 구현 (Next.js 'use client')
✅ 상태에 따라 달라지는 스타일 구현
custom hook을 통한 비동기 처리 구현
✅ 유저인증 시스템(로그인, 회원가입) 구현
jwt등의 유저 인증 시스템 (로그인, 회원가입 기능)
✅ 소켓을 이용한 채팅 구현

[선택 구현 사항]

Next.js를 활용한 서버 사이드 렌더링 구현
typescript를 활용한 앱 구현
storybook을 활용한 디자인 시스템 구현
jest를 활용한 단위 테스트 구현


야놀자 테크 캠프 토이 프로젝트 2 요구 사항

🍋 소켓 기반 채팅앱

주어진 API와 소켓을 분석해 어떤 프로젝트를 진행/완성할 것인지 팀 단위로 자유롭게 결정하고 만들어보세요.
과제 수행 및 리뷰 기간은 별도 공지를 참고하세요!

과제 수행 및 제출 방법

Y_FE_Toy2_{팀명}

E.g, Y_FE_Toy2_GYOHEON
  1. 현재 저장소를 로컬에 클론(Clone)합니다.
  2. 자신의 팀명으로 브랜치를 생성합니다.(구분 가능하도록 팀명을 꼭 파스칼케이스로 표시하세요, git branch Y_FE_Toy2_Team13)
  3. 자신의 팀명 브랜치에서 과제를 수행합니다.
  4. 과제 수행이 완료되면, 자신의 팀명 브랜치를 원격 저장소에 푸시(Push)합니다.(main 브랜치에 푸시하지 않도록 꼭 주의하세요, git push origin Y_FE_Toy2_Team13)
  5. 저장소에서 main 브랜치를 대상으로 Pull Request 생성하면, 과제 제출이 완료됩니다!(E.g, main <== Y_FE_Toy2_Team13)
  6. Pull Request 링크를 LMS로도 제출해 주셔야 합니다.
  7. main 혹은 다른 사람의 브랜치로 절대 병합하지 않도록 주의하세요!
  8. 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들 간의 데이터를 조합하여 이상적인 구조를 만들어보세요.

예시 프로젝트

private-messaging-part-1-chat-ab610e9e03738ad37f7b0fb55c771087

API 사용법

  • 모든 network 요청(Request) headers에 아래 정보가 꼭 포함돼야 합니다!
  • serverId는 팀마다 개별 전달됩니다.
  • 확인할 수 없는 사용자나 팀의 DB 정보는 임의로 삭제될 수 있습니다!
{
	"content-type": "application/json",
	"serverId": "nREmPe9B"
}

기본 데이터 구조

user

interface User {
	id: string;
	password: string;
	name: string;
	picture: string;
	chats: string[]; // chat id만 속합니다.
}

chat

interface Chat {
	id: string;
	name: string;
	isPrivate: boolean;
	users: string[];
	messages: Message[]; // message 객체가 속합니다.

	updatedAt: Date;
}

message

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 중복 체크

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

  • socket.io 의 사용을 추천드립니다.
  • Socket 연결시에도 headers는 유지해야 합니다.

기본 연결

io(`https://fastcampus-chat.net/chat?chatId=${chatId}`, {
	extraHeaders: {
		Authorization: 'Bearer <accessToken>',
		serverId: 'test',
	},
});

emit Event(client -> server)

example

socket.emit('message-to-server', text);

message-to-server

  • 같은 방에 있는 사람들에게 메세지를 전달합니다.

요청 데이터

type RequestData: string;

fetch-messages

  • 이전 대화 목록을 불러옵니다.
  • messages-to-client로 데이터를 받을 수 있습니다.

요청 데이터

  • 없음

users

  • 접속 상태인 유저 목록을 불러옵니다.
  • users-to-client로 데이터를 받을 수 있습니다.

요청 데이터

  • 없음

on Event(server -> client)

example

socket.on('message-to-client', (messageObject) => {
	console.log(messageObject);
});

message-to-client

  • 같은 방에 있는 사람들에게 메세지를 전달합니다.

응답 데이터

interface ResponseData {
	id: string;
	text: string;
	userId: string; // 메세지를 보낸 사람의 id
	createdAt: Date;
}

messages-to-client

  • 이전 대화 목록을 불러옵니다.

응답 데이터

interface Message {
	id: string;
	text: string;
	userId: string; // 메세지를 보낸 사람의 id
	createdAt: Date;
}

interface ResponseData {
	messages: Message[];
}

join

  • 같은 방에 새로운 사람이 들어오면 모든 유저의 정보를 다시 받습니다.

응답 데이터

interface ResponseData {
	users: string[]; // 참여자들 id
	joiners: string[]; // 새로운 참여자 id
}

leave

  • 같은 방에 사람이 나가면 모든 유저의 정보를 다시 받습니다.

응답 데이터

interface ResponseData {
	users: string[]; // 참여자들 id
	leaver: string; // 나간 사용자 id
}

users-to-client

  • 접속 상태인 유저 목록을 불러옵니다.

응답 데이터

interface ResponseData {
	user: string[]; // 참가자들 id
}

server 연결

io(`https://fastcampus-chat.net/server`, {
	extraHeaders: {
		Authorization: 'Bearer <accessToken>',
		serverId: 'test',
	},
});

emit Event(client -> server)

example

socket.emit('users-server');

users-server

  • 같은 serverId를 사용하는 online 사용자를 불러옵니다.
  • users-server-to-client로 데이터를 받을 수 있습니다.

요청 데이터

  • 없음

on Event(server -> client)

example

socket.on('message-to-client', (messageObject) => {
	console.log(messageObject);
});

users-server-to-client

  • 같은 serverId를 사용하는 접속 상태인 유저 목록을 불러옵니다.

응답 데이터

interface ResponseData {
	user: string[]; // 참가자들 id
}

invite

  • 새로운 채팅방 생성시 해당 채팅방 유저에게 채팅방 정보를 전송합니다.
  • 기존 채팅방에 유저 초대시 초대된 유저에게 채팅방 정보를 전송합니다.

응답 데이터

interface ResponseData {
	id: string;
	name: string;
	users: string[]; // 참여자들 id
	isPrivate: boolean;
	updatedAt: Date;
}

new-chat

  • 새로운 대화방이 생긴 경우 (not private) 서버(팀에서 사용하는 serverId)의 참여자들에게 이를 전달합니다.

응답 데이터

interface ResponseData {
	id: string;
	name: string;
	users: string[]; // 참여자들 id
	isPrivate: boolean;
	updatedAt: Date;
}

🧑🏻‍💻 Contributor

@TaePoong719 (최우혁) : 유저 목록, 참여한 채팅 및 유저 검색
@LikeFireAndSky (김민섭) : 채팅 목록 구현, 클라우디너리 연동, 퍼블리싱
@hhjs2 (정효주) : 로그인, 회원가입, 채팅 기능 구현
@JitHoon (최지훈) : 채팅 검색, 채팅 기능 구현
@syb0127 (서예빈) : 로그아웃, 푸터 구현 및 디자인 작업


🧑🏻‍💻 Stack

Stack


🧑🏻‍💻 디자인

Userflow

유저플로우1 유저플로우2
유저플로우3 유저플로우4
유저플로우5

디자인 프로토타입

image

🧑🏻‍💻 주요 기능 구현

로그인, 회원가입

유저 목록 조회, 검색, 채팅

채팅방 검색 및 채팅

드래그 & 드롭


🧑🏻‍💻 Script

Development Mode

$ git clone https://github.com/MOVIEJOJO7/cat-talk.git
$ npm i
$ npm run dev

🧑🏻‍💻 팀 소개

최우혁 프로필 김민섭 프로필 정효주 프로필 최지훈 프로필 서예빈 프로필 김민수 멘토님
최우혁
Frontend
김민섭
Frontend
정효주
Frontend
최지훈
Frontend
서예빈
Frontend
김민수 멘토님

🧑🏻‍💻 개발 기간 : 11일 23.11.06 월 ~ 23.11.16 목