Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 100 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,113 @@
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
<div align="center">
<img width="652" alt="image" src="https://github.com/user-attachments/assets/ddde3e13-b5d5-4dc0-880b-29dec240bdd1" />

## Getting Started
> 📖 당신의 독서 생활에 새로운 페이지를 열어보세요!
<br/> 새로운 사람들과 함께 읽고 나누는 특별한 독서 경험, **북코**가 함께합니다.
> <br/>
<br/>[![Bookco](https://img.shields.io/badge/BOOKCO.SITE-00a991?style=for-the-badge)](https://bookco.vercel.app/)
</div

First, run the development server:
<br/>
<br/>
<br/>

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```
## 🎯 Bookco에서 할 수 있는 일

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
- **👥 독서 모임**

비슷한 취향을 가진 사람들과 함께 책을 읽고 이야기를 나눌 수 있습니다.
- 정해진 책으로 독서 모임에 참여하거나, 직접 모임을 만들 수 있습니다.

You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
- 💬 **채팅하기**

다른 북코 유저들과 채팅 기능을 통해 소통할 수 있습니다.
- 모임의 호스트나 교환하고 싶은 책을 가진 유저와 대화를 나눌 수 있습니다.

- **📚 교환하기 (추후 개발 예정..)**

안 보게 된 책을 등록하면, 다른 사람의 책과 바꿔 읽을 수 있습니다.
- 집에서 방치되던 책을 다른 유저와 공유할 수 있습니다.

This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
<br/>
<br/>

## Learn More
## 📚 서비스 소개

To learn more about Next.js, take a look at the following resources:
<img width="1328" alt="image" src="https://github.com/user-attachments/assets/25ef2ed8-3cef-4b7a-b897-2f95324de9d9" />
<img width="1325" alt="image" src="https://github.com/user-attachments/assets/3f812c80-5b20-45ac-8c21-86bb87e76856" />
<img width="1329" alt="image" src="https://github.com/user-attachments/assets/25d9a7f6-8419-4f0b-86d3-f3d11ac38afa" />
<img width="1327" alt="image" src="https://github.com/user-attachments/assets/3a293a66-b502-4be0-a6d8-b02ed88f233f" />
<img width="1325" alt="image" src="https://github.com/user-attachments/assets/4b90f89f-b3d6-4331-8429-4a2280879f58" />
<img width="1324" alt="image" src="https://github.com/user-attachments/assets/4257b49f-c552-4963-866f-7c3338ab307b" />
<img width="1323" alt="image" src="https://github.com/user-attachments/assets/545fc19d-7ed6-4848-afb0-7452563bf8fb" />
<img width="1325" alt="image" src="https://github.com/user-attachments/assets/c751accd-74ec-4258-b285-5a976562759e" />
<img width="1326" alt="image" src="https://github.com/user-attachments/assets/f7824ee7-864b-4671-a889-d7181701ee9e" />

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
<br/>
<br/>

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
## 🛠️ 기술스택

## Deploy on Vercel
### 💻 Core
![Next.js](https://img.shields.io/badge/Next.js-000000?style=for-the-badge&logo=next.js&logoColor=white)
![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?style=for-the-badge&logo=typescript&logoColor=white)

### 🔄 상태 관리
![TanStack Query](https://img.shields.io/badge/TanStack_Query-FF4154?style=for-the-badge&logo=reactquery&logoColor=white)
![Zustand](https://img.shields.io/badge/Zustand-000000?style=for-the-badge)

### 🌐 통신
![Axios](https://img.shields.io/badge/Axios-5A29E4?style=for-the-badge&logo=axios&logoColor=white)
![SockJS](https://img.shields.io/badge/SockJS-000000?style=for-the-badge&logo=socket.io&logoColor=white)
![STOMP](https://img.shields.io/badge/STOMP-000000?style=for-the-badge)

### 🎨 스타일링
![Tailwind CSS](https://img.shields.io/badge/Tailwind_CSS-06B6D4?style=for-the-badge&logo=tailwindcss&logoColor=white)

### ⚙️ 유틸리티
![Zod](https://img.shields.io/badge/Zod-3E67B1?style=for-the-badge&logo=zod&logoColor=white)

### 🧪 테스팅
![Jest](https://img.shields.io/badge/Jest-C21325?style=for-the-badge&logo=jest&logoColor=white)
![React Testing Library](https://img.shields.io/badge/React_Testing_Library-E33332?style=for-the-badge&logo=testing-library&logoColor=white)
![Storybook](https://img.shields.io/badge/Storybook-FF4785?style=for-the-badge&logo=storybook&logoColor=white)

### 📋 코드 품질
![ESLint](https://img.shields.io/badge/ESLint-4B32C3?style=for-the-badge&logo=eslint&logoColor=white)
![Prettier](https://img.shields.io/badge/Prettier-F7B93E?style=for-the-badge&logo=prettier&logoColor=black)
![Husky](https://img.shields.io/badge/Husky-000000?style=for-the-badge)

<br/>
<br/>

## 🤝 팀 협업 방식, 브랜치 전략

### ✅ **PR 리뷰 방식**
- **2명 Approve** 방식
- PR 확인 시간 고정: `09:00`, `13:00`, `18:00`
- **Pn 룰**과 **Dn 룰** 적용
- **데일리 스크럼** 진행

### ✅ **브랜치 전략**
- **GitHub Flow** 적용
- `feature` → `develop` → `main`
- `hotfix` 는 Main에서 급하게 수정할 일 있을 때 사용

### ✅ **CI/CD 전략**
- **Husky**를 통한 코드 품질 관리
- 커밋시 린트 검사
- **디스코드 웹훅 연결**로 실시간 알림
- PR 작성시 Lint 검사, test 코드 실행, 스토리북 빌드, 프로덕션 빌드 실행하여 검사

<br/>
<br/>

## 👥 팀원 구성

|FE|FE|FE|FE|
|:---:|:---:|:---:|:---:|
|<img src="https://avatars.githubusercontent.com/u/108677235?v=4" width="150"/>|<img src="https://avatars.githubusercontent.com/u/97824352?v=4" width="150"/>|<img src="https://avatars.githubusercontent.com/u/104830526?v=4" width="150"/>|<img src="https://avatars.githubusercontent.com/u/32586926?v=4" width="150"/>|
|[김선구](https://github.com/haegu97)|[김민경](https://github.com/wynter24)|[신선](https://github.com/sunnwave)|[김정호](https://github.com/cloud0406)|
<br>

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
4 changes: 4 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ const config = {
coverageProvider: 'v8',
testEnvironment: 'jsdom',
// setupFilesAfterEnv: ['<rootDir>/src/setupTests.ts'],
moduleNameMapper: {
// 절대 경로 매핑
'^@/(.*)$': '<rootDir>/src/$1',
},
};

// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
Expand Down
9 changes: 8 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@
"@stomp/stompjs": "^7.0.0",
"@tanstack/react-query": "^5.61.3",
"@tanstack/react-query-devtools": "^5.61.3",
"@types/lodash": "^4.17.14",
"@types/react-datepicker": "^6.2.0",
"@types/sockjs-client": "^1.5.4",
"axios": "^1.7.8",
"lodash": "^4.17.21",
"next": "15.0.3",
"react": "^18.3.1",
"react-datepicker": "^7.5.0",
Expand Down
47 changes: 47 additions & 0 deletions public/icons/AlertCircleIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { SVGProps } from 'react';

interface AlertCircleIconProps extends SVGProps<SVGSVGElement> {
width?: number;
height?: number;
}

function AlertCircleIcon({
width = 50,
height = 50,
...props
}: AlertCircleIconProps) {
return (
<svg
width={width}
height={height}
viewBox="0 0 50 50"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M24.9994 45.8337C36.5053 45.8337 45.8327 36.5063 45.8327 25.0003C45.8327 13.4944 36.5053 4.16699 24.9994 4.16699C13.4934 4.16699 4.16602 13.4944 4.16602 25.0003C4.16602 36.5063 13.4934 45.8337 24.9994 45.8337Z"
stroke="#00A991"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M25 16.667V26.667"
stroke="#00A991"
strokeWidth="3"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M25 33.333H25.0208"
stroke="#00A991"
strokeWidth="3"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}

export default AlertCircleIcon;
1 change: 1 addition & 0 deletions public/icons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ export { default as OnlineIcon } from './OnlineIcon';
export { default as MessageIcon } from './MessageIcon';
export { default as PencilIcon } from './PencilIcon';
export { default as IcCheckOnly } from './IcCheckOnly';
export { default as AlertCircleIcon } from './AlertCircleIcon';
Binary file added public/images/errorImage.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 9 additions & 2 deletions src/api/auth/react-query/customHooks.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useMutation } from '@tanstack/react-query';
import { showToast } from '@/components/toast/toast';
import { TOAST_MESSAGES } from '@/constants/messages/toast';
import { authClientAPI } from '../authClientAPI';
import { getUserInfo } from '@/features/auth/api/auth';

Expand All @@ -9,10 +10,16 @@ export function useEditInfoMutation() {
mutationFn: (formData: FormData) => authClientAPI.editInfo(formData),
onSuccess: () => {
getUserInfo();
showToast({ message: '프로필 수정이 완료되었습니다.', type: 'success' });
showToast({
message: TOAST_MESSAGES.SUCCESS.PROFILE_EDIT,
type: 'success',
});
},
onError: (error) => {
showToast({ message: '프로필 수정을 실패하였습니다', type: 'error' });
showToast({
message: TOAST_MESSAGES.ERROR.PROFILE_EDIT_FAILED,
type: 'error',
});
console.error(error);
},
});
Expand Down
16 changes: 13 additions & 3 deletions src/api/book-club/react-query/customHooks.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { bookClubs } from './queries';
import { showToast } from '@/components/toast/toast';
import { TOAST_MESSAGES } from '@/constants/messages/toast';
import {
bookClubLikeAPI,
bookClubMainAPI,
Expand All @@ -25,7 +26,10 @@ export function useBookClubCreateMutation() {
});
},
onError: () => {
showToast({ message: '북클럽 생성에 실패했습니다.', type: 'error' });
showToast({
message: TOAST_MESSAGES.ERROR.CLUB_CREATE_FAILED,
type: 'error',
});
},
});
}
Expand Down Expand Up @@ -74,12 +78,18 @@ export function useWriteReview() {
queryClient.invalidateQueries({
queryKey: bookClubs.my()._ctx.reviews().queryKey,
});
showToast({ message: '리뷰 작성을 완료하였습니다', type: 'success' });
showToast({
message: TOAST_MESSAGES.SUCCESS.REVIEW_CREATE,
type: 'success',
});
},
onError: (error) => {
console.error(error);

showToast({ message: '리뷰 작성을 실패하였습니다.', type: 'error' });
showToast({
message: TOAST_MESSAGES.ERROR.REVIEW_CREATE_FAILED,
type: 'error',
});
},
});
}
Expand Down
20 changes: 20 additions & 0 deletions src/app/bookclub/create/error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use client';

import ErrorTemplate from '@/components/error/ErrorTemplate';

export default function BookClubCreateError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<ErrorTemplate
error={error}
reset={reset}
title="북클럽 생성 오류"
message="북클럽 생성 페이지 정보를 불러오던 중 에러가 발생했습니다."
/>
);
}
Loading