Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FE-27 🔀 브랜치 최신화 #99

Merged
merged 83 commits into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
4a0d730
Merge pull request #11 from epigram5-9/merge/FE-29
jangmoonwon Jul 10, 2024
8ee83af
:heavy_plus_sign: 이미지 파일 추가
jangmoonwon Jul 10, 2024
f472f6f
:lipstick: 로그인 페이지 레이아웃 생성
jangmoonwon Jul 10, 2024
5b33ad7
:lipstick: 로그인 페이지 UI 생성 및 반응형 디자인 구현
jangmoonwon Jul 10, 2024
f69dd4f
Merge branch 'epic/FE-29' into feat/FE-30--signin-page-ui
jangmoonwon Jul 10, 2024
342bf76
Merge pull request #13 from epigram5-9/feat/FE-30--signin-page-ui
jangmoonwon Jul 11, 2024
c38c937
FE-60 :sparkles: react hook form, zod 추가
jangmoonwon Jul 12, 2024
03c6928
FE-60 :lipstick: 로그인 폼 스타일 수정
jangmoonwon Jul 12, 2024
8717c2f
FE-60 :recycle: 로그인 스키마 분리
jangmoonwon Jul 12, 2024
665deb9
Merge pull request #20 from epigram5-9/feat/FE-60--signin-vaildate
jangmoonwon Jul 13, 2024
3b7f965
:twisted_rightwards_arrows: Merge branch 'epic/FE-29' into merge/FE-29
jangmoonwon Jul 14, 2024
bad3c27
Merge pull request #23 from epigram5-9/merge/FE-29
jangmoonwon Jul 15, 2024
07493a9
:sparkles: 로그인 응답 데이터 스키마 정의
jangmoonwon Jul 17, 2024
10c6440
:sparkles: 로그인 api 생성
jangmoonwon Jul 17, 2024
5392384
:sparkles: 요청과 응답에 관한 인터셉터 추가
jangmoonwon Jul 17, 2024
22106ac
:sparkles: useSignin mutation hook 생성
jangmoonwon Jul 17, 2024
e4bebb9
:zap: useSignin hook 로그인 폼에 적용
jangmoonwon Jul 17, 2024
74648e7
:lipstick: 회원가입 페이지 레이아웃 추가
jangmoonwon Jul 17, 2024
8a437d1
:lipstick: 간편 로그인 로고 추가
jangmoonwon Jul 17, 2024
9537b85
:lipstick: 회원가입 ui 추가
jangmoonwon Jul 17, 2024
7da1e94
Merge pull request #36 from epigram5-9/feat/FE-63
jangmoonwon Jul 18, 2024
61098fb
Merge pull request #37 from epigram5-9/feat/FE-67
jangmoonwon Jul 18, 2024
eb34b5d
:sparkles: 회원가입 스키마 정의
jangmoonwon Jul 18, 2024
eaaf1cc
:heavy_plus_sign: 회원가입 페이지에 스키마 적용
jangmoonwon Jul 18, 2024
dfb693a
:lipstick: 에러 메시지 뜰 때 라벨, 인풋도 같은 에러 색깔 추가
jangmoonwon Jul 18, 2024
c1305fc
:memo: 유효성 검사를 통한 버튼의 비활성화 처리
jangmoonwon Jul 18, 2024
91e1fff
:memo: 유효성 검사에 따른 인풋 테두리 색상 처리
jangmoonwon Jul 18, 2024
03d62c5
Merge pull request #40 from epigram5-9/feat/FE-68
jangmoonwon Jul 19, 2024
bfd7d3b
:fire: AuthLayout 삭제
jangmoonwon Jul 19, 2024
467a76c
:art: 회원가입 페이지 브라우저 확대시 ui 깨짐 수정
jangmoonwon Jul 19, 2024
bd48061
:truck: 정규표현식 네이밍 변경
jangmoonwon Jul 19, 2024
1256f61
Merge pull request #48 from epigram5-9/fix/FE-66
jangmoonwon Jul 20, 2024
265c6e7
🔀 Merge branch 'epic/FE-29' into merge/FE-29
jangmoonwon Jul 20, 2024
9a253df
Merge pull request #50 from epigram5-9/merge/FE-29
jangmoonwon Jul 21, 2024
a81913f
:fire: AuthLayout 삭제
jangmoonwon Jul 22, 2024
d7cc6ad
:art: onSubmit 함수 인라인으로 정의
jangmoonwon Jul 22, 2024
da2ccc7
:recycle: 응답 인터셉터의 에러 처리 및 토큰 갱신 로직 개선
jangmoonwon Jul 22, 2024
9f18429
:recycle: postSignin api 에러처리 로직 삭제
jangmoonwon Jul 22, 2024
ba68251
:fire: useSignin hook 삭제
jangmoonwon Jul 22, 2024
9b205a5
:truck: useSigninMutation hook으로 이름 변경 및 파일 이동
jangmoonwon Jul 22, 2024
243e509
:sparkles: Toaster 컴포넌트 추가
jangmoonwon Jul 22, 2024
2c484ba
:sparkles: toast로 에러메시지 띄우기
jangmoonwon Jul 22, 2024
e4b2064
Merge pull request #54 from epigram5-9/fix/FE-29
jangmoonwon Jul 22, 2024
c3b2e06
:twisted_rightwards_arrows: Merge branch 'epic/FE-66' into merge/FE-66
jangmoonwon Jul 22, 2024
36f4b58
Merge pull request #55 from epigram5-9/merge/FE-66
jangmoonwon Jul 22, 2024
70d45e4
:sparkles: 회원가입 응답 데이터 스키마 정의
jangmoonwon Jul 23, 2024
f79b702
:sparkles: 회원가입 api 생성
jangmoonwon Jul 23, 2024
8ba0103
:sparkles: useRegisterMutation hook 생성
jangmoonwon Jul 23, 2024
e5efa3e
:zap: 회원가입 폼에 mutaion hook 적용
jangmoonwon Jul 23, 2024
ca394cc
:sparkles: Toaster 컴포넌트 추가
jangmoonwon Jul 23, 2024
4e9e01a
:sparkles: toast로 에러메시지 띄우기
jangmoonwon Jul 23, 2024
ea2264f
:zap: isAxiosError로 변경
jangmoonwon Jul 24, 2024
8a35cf2
Merge pull request #69 from epigram5-9/fix/FE-69
jangmoonwon Jul 24, 2024
a367f41
Merge pull request #59 from epigram5-9/feat/FE-69
jangmoonwon Jul 25, 2024
596369a
FE-29 :twisted_rightwards_arrows: 로그인 페이지 머지 요청 (#39)
jangmoonwon Jul 25, 2024
7c1f9c4
:twisted_rightwards_arrows: 메인 pr 최신화 및 confiict 수정
jangmoonwon Jul 26, 2024
5785703
:twisted_rightwards_arrows: 충돌 해결
jangmoonwon Jul 26, 2024
a715986
Merge branch 'epic/FE-66' into merge/FE-66
jangmoonwon Jul 26, 2024
9715918
:bug: postSignup 함수 추가
jangmoonwon Jul 26, 2024
0762f98
:bug: postSignin 내보내는 방식 수정
jangmoonwon Jul 26, 2024
a45fdf4
:twisted_rightwards_arrows: conflict 수정
jangmoonwon Jul 26, 2024
7e4eba6
:wrench: lint 수정
jangmoonwon Jul 26, 2024
b5227f6
Merge pull request #74 from epigram5-9/merge/FE-66
jangmoonwon Jul 26, 2024
fbe86b8
:sparkles: oauth api 생성
jangmoonwon Jul 27, 2024
4ba94c8
FE-71 🔀 에피그램 작성 페이지 (#71)
jisurk Jul 27, 2024
a8378ef
:sparkles: 카카오톡 리디렉트 uri 설정
jangmoonwon Jul 27, 2024
0039236
:sparkles: useKakaoLogin mutation hook 생성
jangmoonwon Jul 27, 2024
599efae
:zap: 네이버 구글 카카오 간편 로그인 링크 설정
jangmoonwon Jul 27, 2024
912b9ef
:twisted_rightwards_arrows: Merge branch 'epic/FE-66' into merge/FE-66
jangmoonwon Jul 27, 2024
ceaaeec
Merge pull request #87 from epigram5-9/merge/FE-66
jangmoonwon Jul 27, 2024
ae7c00e
:twisted_rightwards_arrows: Merge branch 'epic/FE-29' of into merge/F…
jangmoonwon Jul 28, 2024
192e06e
:recycle: 에러처리 로직 수정
jangmoonwon Jul 28, 2024
3322aaa
Merge pull request #95 from epigram5-9/fix/FE-66
jangmoonwon Jul 28, 2024
33faa00
Merge pull request #94 from epigram5-9/merge/FE-29
jangmoonwon Jul 28, 2024
ffd5e62
Merge branch 'epic/FE-29' into feat/FE-70
jangmoonwon Jul 28, 2024
1cc4151
Merge pull request #83 from epigram5-9/feat/FE-70
jangmoonwon Jul 28, 2024
9daf9fe
Merge pull request #72 from epigram5-9/epic/FE-66
jangmoonwon Jul 30, 2024
197e37c
Merge pull request #96 from epigram5-9/epic/FE-29
jangmoonwon Jul 30, 2024
547e7d3
FE-51 :twisted_rightwards_arrows: 공용 API 머지 요청 (#92)
JeonYumin94 Jul 30, 2024
0c7471d
Merge branch 'main' of https://github.com/epigram5-9/epigram into mer…
JeonYumin94 Jul 30, 2024
f75383f
FE-27 :hammer: 충돌오류 수정
JeonYumin94 Jul 30, 2024
1dd40d2
FE-27 :hammer: 충돌내역 수정
JeonYumin94 Jul 30, 2024
5a9d197
FE-27 :twisted_rightwards_arrows: 충돌오류 수정
JeonYumin94 Jul 30, 2024
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
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
Loading