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 ✨ 마이페이지 오늘의 감정 선택 main 머지 요청 #158

Merged
merged 25 commits into from
Aug 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
d24ecd3
FE-34 :sparkles: 마이페이지 프로필 수정 기능 (#53)
JeonYumin94 Jul 22, 2024
6d5e64b
FE-36 :sparkles: 마이페이지 캘린더 출력 함수 (#58)
JeonYumin94 Jul 23, 2024
74a85a3
FE-36 :sparkles: 마이페이지 감정달력 (#65)
JeonYumin94 Jul 24, 2024
4d3cc05
FE-27 :twisted_rightwards_arrows: 에픽 브랜치 최신화 (#81)
JeonYumin94 Jul 27, 2024
5996a59
FE-27 :twisted_rightwards_arrows: 마이페이지 에픽 브랜치 최신화 (#85)
JeonYumin94 Jul 27, 2024
10a42ad
FE-27 :twisted_rightwards_arrows: 브랜치 최신화 (#86)
JeonYumin94 Jul 27, 2024
569f2e0
FE-37 :sparkles: 마이페이지 감정차트 (#89)
JeonYumin94 Jul 28, 2024
0ac6c7f
FE-27 :twisted_rightwards_arrows: 브랜치 최신화 (#99)
JeonYumin94 Jul 30, 2024
cd9d59e
FE-27 :twisted_rightwards_arrows: 마이페이지 최신화 (#102)
JeonYumin94 Jul 30, 2024
9e4ab47
FE-27 :twisted_rightwards_arrows: 브랜치 최신화 (충돌수정) (#111)
kich555 Jul 30, 2024
bbdbbb2
Merge branch 'main' into epic/FE-27--my-page
JeonYumin94 Jul 30, 2024
37295ed
FE-27 :twisted_rightwards_arrows: 충돌에러 수정 (#113)
JeonYumin94 Jul 30, 2024
80cc38e
FE-27 :truck: 마이페이지 ui 관련 폴더 이동 (#114)
JeonYumin94 Jul 30, 2024
3685edc
FE-38 :sparkles: 마이페이지 내 에피그램 목록 조회 (#126)
JeonYumin94 Jul 30, 2024
11b67a0
FE-27 :twisted_rightwards_arrows: 브랜치 최신화 (#127)
JeonYumin94 Jul 30, 2024
dbbdf15
Merge branch 'main' into epic/FE-27--my-page
JeonYumin94 Jul 30, 2024
aa1a7b6
FE-27 :twisted_rightwards_arrows: 마이페이지 브랜치 최신화 (#130)
JeonYumin94 Jul 31, 2024
e3a8a8c
FE-27 :sparkles: 마이페이지 내 댓글목록 조회 (#137)
JeonYumin94 Jul 31, 2024
8e76899
FE-40 :sparkles: 내 댓글 수정 및 삭제 기능 (#141)
JeonYumin94 Aug 1, 2024
6f76eab
FE-27 :twisted_rightwards_arrows: 마이페이지 브랜치 최신화 (#145)
JeonYumin94 Aug 1, 2024
c0ff71f
Merge branch 'main' into epic/FE-27--my-page
JeonYumin94 Aug 1, 2024
b4b1682
FE-27 :fire: 폴더이동 후 삭제 안된 기존 파일 삭제 (#148)
JeonYumin94 Aug 1, 2024
003bdcf
FE-35 :sparkles: 마이페이지 오늘의 감정 컴포넌트 연동 (#149)
JeonYumin94 Aug 2, 2024
3009fa4
FE-35 :recycle: 마이페이지 내 에피그램/댓글 선택 버튼컴포넌트로 변경 (#177)
JeonYumin94 Aug 3, 2024
9e90eca
Merge branch 'main' into epic/FE-27--my-page
JeonYumin94 Aug 3, 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
8 changes: 8 additions & 0 deletions src/apis/epigramComment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ export const getEpigramComments = async (params: CommentRequestType): Promise<Co
}
};

export const getMyEpigramComments = async (params: CommentRequestType): Promise<CommentResponseType> => {
const { id, ...restParams } = params;
const response = await httpClient.get(`/users/${id}/comments`, {
params: restParams,
});
return response.data;
};

export const postComment = async (commentData: PostCommentRequest) => {
const response = await httpClient.post('/comments', commentData);
return response.data;
Expand Down
12 changes: 10 additions & 2 deletions src/apis/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { EpigramRequestType } from '@/schema/epigram';
import { CommentRequestType } from '@/schema/comment';
import { GetMonthlyEmotionLogsRequestType } from '@/schema/emotion';
import { GetEpigramsParamsType } from '@/schema/epigrams';
import { getMe, getUser } from './user';
import { getMe, getUser, getMyContentCount } from './user';
import { getEpigram } from './epigram';
import { getEpigramComments } from './epigramComment';
import { getEpigramComments, getMyEpigramComments } from './epigramComment';
import getMonthlyEmotionLogs from './emotion';
import getEpigrams from './getEpigrams';

Expand All @@ -20,6 +20,10 @@ const queries = createQueryKeyStore({
queryKey: [request],
queryFn: () => getUser(request),
}),
getMyContentCount: (request: GetUserRequestType) => ({
queryKey: ['getMyContentCount', request],
queryFn: () => getMyContentCount(request),
}),
},
// NOTE: Epigram 관련 query함수
epigram: {
Expand All @@ -39,6 +43,10 @@ const queries = createQueryKeyStore({
queryKey: ['epigramComments', request],
queryFn: () => getEpigramComments(request),
}),
getMyComments: (request: CommentRequestType) => ({
queryKey: ['myEpigramComments', request],
queryFn: () => getMyEpigramComments(request),
}),
},
emotion: {
getMonthlyEmotionLogs: (request: GetMonthlyEmotionLogsRequestType) => ({
Expand Down
16 changes: 15 additions & 1 deletion src/apis/user.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { GetUserResponseType, GetUserRequestType, PatchMeRequestType, PostPresignedUrlRequestType, PostPresignedUrlResponseType } from '@/schema/user';
import type { GetUserResponseType, GetUserRequestType, PatchMeRequestType, PostPresignedUrlRequestType, PostPresignedUrlResponseType, GetMyContentCountType } from '@/schema/user';
import httpClient from '.';

export const getMe = async (): Promise<GetUserResponseType> => {
Expand All @@ -23,3 +23,17 @@ export const createPresignedUrl = async (request: PostPresignedUrlRequestType):
const response = await httpClient.post('/images/upload', formData);
return response.data;
};

export const getMyContentCount = async (request: GetUserRequestType): Promise<GetMyContentCountType> => {
const { id } = request;

// 에피그램 카운트
const epigram = await httpClient.get(`/epigrams`, { params: { limit: 1, cursor: 0, writerId: id } });

// 댓글 카운트
const comment = await httpClient.get(`/users/${id}/comments`, { params: { limit: 1, cursor: 0 } });

const response = { epigramCount: epigram.data.totalCount, commentCount: comment.data.totalCount };

return response;
};
7 changes: 7 additions & 0 deletions src/hooks/useCommentsHook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import quries from '@/apis/queries';
import { CommentRequestType } from '@/schema/comment';
import { useQuery } from '@tanstack/react-query';

const useCommentsHook = (requset: CommentRequestType) => useQuery(quries.epigramComment.getMyComments(requset));

export default useCommentsHook;
7 changes: 7 additions & 0 deletions src/hooks/useGetMyContentHook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import quries from '@/apis/queries';
import { GetUserRequestType } from '@/schema/user';
import { useQuery } from '@tanstack/react-query';

const useGetMyContentHook = (requset: GetUserRequestType) => useQuery(quries.user.getMyContentCount(requset));

export default useGetMyContentHook;
154 changes: 129 additions & 25 deletions src/pageLayout/MypageLayout/MyContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,68 +4,172 @@ import MyEpigrams from '@/user/ui-content/MyEpigrams';
import Image from 'next/image';
import { useToast } from '@/components/ui/use-toast';
import { EpigramsResponse } from '@/types/epigram.types';
import { CommentResponseType } from '@/schema/comment';
import useCommentsHook from '@/hooks/useCommentsHook';
import useGetMyContentHook from '@/hooks/useGetMyContentHook';
import MyComment from '@/user/ui-content/MyComment';
import UserInfo from '@/types/user';
import useDeleteCommentMutation from '@/hooks/useDeleteCommentHook';
import { Button } from '@/components/ui/button';
import spinner from '../../../public/spinner.svg';

interface MyContentProps {
userId: number;
user: UserInfo;
}

export default function MyContent({ userId }: MyContentProps) {
export default function MyContent({ user }: MyContentProps) {
const limit = 3;
const [cursor, setCursor] = useState(0);
const [epigrams, setEpigrams] = useState<EpigramsResponse>({ totalCount: 0, nextCursor: null, list: [] });
const [isLoadingMore, setIsLoadingMore] = useState(false);
const [selectedTab, setSelectedTab] = useState<'epigrams' | 'comments'>('epigrams');
const { toast } = useToast();

/** ************ 내 에피그램/댓글 카운트 조회 ************* */
const [epigramCount, setEpigramCount] = useState(0);
const [commentCount, setCommentCount] = useState(0);
const { data: count } = useGetMyContentHook({ id: user.id });
useEffect(() => {
if (count) {
setEpigramCount(count.epigramCount);
setCommentCount(count.commentCount);
}
}, [count]);

/** ************ 내 에피그램 조회 ************* */
const [epigramCursor, setEpigramCursor] = useState<number>(0);
const [epigrams, setEpigrams] = useState<EpigramsResponse>({ totalCount: 0, nextCursor: null, list: [] });
const epigramsRequest = {
limit,
cursor,
writerId: userId,
cursor: epigramCursor,
writerId: user.id,
};
const { data: epigramsData, isLoading: isEpigramsLoading, error: epigramsError } = useGetEpigrams(epigramsRequest);

/** ************ 내 댓글 조회 ************* */
const [commentCursor, setCommentCursor] = useState<number>(0);
const [comments, setComments] = useState<CommentResponseType>({ totalCount: 0, nextCursor: null, list: [] });
const commentsRequest = {
limit,
cursor: commentCursor,
id: user.id,
};
const { data, isLoading, error } = useGetEpigrams(epigramsRequest);
const { data: commentData, isLoading: isCommentsLoading, error: commentsError, refetch: refetchComments } = useCommentsHook(commentsRequest);

// [내 에피그램] 탭 선택 시
useEffect(() => {
if (data && data.list.length > 0) {
if (selectedTab === 'epigrams' && epigramsData) {
setEpigrams((prev) => ({
totalCount: data.totalCount,
nextCursor: data.nextCursor,
list: [...prev.list, ...data.list],
totalCount: epigramsData.totalCount,
nextCursor: epigramsData.nextCursor,
list: [...prev.list, ...epigramsData.list],
}));
setIsLoadingMore(false);
}
}, [epigramsData, selectedTab]);

// [내 댓글] 탭 선택 시
useEffect(() => {
if (selectedTab === 'comments' && commentData) {
setComments((prev) => ({
totalCount: commentData.totalCount,
nextCursor: commentData.nextCursor,
list: [...prev.list, ...commentData.list],
}));
setIsLoadingMore(false);
}
}, [data]);
}, [commentData, selectedTab]);

const handleMoreEpigramLoad = () => {
if (epigrams.nextCursor !== null) {
setCursor(epigrams.nextCursor);
// 더보기 버튼 클릭 시
const handleMoreLoad = () => {
if (selectedTab === 'epigrams' && epigrams.nextCursor) {
setEpigramCursor(epigrams.nextCursor);
setIsLoadingMore(true);
} else if (selectedTab === 'comments' && comments.nextCursor) {
setCommentCursor(comments.nextCursor);
setIsLoadingMore(true);
}
};

if (isLoading && !isLoadingMore) {
// [내 에피그램] [내 댓글] 탭 선택
const handleTabClick = (tab: 'epigrams' | 'comments') => {
setSelectedTab(tab);
// 데이터 초기화
if (tab === 'epigrams') {
setEpigrams({ totalCount: 0, nextCursor: null, list: [] });
setEpigramCursor(0);
} else {
setComments({ totalCount: 0, nextCursor: null, list: [] });
setCommentCursor(0);
}
setIsLoadingMore(false);
};

// 댓글 삭제
const deleteCommentMutation = useDeleteCommentMutation();
const handleDeleteComment = async (commentId: number) => {
try {
await deleteCommentMutation.mutateAsync(commentId);
setComments((prev) => ({
totalCount: prev.totalCount - 1,
nextCursor: prev.nextCursor,
list: prev.list.filter((comment) => comment.id !== commentId),
}));
setCommentCount((prev) => prev - 1);
toast({
title: '댓글이 삭제되었습니다.',
variant: 'destructive',
});
} catch (error) {
toast({
title: '댓글 삭제 실패했습니다.',
variant: 'destructive',
});
}
};

// 댓글 수정
const handleEditComment = () => {
setComments({ totalCount: 0, nextCursor: null, list: [] });
setCommentCursor(0);
refetchComments();
};

// 로딩 중
if ((isEpigramsLoading || isCommentsLoading) && !isLoadingMore) {
return <Image src={spinner} alt='로딩중' width={200} height={200} />;
}

if (error) {
// 에러
if (epigramsError || commentsError) {
toast({
description: error.message,
description: epigramsError?.message || commentsError?.message,
className: 'border-state-error text-state-error font-semibold',
});
}

return (
<div className='flex flex-col w-full lg:max-w-[640px] md:max-w-[640px] gap-12'>
<div className='inline-flex gap-6'>
<button type='button' className='text-black-600 font-semibold text-2xl'>
내 에피그램({epigrams.totalCount})
</button>
<button type='button' className='text-neutral-400 font-semibold text-2xl'>
내 댓글(0)
</button>
<Button
className={`font-semibold text-2xl ${selectedTab === 'epigrams' ? 'cursor-default text-black-600 disabled:opacity-1 ' : 'cursor-pointer text-neutral-400'}`}
onClick={() => selectedTab !== 'epigrams' && handleTabClick('epigrams')}
disabled={selectedTab === 'epigrams'}
>
내 에피그램({epigramCount})
</Button>
<Button
className={`font-semibold text-2xl ${selectedTab === 'comments' ? 'cursor-default text-black-600 disabled:opacity-1 ' : 'cursor-pointer text-neutral-400'}`}
onClick={() => selectedTab !== 'comments' && handleTabClick('comments')}
disabled={selectedTab === 'comments'}
>
내 댓글({commentCount})
</Button>
</div>
<div className='w-full py-[36px]'>
<div className='flex flex-col gap-[48px]'>
<MyEpigrams epigrams={epigrams.list} totalCount={epigrams.totalCount} onMoreEpigramLoad={handleMoreEpigramLoad} />
{selectedTab === 'epigrams' && <MyEpigrams epigrams={epigrams.list} totalCount={epigrams.totalCount} onMoreEpigramLoad={handleMoreLoad} />}
{selectedTab === 'comments' && (
<MyComment comments={comments.list} totalCount={comments.totalCount} onMoreEpigramLoad={handleMoreLoad} onDeleteComment={handleDeleteComment} onEditComment={handleEditComment} />
)}
{isLoadingMore && (
<div className='w-full flex items-center justify-center lg:mt-[70px] md:mt-[50px]'>
<Image src={spinner} alt='로딩중' width={200} height={200} />
Expand Down
7 changes: 5 additions & 2 deletions src/pageLayout/MypageLayout/MyPageLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import UserInfo from '@/types/user';
import EmotionMonthlyLogs from '@/pageLayout/MypageLayout/EmotionMonthlyLogs';
import Profile from '@/user/ui-profile/Profile';
import { useRouter } from 'next/navigation';
import TodayEmotion from '@/components/main/TodayEmotion';
import MyContent from './MyContent';

export default function MyPageLayout() {
Expand Down Expand Up @@ -31,11 +32,13 @@ export default function MyPageLayout() {
<div className='bg-background-100 w-full h-[200px]'></div>
<div className='w-full flex flex-col items-center bg-blue-100 rounded-3xl relative shadow-3xl'>
<Profile image={data.image} nickname={data.nickname} />
<div className='flex flex-col w-full lg:max-w-[640px] md:max-w-[640px] mt-[160px] space-y-0 md:mb-10 mb-5 border border-black'>오늘의 감정</div>
<div className='mt-[300px]'>
<TodayEmotion />
</div>
<EmotionMonthlyLogs userId={data.id} />
</div>
<div className='bg-background-100 flex flex-col items-center w-full py-[100px]'>
<MyContent userId={data.id} />
<MyContent user={data} />
</div>
</div>
);
Expand Down
7 changes: 7 additions & 0 deletions src/schema/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,16 @@ export const PostPresignedUrlResponse = z.object({
url: z.string().url(),
});

export const GetMyContentCount = z.object({
epigramCount: z.number(),
commentCount: z.number(),
});

export type GetUserResponseType = z.infer<typeof GetUserResponse>;
export type GetUserRequestType = z.infer<typeof GetUserRequest>;
export type PatchMeRequestType = z.infer<typeof PatchMeRequest>;

export type PostPresignedUrlRequestType = z.infer<typeof PostPresignedUrlRequest>;
export type PostPresignedUrlResponseType = z.infer<typeof PostPresignedUrlResponse>;

export type GetMyContentCountType = z.infer<typeof GetMyContentCount>;
Loading
Loading