Skip to content

Commit

Permalink
FE-27 🔀 브랜치 최신화 (#99)
Browse files Browse the repository at this point in the history
* ➕ 이미지 파일 추가

* 💄 로그인 페이지 레이아웃 생성

* 💄 로그인 페이지 UI 생성 및 반응형 디자인 구현

* FE-60 ✨ react hook form, zod 추가

* FE-60 💄 로그인 폼 스타일 수정

- 텍스트 인풋 테두리
- 로그인 버튼

* FE-60 ♻️ 로그인 스키마 분리

* ✨ 로그인 응답 데이터 스키마 정의

* ✨ 로그인 api 생성

* ✨ 요청과 응답에 관한 인터셉터 추가

* ✨ useSignin mutation hook 생성

* ⚡ useSignin hook 로그인 폼에 적용

* 💄 회원가입 페이지 레이아웃 추가

* 💄 간편 로그인 로고 추가

* 💄 회원가입 ui 추가

* ✨ 회원가입 스키마 정의

* ➕ 회원가입 페이지에 스키마 적용

* 💄 에러 메시지 뜰 때 라벨, 인풋도 같은 에러 색깔 추가

* 📝 유효성 검사를 통한 버튼의 비활성화 처리

* 📝 유효성 검사에 따른 인풋 테두리 색상 처리

* 🔥 AuthLayout 삭제

* 🎨 회원가입 페이지 브라우저 확대시 ui 깨짐 수정

* 🚚 정규표현식 네이밍 변경

* 🔥 AuthLayout 삭제

* 🎨 onSubmit 함수 인라인으로 정의

* ♻️ 응답 인터셉터의 에러 처리 및 토큰 갱신 로직 개선

* ♻️ postSignin api 에러처리 로직 삭제

* 🔥 useSignin hook 삭제

* 🚚 useSigninMutation hook으로 이름 변경 및 파일 이동

* ✨ Toaster 컴포넌트 추가

* ✨ toast로 에러메시지 띄우기

* ✨ 회원가입 응답 데이터 스키마 정의

* ✨ 회원가입 api 생성

* ✨ useRegisterMutation hook 생성

* ⚡ 회원가입 폼에 mutaion hook 적용

* ✨ Toaster 컴포넌트 추가

* ✨ toast로 에러메시지 띄우기

* ⚡ isAxiosError로 변경

* FE-29 🔀 로그인 페이지 머지 요청 (#39)

* ➕ 이미지 파일 추가

* 💄 로그인 페이지 레이아웃 생성

* 💄 로그인 페이지 UI 생성 및 반응형 디자인 구현

* FE-60 ✨ react hook form, zod 추가

* FE-60 💄 로그인 폼 스타일 수정

- 텍스트 인풋 테두리
- 로그인 버튼

* FE-60 ♻️ 로그인 스키마 분리

* ✨ 로그인 응답 데이터 스키마 정의

* ✨ 로그인 api 생성

* ✨ 요청과 응답에 관한 인터셉터 추가

* ✨ useSignin mutation hook 생성

* ⚡ useSignin hook 로그인 폼에 적용

* 🔥 AuthLayout 삭제

* 🎨 onSubmit 함수 인라인으로 정의

* ♻️ 응답 인터셉터의 에러 처리 및 토큰 갱신 로직 개선

* ♻️ postSignin api 에러처리 로직 삭제

* 🔥 useSignin hook 삭제

* 🚚 useSigninMutation hook으로 이름 변경 및 파일 이동

* ✨ Toaster 컴포넌트 추가

* ✨ toast로 에러메시지 띄우기

* 🔀 충돌 해결

* 🐛 postSignup 함수 추가

* 🐛 postSignin 내보내는 방식 수정

* 🔧 lint 수정

* ✨ oauth api 생성

* FE-71 🔀 에피그램 작성 페이지 (#71)

* FE-64💄 글작성 페이지 UI추가 (#44)

* FE-72 ✨ 에피그램 등록 api연동 (#52)

* FE-72✨ 글작성페이지 스키마 추가

* FE-72✨ form태그 Form컴포넌트로 변경

* FE-72✨ 태그 저장기능 추가

* FE-72✨ 에피그램 등록 api연동

* FE-72✨ 에피그램 등록시 해당 에피그램 페이지로 이동 기능 추가

* FE-72✨ 등록 중일때의 로직추가

* FE-72✨  toast-> alert-dailog로 변경

* FE-72📝 TODO주석 추가

---------

Co-authored-by: 우지석 <[email protected]>

* FE-73✨ 유효성검사 추가 (#66)

* FE-73♻️  Tag관리 함수 훅으로 분리

* FE-73✨  RadioGroup 로직 수정

* FE-73✨ 유효성검사 추가

* FE-73♻️  저자 본인 선택시의  로직 변경

* FE-73✨ 중복 태그 검사 로직 추가

* FE-73♻️ 출처 유효성(optional)검사 수정

* FE-73✨  필수항목 입력했을때 버튼 활성화

* FE-73🐛 태그를 입력했다가 지웠을때 버튼 활성화되있는 버그 수정

* FE-73🐛 useEffect 의존성배열 lint problem 해결

* FE-73🐛 url유효성검사 에러 메세지 안뜨는 버그 수정

---------

Co-authored-by: 우지석 <[email protected]>

* FE-71♻️ epic브랜치 코드리뷰 반영 (#76)

* FE-71♻️  token,interceptor 로직 수정

* FE-71♻️  AddEpigram 코드리뷰 반영

* FE-71🔥 테스트용 상세페이지 삭제

* FE-71♻️  onKeyDown -> onKeyUp 수정

---------

Co-authored-by: 우지석 <[email protected]>

* ✨ 카카오톡 리디렉트 uri 설정

* ✨ useKakaoLogin mutation hook 생성

* ⚡ 네이버 구글 카카오 간편 로그인 링크 설정

* ♻️ 에러처리 로직 수정

* FE-51 🔀 공용 API 머지 요청 (#92)

* FE-52  ✨에피그램 목록조회 API (#34)

* FE-52 feat: api schema 작성

* FE-52 ✨feat: getEpigrams api 작성

* FE-522 ✨fix:  default export로 변경

* FE-52 ✨test: 테스트 코드 작성

* FE-52 ✨feat: BaseUrl, TeamID 상수 추가 및 axios baseTRL 수정

* FE-52 ✨fix: schema 및 apis 파일 수정

* FE-52 ✨test:  테스트 코드 삭제

* FE-52 ✨fix: .env파일 생성 및 BaseURL 수정

* FE-52 ✨fix: limit 타입  수정(optional 삭제)

* FE-52 ✨text: 테 테스트코드 삭제

* FE-52 ✨fix: api GET요청 주소 수정('epigrams' -> '/epigrams')

* FE-53 ✨ 감정이모티콘 저장 스키마 정의

* FE-53 ✨ 오늘의 감정 저장 api 생성

* FE-53 ✨ getMe 함수를 사용해 로그인 상태 확인 기능 구현

* FE-53 ✨ 감정 한영 변환 함수

* FE-53 ✨ 감정 저장 후 토스트 알림 표시

* FE-53 ✨ 오늘의 감정 조회 api 생성

* FE-53 🔨 감정 한영 변환 함수 추가

+) post, get 함수 내부로 한영 변환 함수 이동

* FE-53 ✨ 오늘의 감정 스키마 추가 정의

* FE-53 ✨ 오늘의 감정 조회 함수 적용

* FE-53 🚚 오늘의 감정 type 이름 변경

* FE-53 ✨ useMutation 훅 사용

* FE-53 📝 EmotionSelector 주석 추가

* FE-53 🔥 api 함수 에러 처리 부분 제거

* FE-53 🔨 useQuery를 사용해 오늘의 감정 조회

데이터를 조회할 때는 useQuery를 사용하는거라 함

* FE-56 ✨ 댓글 수정 API (#84)

* FE-29 🔀 로그인 페이지 머지 요청 (#39)

* ➕ 이미지 파일 추가

* 💄 로그인 페이지 레이아웃 생성

* 💄 로그인 페이지 UI 생성 및 반응형 디자인 구현

* FE-60 ✨ react hook form, zod 추가

* FE-60 💄 로그인 폼 스타일 수정

- 텍스트 인풋 테두리
- 로그인 버튼

* FE-60 ♻️ 로그인 스키마 분리

* ✨ 로그인 응답 데이터 스키마 정의

* ✨ 로그인 api 생성

* ✨ 요청과 응답에 관한 인터셉터 추가

* ✨ useSignin mutation hook 생성

* ⚡ useSignin hook 로그인 폼에 적용

* 🔥 AuthLayout 삭제

* 🎨 onSubmit 함수 인라인으로 정의

* ♻️ 응답 인터셉터의 에러 처리 및 토큰 갱신 로직 개선

* ♻️ postSignin api 에러처리 로직 삭제

* 🔥 useSignin hook 삭제

* 🚚 useSigninMutation hook으로 이름 변경 및 파일 이동

* ✨ Toaster 컴포넌트 추가

* ✨ toast로 에러메시지 띄우기

* FE-71 🔀 에피그램 작성 페이지 (#71)

* FE-64💄 글작성 페이지 UI추가 (#44)

* FE-72 ✨ 에피그램 등록 api연동 (#52)

* FE-72✨ 글작성페이지 스키마 추가

* FE-72✨ form태그 Form컴포넌트로 변경

* FE-72✨ 태그 저장기능 추가

* FE-72✨ 에피그램 등록 api연동

* FE-72✨ 에피그램 등록시 해당 에피그램 페이지로 이동 기능 추가

* FE-72✨ 등록 중일때의 로직추가

* FE-72✨  toast-> alert-dailog로 변경

* FE-72📝 TODO주석 추가

---------

Co-authored-by: 우지석 <[email protected]>

* FE-73✨ 유효성검사 추가 (#66)

* FE-73♻️  Tag관리 함수 훅으로 분리

* FE-73✨  RadioGroup 로직 수정

* FE-73✨ 유효성검사 추가

* FE-73♻️  저자 본인 선택시의  로직 변경

* FE-73✨ 중복 태그 검사 로직 추가

* FE-73♻️ 출처 유효성(optional)검사 수정

* FE-73✨  필수항목 입력했을때 버튼 활성화

* FE-73🐛 태그를 입력했다가 지웠을때 버튼 활성화되있는 버그 수정

* FE-73🐛 useEffect 의존성배열 lint problem 해결

* FE-73🐛 url유효성검사 에러 메세지 안뜨는 버그 수정

---------

Co-authored-by: 우지석 <[email protected]>

* FE-71♻️ epic브랜치 코드리뷰 반영 (#76)

* FE-71♻️  token,interceptor 로직 수정

* FE-71♻️  AddEpigram 코드리뷰 반영

* FE-71🔥 테스트용 상세페이지 삭제

* FE-71♻️  onKeyDown -> onKeyUp 수정

---------

Co-authored-by: 우지석 <[email protected]>

* FE-56 ✨ 댓글 수정 API

---------

Co-authored-by: MOON <[email protected]>
Co-authored-by: Jiseok Woo <[email protected]>
Co-authored-by: 우지석 <[email protected]>

* FE-57 ✨ 댓글 삭제 API (#88)

* FE-51 🔀 공용 API 최신화 (#93)

* FE-29 🔀 로그인 페이지 머지 요청 (#39)

* ➕ 이미지 파일 추가

* 💄 로그인 페이지 레이아웃 생성

* 💄 로그인 페이지 UI 생성 및 반응형 디자인 구현

* FE-60 ✨ react hook form, zod 추가

* FE-60 💄 로그인 폼 스타일 수정

- 텍스트 인풋 테두리
- 로그인 버튼

* FE-60 ♻️ 로그인 스키마 분리

* ✨ 로그인 응답 데이터 스키마 정의

* ✨ 로그인 api 생성

* ✨ 요청과 응답에 관한 인터셉터 추가

* ✨ useSignin mutation hook 생성

* ⚡ useSignin hook 로그인 폼에 적용

* 🔥 AuthLayout 삭제

* 🎨 onSubmit 함수 인라인으로 정의

* ♻️ 응답 인터셉터의 에러 처리 및 토큰 갱신 로직 개선

* ♻️ postSignin api 에러처리 로직 삭제

* 🔥 useSignin hook 삭제

* 🚚 useSigninMutation hook으로 이름 변경 및 파일 이동

* ✨ Toaster 컴포넌트 추가

* ✨ toast로 에러메시지 띄우기

* FE-71 🔀 에피그램 작성 페이지 (#71)

* FE-64💄 글작성 페이지 UI추가 (#44)

* FE-72 ✨ 에피그램 등록 api연동 (#52)

* FE-72✨ 글작성페이지 스키마 추가

* FE-72✨ form태그 Form컴포넌트로 변경

* FE-72✨ 태그 저장기능 추가

* FE-72✨ 에피그램 등록 api연동

* FE-72✨ 에피그램 등록시 해당 에피그램 페이지로 이동 기능 추가

* FE-72✨ 등록 중일때의 로직추가

* FE-72✨  toast-> alert-dailog로 변경

* FE-72📝 TODO주석 추가

---------

Co-authored-by: 우지석 <[email protected]>

* FE-73✨ 유효성검사 추가 (#66)

* FE-73♻️  Tag관리 함수 훅으로 분리

* FE-73✨  RadioGroup 로직 수정

* FE-73✨ 유효성검사 추가

* FE-73♻️  저자 본인 선택시의  로직 변경

* FE-73✨ 중복 태그 검사 로직 추가

* FE-73♻️ 출처 유효성(optional)검사 수정

* FE-73✨  필수항목 입력했을때 버튼 활성화

* FE-73🐛 태그를 입력했다가 지웠을때 버튼 활성화되있는 버그 수정

* FE-73🐛 useEffect 의존성배열 lint problem 해결

* FE-73🐛 url유효성검사 에러 메세지 안뜨는 버그 수정

---------

Co-authored-by: 우지석 <[email protected]>

* FE-71♻️ epic브랜치 코드리뷰 반영 (#76)

* FE-71♻️  token,interceptor 로직 수정

* FE-71♻️  AddEpigram 코드리뷰 반영

* FE-71🔥 테스트용 상세페이지 삭제

* FE-71♻️  onKeyDown -> onKeyUp 수정

---------

Co-authored-by: 우지석 <[email protected]>

---------

Co-authored-by: MOON <[email protected]>
Co-authored-by: Jiseok Woo <[email protected]>
Co-authored-by: 우지석 <[email protected]>

* FE-51 🔀 공용 API 최신화 (충돌수정) (#98)

---------

Co-authored-by: imsoohyeok <[email protected]>
Co-authored-by: NEWJIN <[email protected]>
Co-authored-by: NEWJIN <[email protected]>
Co-authored-by: MOON <[email protected]>
Co-authored-by: Jiseok Woo <[email protected]>
Co-authored-by: 우지석 <[email protected]>

* FE-27 🔨 충돌오류 수정

* FE-27 🔨 충돌내역 수정

* FE-27 🔀 충돌오류 수정

---------

Co-authored-by: MOON <[email protected]>
Co-authored-by: MOON <[email protected]>
Co-authored-by: Jiseok Woo <[email protected]>
Co-authored-by: 우지석 <[email protected]>
Co-authored-by: imsoohyeok <[email protected]>
Co-authored-by: NEWJIN <[email protected]>
Co-authored-by: NEWJIN <[email protected]>
  • Loading branch information
8 people authored Jul 30, 2024
1 parent 569f2e0 commit 0ac6c7f
Show file tree
Hide file tree
Showing 36 changed files with 860 additions and 69 deletions.
9 changes: 6 additions & 3 deletions src/apis/auth.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import type { PostSigninRequestType, PostSigninResponseType } from '@/schema/auth';
import type { PostSigninRequestType, PostSigninResponseType, PostSignUpRequestType, PostSignUpResponseType } from '@/schema/auth';
import httpClient from '.';

const postSignin = async (request: PostSigninRequestType): Promise<PostSigninResponseType> => {
export const postSignin = async (request: PostSigninRequestType): Promise<PostSigninResponseType> => {
const response = await httpClient.post('/auth/signIn', request);
return response.data;
};

export default postSignin;
export const postSignup = async (request: PostSignUpRequestType): Promise<PostSignUpResponseType> => {
const response = await httpClient.post('/auth/signUp', request);
return response.data;
};
46 changes: 46 additions & 0 deletions src/apis/epigramComment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import httpClient from '@/apis/index';
import { CommentRequestSchema, CommentRequestType, CommentResponseSchema, CommentResponseType } from '@/schema/comment';
import { PostCommentRequest, PatchCommentRequest } from '@/types/epigram.types';

export const getEpigramComments = async (params: CommentRequestType): Promise<CommentResponseType> => {
try {
// 요청 파라미터 유효성 검사
const validatedParams = CommentRequestSchema.parse(params);

const { id, limit, cursor } = validatedParams;

// NOTE: URL의 쿼리 문자열을 사용
// NOTE : cursor값이 있다면 ?limit=3&cursor=100, 없다면 ?limit=3,(숫자는 임의로 지정한 것)
const queryParams = new URLSearchParams({
limit: limit.toString(),
...(cursor !== undefined && { cursor: cursor.toString() }),
});

const response = await httpClient.get<CommentResponseType>(`/epigrams/${id}/comments?${queryParams.toString()}`);

// 응답 데이터 유효성 검사
const validatedData = CommentResponseSchema.parse(response.data);

return validatedData;
} catch (error) {
if (error instanceof Error) {
throw new Error(`댓글을 불러오는데 실패했습니다: ${error.message}`);
}
throw error;
}
};

export const postComment = async (commentData: PostCommentRequest) => {
const response = await httpClient.post('/comments', commentData);
return response.data;
};

export const patchComment = async (commentId: number, commentData: PatchCommentRequest) => {
const response = await httpClient.patch(`/comments/${commentId}`, commentData);
return response.data;
};

export const deleteComment = async (commentId: number) => {
const response = await httpClient.delete(`/comments/${commentId}`);
return response.data;
};
25 changes: 25 additions & 0 deletions src/apis/getEmotion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { EmotionType } from '@/types/emotion';
import type { GetEmotionResponseType } from '@/schema/emotion';
import { translateEmotionToKorean } from '@/utils/emotionMap';
import httpClient from '.';
import { getMe } from './user';

const getEmotion = async (): Promise<EmotionType | null> => {
const user = await getMe();
if (!user) {
throw new Error('로그인이 필요합니다.');
}

const response = await httpClient.get<GetEmotionResponseType>('/emotionLogs/today', {
params: { userId: user.id },
});

if (response.status === 204) {
return null; // No content
}

const koreanEmotion = translateEmotionToKorean(response.data.emotion);
return koreanEmotion;
};

export default getEmotion;
12 changes: 12 additions & 0 deletions src/apis/getEpigrams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { GetEpigramsParamsType, GetEpigramsResponseType, GetEpigramsResponse } from '@/schema/epigrams';
import httpClient from '.';

const getEpigrams = async (params: GetEpigramsParamsType): Promise<GetEpigramsResponseType> => {
const response = await httpClient.get(`/epigrams`, { params });

// 데이터 일치하는지 확인
const parsedResponse = GetEpigramsResponse.parse(response.data);
return parsedResponse;
};

export default getEpigrams;
42 changes: 42 additions & 0 deletions src/apis/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,45 @@ httpClient.interceptors.response.use(
);

export default httpClient;

// NOTE: eslint-disable no-param-reassign 미해결로 인한 설정
httpClient.interceptors.request.use((config) => {
const accessToken = localStorage.getItem('accessToken');
/* eslint-disable no-param-reassign */
if (accessToken) config.headers.Authorization = `Bearer ${accessToken}`;
/* eslint-enable no-param-reassign */
return config;
});

httpClient.interceptors.response.use(
(response) => response,

(error) => {
if (error.response && error.response.status === 401) {
const refreshToken = localStorage.getItem('refreshToken');

if (!refreshToken) {
window.location.href = '/auth/SignIn';
return Promise.reject(error);
}

return httpClient
.post('/auth/refresh-token', null, {
headers: { Authorization: `Bearer ${refreshToken}` },
})
.then((response) => {
const { accessToken, refreshToken: newRefreshToken } = response.data;
localStorage.setItem('accessToken', accessToken);
localStorage.setItem('refreshToken', newRefreshToken);

const originalRequest = error.config;
return httpClient(originalRequest);
})
.catch(() => {
window.location.href = '/auth/SignIn';
return Promise.reject(error);
});
}
return Promise.reject(error);
},
);
11 changes: 11 additions & 0 deletions src/apis/oauth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import axios from 'axios';

const postOauth = async (code: string) => {
const response = await axios.post(`${process.env.NEXT_PUBLIC_BASE_URL}/auth/signIn/KAKAO`, {
redirectUri: process.env.NEXT_PUBLIC_REDIRECT_URI,
token: code,
});
return response.data;
};

export default postOauth;
24 changes: 24 additions & 0 deletions src/apis/postEmotion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { EmotionType } from '@/types/emotion';
import type { PostEmotionRequestType, PostEmotionResponseType } from '@/schema/emotion';
import { translateEmotionToEnglish } from '@/utils/emotionMap';
import httpClient from '.';
import { getMe } from './user';

const postEmotion = async (emotion: EmotionType): Promise<PostEmotionResponseType> => {
const user = await getMe();
if (!user) {
throw new Error('로그인이 필요합니다.');
}

const englishEmotion = translateEmotionToEnglish(emotion);
const request: PostEmotionRequestType = { emotion: englishEmotion };

const response = await httpClient.post<PostEmotionResponseType>('/emotionLogs/today', {
...request,
userId: user.id,
});

return response.data;
};

export default postEmotion;
8 changes: 4 additions & 4 deletions src/apis/user.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import type { GetUserReponseType, GetUserRequestType, PatchMeRequestType, PostPresignedUrlRequestType, PostPresignedUrlResponseType } from '@/schema/user';
import type { GetUserResponseType, GetUserRequestType, PatchMeRequestType, PostPresignedUrlRequestType, PostPresignedUrlResponseType } from '@/schema/user';

import httpClient from '.';

export const getMe = async (): Promise<GetUserReponseType> => {
export const getMe = async (): Promise<GetUserResponseType> => {
const response = await httpClient.get('/users/me');
return response.data;
};

export const getUser = async (request: GetUserRequestType): Promise<GetUserReponseType> => {
export const getUser = async (request: GetUserRequestType): Promise<GetUserResponseType> => {
const { id } = request;
const response = await httpClient.get(`/users/${id}`);
return response.data;
};

export const updateMe = async (request: PatchMeRequestType): Promise<GetUserReponseType> => {
export const updateMe = async (request: PatchMeRequestType): Promise<GetUserResponseType> => {
const response = await httpClient.patch('/users/me', { ...request });
return response.data;
};
Expand Down
2 changes: 1 addition & 1 deletion src/components/Emotion/EmotionCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import React from 'react';
import cn from '@/lib/utils';
import Image from 'next/image';
import { EmotionIconCardProps } from '@/types/EmotionTypes';
import { EmotionIconCardProps } from '@/types/emotion';

// 아이콘 파일 경로 매핑
const iconPaths = {
Expand Down
34 changes: 34 additions & 0 deletions src/components/Emotion/EmotionSaveToast.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* 오늘의 감정을 선택하면 표시되는 toast입니다.
* 감정을 확인하기 위해 마이페이지로 연결됩니다.
*/

import React, { useEffect } from 'react';
import { useToast } from '@/components/ui/use-toast';
import { ToastAction } from '@/components/ui/toast';
import { useRouter } from 'next/router';

interface EmotionSaveToastProps {
iconType: string;
}

function EmotionSaveToast({ iconType }: EmotionSaveToastProps) {
const { toast } = useToast();
const router = useRouter();

useEffect(() => {
toast({
title: '오늘의 감정이 저장되었습니다.',
description: `오늘의 감정: ${iconType}`,
action: (
<ToastAction altText='확인하기' onClick={() => router.push('/mypage')}>
확인하기
</ToastAction>
),
});
}, [iconType, toast, router]);

return null;
}

export default EmotionSaveToast;
81 changes: 63 additions & 18 deletions src/components/Emotion/EmotionSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
/*
여러 개의 EmotionIconCard를 관리합니다.
사용자 인터페이스에 필요한 상호 작용 로직을 포함합니다.
*/

import React, { useState } from 'react';
import EmotionIconCard from '@/components/Emotion/EmotionCard';
import React, { useState, useEffect } from 'react';
import useMediaQuery from '@/hooks/useMediaQuery';
import { EmotionType, EmotionState } from '@/types/EmotionTypes';
import EmotionIconCard from '@/components/Emotion/EmotionCard';
import { EmotionType, EmotionState } from '@/types/emotion';
import usePostEmotion from '@/hooks/usePostEmotion';
import { useGetEmotion } from '@/hooks/useGetEmotion';
import EmotionSaveToast from './EmotionSaveToast';

// EmotionSelector 컴포넌트 함수 선언
/**
* EmotionSelector 컴포넌트는 여러 개의 EmotionIconCard를 관리하고
* 사용자의 오늘의 감정을 선택하고 저장하고 출력합니다.
*/
function EmotionSelector() {
// 반응형 디자인을 위한 미디어 쿼리 훅
const isTablet = useMediaQuery('(min-width: 768px) and (max-width: 1024px)');
const isMobile = useMediaQuery('(max-width: 767px)');

// 감정 카드 상태 관리
// 감정 카드 상태 관리를 위한 useState 훅
const [states, setStates] = useState<Record<EmotionType, EmotionState>>({
감동: 'Default',
기쁨: 'Default',
Expand All @@ -22,13 +24,37 @@ function EmotionSelector() {
분노: 'Default',
});

// 감정 카드 클릭 핸들러
const handleCardClick = (iconType: EmotionType) => {
// 현재 선택된 감정을 관리하는 useState 훅
const [selectedEmotion, setSelectedEmotion] = useState<EmotionType | null>(null);
// 오늘의 감정을 조회하기 위한 훅
const { data: emotion, error: getError, isLoading: isGetLoading } = useGetEmotion();
// 감정을 저장하기 위한 훅
const postEmotionMutation = usePostEmotion();

// 컴포넌트가 마운트될 때 한 번만 실행되는 useEffect 훅
// 오늘의 감정을 조회하고 상태를 업데이트합니다.
useEffect(() => {
if (emotion) {
setStates((prevStates) => ({
...prevStates,
[emotion]: 'Clicked',
}));
}
}, [emotion]);

/**
* 감정 카드 클릭 핸들러
* 사용자가 감정 카드를 클릭했을 때 호출됩니다.
* 클릭된 감정 카드를 'Clicked' 상태로 설정하고 나머지 카드는 'Unclicked' 상태로 설정합니다.
* 감정을 서버에 저장합니다.
* @param iconType - 클릭된 감정의 타입
*/
const handleCardClick = async (iconType: EmotionType) => {
setStates((prevStates) => {
const newStates = { ...prevStates };

if (prevStates[iconType] === 'Clicked') {
// 현재 클릭된 카드가 다시 클릭되면 모두 Default로 설정
// 현재 클릭된 카드가 다시 클릭되면 모든 카드를 Default로 설정
Object.keys(newStates).forEach((key) => {
newStates[key as EmotionType] = 'Default';
});
Expand All @@ -41,8 +67,20 @@ function EmotionSelector() {

return newStates;
});

// 오늘의 감정 저장
postEmotionMutation.mutate(iconType, {
onSuccess: (_, clickedIconType) => {
setSelectedEmotion(clickedIconType);
},
onError: (error: unknown) => {
// eslint-disable-next-line
console.error(error);
},
});
};

// 반응형 디자인을 위한 카드 크기 설정
let containerClass = 'w-[544px] h-[136px] gap-4';
let cardSize: 'lg' | 'md' | 'sm' = 'lg';

Expand All @@ -54,12 +92,19 @@ function EmotionSelector() {
cardSize = 'sm';
}

if (isGetLoading) return <p>Loading...</p>;
if (getError) return <p>{getError.message}</p>;

return (
<div className={`justify-start items-start inline-flex ${containerClass}`}>
{(['감동', '기쁨', '고민', '슬픔', '분노'] as const).map((iconType) => (
<EmotionIconCard key={iconType} iconType={iconType} size={cardSize} state={states[iconType]} onClick={() => handleCardClick(iconType)} />
))}
</div>
<>
<div className={`justify-start items-start inline-flex ${containerClass}`}>
{(['감동', '기쁨', '고민', '슬픔', '분노'] as const).map((iconType) => (
<EmotionIconCard key={iconType} iconType={iconType} size={cardSize} state={states[iconType]} onClick={() => handleCardClick(iconType)} />
))}
</div>
{/* 감정이 선택되었을 때 토스트 메시지 표시 */}
{selectedEmotion && <EmotionSaveToast iconType={selectedEmotion} />}
</>
);
}

Expand Down
Loading

0 comments on commit 0ac6c7f

Please sign in to comment.