Skip to content
Open
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
61 changes: 61 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,62 @@
# Emotional-Core

## 프로젝트

감성코어는 사용자가 시, 소설, 웹툰과 같은 창작물을 업로드하고, 댓글과 북마크 등으로 소통할 수 있는 창작물 커뮤니티 웹 서비스입니다.

## Team Member(FE)

| 박지원(Leader) | 김선진 | 이현지 |
| ------------------------------------------ | -------------------------------------- | ---------------------------------------- |
| [@PJW980921](https://github.com/PJW980921) | [@SJ-1220](https://github.com/SJ-1220) | [@Hyunji0012](https://github.com/za0012) |

백엔드 3명, 프론트엔드 3명, 디자이너 2명으로 구성된 협업 프로젝트입니다.

## 프로젝트 개요

게시판 형식을 활용한 창작물 공유 커뮤니티 사이트 개발

- **이름**: 감성코어(Emotional-Core)
- **형식**: 게시판 기반 창작물 공유 커뮤니티
- **기획/디자인**: Figma
- **배포**: Vercel(총 3차 중 2차 배포 완료 기준 작성)

## 기술 스택

- **Frontend**: React, TypeScript, Tailwind CSS
- **Backend**: Spring Boot, JWT 기반 인증, Swagger
- **Deployment**: Vercel (FE), ?? (BE)
- **협업 툴**: GitHub, Notion, Figma

## 주요 기능

1. **회원 기능**

- 일반 로그인 / 회원가입 (React Hook Form)
- 소셜 로그인: Google, Kakao, Naver
- JWT 기반 인증 (AccessToken: Body / RefreshToken: Cookie)
- 마이페이지: 회원 정보 수정, 탈퇴 기능

2. **게시판 기능**

- 카테고리별 게시판 제공: 시 / 소설 / 웹툰
- 작품 등록: 제목, 이미지, 카테고리, 장르, 내용
- 장르 분류: 전체, 일상, 에세이, 판타지, 로맨스, 공포, SF, 스포츠, 기타
- 댓글, 좋아요, 북마크 기능

3. **검색 & 추천**

- 검색: 작가명 / 작품명 기준 검색
- 추천 콘텐츠: ??

4. **메인 페이지 구성**

- 게시판 이동 탭
- 이달의 인기 작품 (글/시/웹툰 각 3개)
- 이달의 우수 작가 (총 3명, 한줄 소개 및 대표작)
- 인기 Best 작품 TOP 5
- 추천 콘텐츠: 시(3), 소설(3), 웹툰(6)

## 프로젝트 구조

## 프로젝트 수행 결과(영상)
4 changes: 2 additions & 2 deletions src/app/(sub pages)/(social login)/auth/oauth2/kakao/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ const KakaoPage = () => {

if (res.ok) {
// accessToken 받기
const { accessToken } = await res.json();
localStorage.setItem('accessToken', accessToken);
const { access_token } = await res.json();
localStorage.setItem('accessToken', access_token);
router.push('/');
} else {
console.error('토큰 요청 실패');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ const GooglePage = () => {

if (res.ok) {
// accessToken 받기
const { accessToken } = await res.json();
localStorage.setItem('accessToken', accessToken);
const { access_token } = await res.json();
localStorage.setItem('accessToken', access_token);
router.push('/');
} else {
console.error('토큰 요청 실패');
Expand Down
4 changes: 2 additions & 2 deletions src/app/(sub pages)/(social login)/signin/naver/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ const NaverPage = () => {

if (res.ok) {
// accessToken 받기
const { accessToken } = await res.json();
localStorage.setItem('accessToken', accessToken);
const { access_token } = await res.json();
localStorage.setItem('accessToken', access_token);
router.push('/');
} else {
console.error('토큰 요청 실패');
Expand Down
21 changes: 19 additions & 2 deletions src/app/_lib/axios/instance/instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,32 @@ const applyRequestInterceptor = (instance: typeof apiInstance) => {
const applyResponseInterceptor = (instance: typeof apiInstance) => {
instance.interceptors.response.use(
(response) => response,
(error) => {
async (error) => {
const originalRequest = error.config;

if (isAxiosError(error) && error.response) {
const status = error.response.status;
switch (status) {
case HttpStatusCode.BadRequest:
console.error('400: Bad Request');
break;
case HttpStatusCode.Unauthorized:
console.error('401: Unauthorized - Token may have expired');
if (!originalRequest._retry) {
originalRequest._retry = true;
console.error('401: Unauthorized - Token may have expired');

try {
const refreshToken = await apiInstance.post('/api/member/token/refresh', {}, { withCredentials: true });
console.log('refreshToken 전체 response:', refreshToken);
const newAccsessToken = refreshToken.data.accessToken;
console.log('New access token received:', newAccsessToken);
localStorage.setItem('accessToken', newAccsessToken);
originalRequest.headers.Authorization = `Bearer ${newAccsessToken}`;
return instance(originalRequest);
} catch (error) {
console.error('Token refresh failed:', error);
}
}
break;
case HttpStatusCode.Forbidden:
console.error('403: Forbidden');
Expand Down