Skip to content

Commit

Permalink
[FEAT] 내 투표 조회 및 삭제 기능 (#197)
Browse files Browse the repository at this point in the history
  • Loading branch information
jhsung23 authored Mar 11, 2024
1 parent fdd9ea7 commit a7dbc61
Show file tree
Hide file tree
Showing 11 changed files with 256 additions and 21 deletions.
114 changes: 112 additions & 2 deletions src/app/mypage/_components/MyVote.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,119 @@
import { EmptyVote } from '@/components/shared';
'use client';

import dayjs from 'dayjs';
import Link from 'next/link';
import { useRef } from 'react';

import { Button } from '@/components/common/button';
import { VoteCard } from '@/components/features/vote';
import { ConfirmBottomSheet, EmptyVote, OptionBottomSheet } from '@/components/shared';
import { Typography } from '@/foundations/typography';
import { useBottomSheetState } from '@/hooks';
import { useDeleteVoteMutation, useGetMyVote } from '@/hooks/vote';
import { VoteType } from '@/types/vote';
import { fromNowOf } from '@/utils/dates';

type BottomSheetType = 'askDelete' | 'selectOption';

const MyVote = () => {
const { data, status } = useGetMyVote();
const { mutate: onDelete, isPending } = useDeleteVoteMutation();
const { onOpenSheet, openedSheet, onCloseSheet } = useBottomSheetState<BottomSheetType>();
const deleteTarget = useRef<VoteType['id'] | null>(null);

// TODO Suspense or ssr
return (
<>
<EmptyVote />
{status === 'pending' ? (
<></>
) : status === 'error' ? (
<></>
) : data.length > 0 ? (
<div className="mt-3xs flex flex-col gap-3xs">
{data.map((vote) => (
<VoteCard key={vote.id}>
<div className="mb-4xs flex justify-between">
<div className="flex gap-5xs">
<Typography type="title4" className="text-secondary-deep">
{vote.category}
</Typography>
<Typography type="title4" className="text-primary-700">
{fromNowOf(dayjs(vote.closeDate).endOf('day'))}
</Typography>
</div>
<Button
variant="empty"
iconOnly
icon="more"
iconSize={16}
className="!p-0"
onClick={() => {
onOpenSheet('selectOption');
deleteTarget.current = vote.id;
}}
disabled={isPending && deleteTarget.current === vote.id}
/>
</div>

<VoteCard.Description title={vote.title} content={vote.content} />
<Link
href={`/vote/${vote.id}`}
className="flex items-center justify-center rounded-lg bg-slate-200 px-3xs py-4xs text-base"
>
상세보기
</Link>
</VoteCard>
))}
<OptionBottomSheet
isOpen={openedSheet === 'selectOption'}
onClose={() => {
onCloseSheet();
deleteTarget.current = null;
}}
options={[
{
variant: 'warning',
optionLabel: '삭제',
onClick: () => onOpenSheet('askDelete'),
},
]}
/>
<ConfirmBottomSheet
isOpen={openedSheet === 'askDelete'}
onClose={() => {
onCloseSheet();
deleteTarget.current = null;
}}
title="투표를 삭제하시겠어요?"
description="투표를 삭제하면 되돌릴 수 없어요."
PrimaryButton={
<Button
width="full"
onClick={() => {
onCloseSheet();
onDelete({ voteId: deleteTarget.current! });
}}
>
확인
</Button>
}
SecondaryButton={
<Button
variant="secondary"
width="full"
onClick={() => {
onCloseSheet();
deleteTarget.current = null;
}}
>
취소
</Button>
}
/>
</div>
) : (
<EmptyVote />
)}
</>
);
};
Expand Down
79 changes: 66 additions & 13 deletions src/app/vote/[slug]/_component/VoteExtraDetail.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
'use client';

import { useRouter } from 'next/navigation';

import { Button } from '@/components/common/button';
import { ConfirmBottomSheet, OptionBottomSheet } from '@/components/shared';
import Profile from '@/components/shared/profile/Profile';
import { Typography } from '@/foundations/typography';
import { useBottomSheetState } from '@/hooks';
import { useGetUser } from '@/hooks/auth';
import { useDeleteVoteMutation } from '@/hooks/vote';
import { VoteType } from '@/types/vote';

type Props = {
Expand All @@ -13,24 +18,72 @@ type Props = {
category: string;
};

const VoteExtraDetail = ({ author, views, category }: Props) => {
type BottomSheetType = 'askDelete' | 'replyOption';

const VoteExtraDetail = ({ author, views, category, voteId }: Props) => {
const { data: user } = useGetUser();
const { mutate: onDelete, isPending } = useDeleteVoteMutation();

const router = useRouter();
const { onOpenSheet, openedSheet, onCloseSheet } = useBottomSheetState<BottomSheetType>();

return (
<aside className="flex flex-col gap-5xs">
<Typography type="subLabel2" className="text-secondary-deep">
{category}
</Typography>
<Profile
nickname={author.nickname}
subText={`${views}명 조회`}
actionButton={
user?.userId === author.id && (
<Button variant="empty" iconOnly icon="more" className="!p-0" onClick={() => {}} />
)
<>
<aside className="flex flex-col gap-5xs">
<Typography type="subLabel2" className="text-secondary-deep">
{category}
</Typography>
<Profile
nickname={author.nickname}
subText={`${views}명 조회`}
actionButton={
user?.userId === author.id && (
<Button
variant="empty"
iconOnly
icon="more"
className="!p-0"
onClick={() => onOpenSheet('replyOption')}
disabled={isPending}
/>
)
}
/>
</aside>
<OptionBottomSheet
isOpen={openedSheet === 'replyOption'}
onClose={onCloseSheet}
options={[
{
variant: 'warning',
optionLabel: '삭제',
onClick: () => onOpenSheet('askDelete'),
},
]}
/>
<ConfirmBottomSheet
isOpen={openedSheet === 'askDelete'}
onClose={onCloseSheet}
title="투표를 삭제하시겠어요?"
description="투표를 삭제하면 되돌릴 수 없어요."
PrimaryButton={
<Button
width="full"
onClick={() => {
onCloseSheet();
onDelete({ voteId }, { onSuccess: () => router.replace('/vote') });
}}
>
확인
</Button>
}
SecondaryButton={
<Button variant="secondary" width="full" onClick={onCloseSheet}>
취소
</Button>
}
/>
</aside>
</>
);
};

Expand Down
2 changes: 1 addition & 1 deletion src/app/vote/create/_components/CreateVoteForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { Textarea as ContentInput } from '@/components/common/textarea';
import { Header } from '@/components/layout/header';
import { Typography } from '@/foundations/typography';
import { useToast } from '@/hooks';
import { useCreateVoteMutation } from '@/hooks/api/vote';
import { useCreateVoteMutation } from '@/hooks/vote';
import { createVoteSchema } from '@/schema/CreateVoteSchema';

import { CategorySelector, TitleInput, VoteDateForm, VoteItemForm } from '.';
Expand Down
1 change: 0 additions & 1 deletion src/hooks/api/vote/index.ts

This file was deleted.

3 changes: 1 addition & 2 deletions src/hooks/auth/useGetUser.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { useQuery } from '@tanstack/react-query';
import { isAxiosError } from 'axios';
import { hasCookie } from 'cookies-next';

import { IS_LOGIN } from '@/constants/auth';
Expand All @@ -17,7 +16,7 @@ const useGetUser = () => {
queryKey: ['user'],
queryFn: getUser,
enabled: hasCookie(IS_LOGIN),
throwOnError: (error) => isAxiosError(error),
throwOnError: true,
staleTime: Infinity,
gcTime: Infinity,
});
Expand Down
1 change: 1 addition & 0 deletions src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { default as useBottomSheetState } from './useBottomSheetState';
export { useIsMounted } from './useIsMounted';
export { default as useToast } from './useToast';
export { default as useUploadImage } from './useUploadImage';
17 changes: 17 additions & 0 deletions src/hooks/useBottomSheetState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { useState } from 'react';

const useBottomSheetState = <TBottomSheetType>() => {
const [openedSheet, setOpenedSheet] = useState<TBottomSheetType | null>();

const onOpenSheet = (bottomSheetType: TBottomSheetType) => {
setOpenedSheet(bottomSheetType);
};

const onCloseSheet = () => {
setOpenedSheet(null);
};

return { onOpenSheet, openedSheet, onCloseSheet };
};

export default useBottomSheetState;
4 changes: 3 additions & 1 deletion src/hooks/vote/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
export { default as useCreateVoteMutation } from './useCreateVoteMutation';
export { default as useCreateVoteReplyMutation } from './useCreateVoteReplyMutation';
export { default as useDeleteVoteMutation } from './useDeleteVoteMutation';
export { default as useDeleteVoteReplyMutation } from './useDeleteVoteReplyMutation';
export { useGetAllVotes } from './useGetAllVotes';
export { default as useGetMyVote } from './useGetMyVote';
export { useGetVoteById } from './useGetVoteById';
export { useGetVoteBySearch } from './useGetVoteBySearch';
export { default as useGetVoteReplies } from './useGetVoteReplies';
export { default as useLikeVoteMutation } from './useLikeVoteMutation';
export { default as useLikeVoteReplyMutation } from './useLikeVoteReplyMutation';
export { default as useUpdateVoteReplyMutation } from './useUpdateVoteReplyMutation';
export { default as useVotingMutation } from './useVotingMutation';

Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ const useCreateVoteMutation = () => {
onSuccess: (data) => {
const { id } = data.data.data;
toast({ message: 'VOTE_UPLOAD_SUCCESS' });
queryClient.invalidateQueries({ queryKey: VOTE_KEY.ALL });
queryClient.invalidateQueries({ queryKey: VOTE_KEY.ALL, refetchType: 'all' });
router.replace(`/vote/${id}`);
},
onError: () => {
Expand Down
32 changes: 32 additions & 0 deletions src/hooks/vote/useDeleteVoteMutation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';

import { useToast } from '@/hooks';
import { del } from '@/lib/axios';
import { SuccessResponse } from '@/types/response';

type DeleteVoteRequest = {
voteId: number;
};

const deleteVote = async ({ voteId }: DeleteVoteRequest) => {
const response = await del<SuccessResponse<undefined>>(`/vote/${voteId}`);
return response.data;
};

const useDeleteVoteMutation = () => {
const toast = useToast();
const queryClient = useQueryClient();

return useMutation({
mutationFn: deleteVote,
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: ['votes'], refetchType: 'all' });
toast({ message: 'VOTE_DELETE_SUCCESS' });
},
onError: () => {
toast({ message: 'VOTE_DELETE_FAIL' });
},
});
};

export default useDeleteVoteMutation;
22 changes: 22 additions & 0 deletions src/hooks/vote/useGetMyVote.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { useQuery } from '@tanstack/react-query';

import { get } from '@/lib/axios';
import { SuccessResponse } from '@/types/response';
import { VoteType } from '@/types/vote';

const getMyVote = async () => {
const response = await get<SuccessResponse<VoteType[]>>('/vote/mine');
return response.data.data;
};

const useGetMyVote = () => {
return useQuery({
queryFn: getMyVote,
queryKey: ['votes', 'mine'],
retry: 1,
staleTime: Infinity,
gcTime: Infinity,
});
};

export default useGetMyVote;

0 comments on commit a7dbc61

Please sign in to comment.