diff --git a/app/(route)/admin/user/_components/UserTable/index.tsx b/app/(route)/admin/user/_components/UserTable/index.tsx index 16d49ce..ed96fc8 100644 --- a/app/(route)/admin/user/_components/UserTable/index.tsx +++ b/app/(route)/admin/user/_components/UserTable/index.tsx @@ -13,12 +13,15 @@ import { TableRow, } from '@app/_shadcn/components/ui/table'; import { Input } from '@app/_shadcn/components/ui/input'; +import ButtonWithDialogCheck from '@app/_components/common/WithDialogCheck'; +import useWithdrawalMutation from '@app/_hooks/apis/user/useWithdrawalMutation'; import RoleFilterSelect from './RoleFilterSelect'; import RoleUpdateSelect from './RoleUpdateSelect'; function UserTable() { const roleFilter = useRecoilValue(roleState); const { data } = useUserQuery(roleFilter); + const { mutate: withdrawMutate } = useWithdrawalMutation(); return ( <> @@ -35,6 +38,7 @@ function UserTable() { 닉네임 아이디 이메일 + 회원 탈퇴 @@ -47,6 +51,17 @@ function UserTable() { {nickname} {id} {email} + + withdrawMutate(id)} + > + 탈퇴 + + ))} diff --git a/app/(route)/mypage/_components/AccountSettingSection.tsx b/app/(route)/mypage/_components/AccountSettingSection.tsx new file mode 100644 index 0000000..1119e80 --- /dev/null +++ b/app/(route)/mypage/_components/AccountSettingSection.tsx @@ -0,0 +1,39 @@ +'use client'; + +import ButtonWithDialogCheck from '@app/_components/common/WithDialogCheck'; +import useWithdrawalMutation from '@app/_hooks/apis/user/useWithdrawalMutation'; +import { Button } from '@app/_shadcn/components/ui/button'; +import { myProfileState } from '@app/_store/permissionAtoms'; +import { useRecoilValue } from 'recoil'; + +function AccountSettingSection() { + const { mutate: withdrawMutate } = useWithdrawalMutation(); + const myProfile = useRecoilValue(myProfileState); + + const onWithdrawalClick = () => { + if (!myProfile?.id) { + throw new Error('[개발자 문의 바람] myProfile id 상태를 확인해주세요.'); + } + withdrawMutate(myProfile.id); + }; + + return ( +
+ + + 회원 탈퇴 + +
+ ); +} + +export default AccountSettingSection; diff --git a/app/(route)/mypage/_components/ProfileForm.tsx b/app/(route)/mypage/_components/ProfileForm.tsx index 21db98b..671ea99 100644 --- a/app/(route)/mypage/_components/ProfileForm.tsx +++ b/app/(route)/mypage/_components/ProfileForm.tsx @@ -40,10 +40,7 @@ function ProfileForm({ children, defaultValues }: Props) { return ( -
+ {children}
diff --git a/app/(route)/mypage/layout.tsx b/app/(route)/mypage/layout.tsx index 960375c..563344f 100644 --- a/app/(route)/mypage/layout.tsx +++ b/app/(route)/mypage/layout.tsx @@ -20,7 +20,7 @@ function Layout({ children }: Props) { if (!isLoggedIn) return redirect(PATH.user.login.url); return ( -
+
{children}
diff --git a/app/(route)/mypage/page.tsx b/app/(route)/mypage/page.tsx index 4e40624..1df10eb 100644 --- a/app/(route)/mypage/page.tsx +++ b/app/(route)/mypage/page.tsx @@ -3,11 +3,13 @@ import Link from 'next/link'; import { useRecoilValue } from 'recoil'; import { myProfileState } from '@app/_store/permissionAtoms'; +import { Separator } from '@app/_shadcn/components/ui/separator'; import { PATH } from '@app/_constants/urls'; import { Button, buttonVariants } from '@app/_shadcn/components/ui/button'; import MyAvatarInput from './_components/MyAvatarInput'; import MyInfoSection from './_components/MyInfoSection'; import ProfileForm from './_components/ProfileForm'; +import AccountSettingSection from './_components/AccountSettingSection'; function MyPage() { const myProfile = useRecoilValue(myProfileState); @@ -33,13 +35,17 @@ function MyPage() { }; return ( - - - - - + <> + + + + + + + + ); } diff --git a/app/_components/common/WithDialogCheck.tsx b/app/_components/common/WithDialogCheck.tsx new file mode 100644 index 0000000..4bf4ec5 --- /dev/null +++ b/app/_components/common/WithDialogCheck.tsx @@ -0,0 +1,53 @@ +import React from 'react'; +import { + Dialog, + DialogClose, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '@app/_shadcn/components/ui/dialog'; +import { Button, ButtonProps } from '@app/_shadcn/components/ui/button'; + +interface Props extends ButtonProps { + title: string; + description: string; + confirmVariant?: ButtonProps['variant']; + cancelVariant?: ButtonProps['variant']; +} + +function ButtonWithDialogCheck({ + title, + description, + onClick, + confirmVariant, + cancelVariant, + ...props +}: Props) { + return ( + + + + + + + + + + + ); +} + +export default ButtonWithDialogCheck; diff --git a/app/_hooks/apis/user/useWithdrawalMutation.ts b/app/_hooks/apis/user/useWithdrawalMutation.ts new file mode 100644 index 0000000..ef5ea8c --- /dev/null +++ b/app/_hooks/apis/user/useWithdrawalMutation.ts @@ -0,0 +1,28 @@ +import userService from '@app/_service/userService'; +import { useToast } from '@app/_shadcn/components/ui/use-toast'; +import { useMutation } from '@tanstack/react-query'; + +function useWithdrawalMutation() { + const { toast } = useToast(); + + return useMutation({ + mutationFn: async (userId: string) => { + await userService.withdrawal(userId); + }, + + onSuccess: () => + toast({ + title: '회원 탈퇴', + description: '회원 탈퇴에 성공했어요.', + }), + + onError: () => + toast({ + variant: 'destructive', + title: '회원 탈퇴', + description: '회원 탈퇴에 실패했어요.', + }), + }); +} + +export default useWithdrawalMutation; diff --git a/app/_service/userService.ts b/app/_service/userService.ts index af5c109..11c0247 100644 --- a/app/_service/userService.ts +++ b/app/_service/userService.ts @@ -20,6 +20,13 @@ class UserService extends Service { ); return data; } + + async withdrawal(userId: string) { + const { data } = await this.axiosExtend.delete( + `/api/user/withdrawal/${userId}`, + ); + return data; + } } const userService = new UserService();