feat: delete user from db and unsubscribe from stripe

This commit is contained in:
Ephraim Atta-Duncan
2024-01-20 23:30:56 +00:00
parent f652ca9b73
commit a3e560899a
6 changed files with 98 additions and 15 deletions

View File

@ -10,7 +10,13 @@
"ghcr.io/devcontainers/features/node:1": {}
},
"onCreateCommand": "./.devcontainer/on-create.sh",
"forwardPorts": [3000, 54320, 9000, 2500, 1100],
"forwardPorts": [
3000,
54320,
9000,
2500,
1100
],
"customizations": {
"vscode": {
"extensions": [
@ -25,8 +31,8 @@
"GitHub.copilot",
"GitHub.vscode-pull-request-github",
"Prisma.prisma",
"VisualStudioExptTeam.vscodeintellicode",
"VisualStudioExptTeam.vscodeintellicode"
]
}
}
}
}

View File

@ -3,6 +3,7 @@
import { useRouter } from 'next/navigation';
import { zodResolver } from '@hookform/resolvers/zod';
import { signOut } from 'next-auth/react';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
@ -65,6 +66,7 @@ export const ProfileForm = ({ className, user }: ProfileFormProps) => {
const isSubmitting = form.formState.isSubmitting;
const { mutateAsync: updateProfile } = trpc.profile.updateProfile.useMutation();
const { mutateAsync: deleteAccount } = trpc.profile.deleteAccount.useMutation();
const onFormSubmit = async ({ name, signature }: TProfileFormSchema) => {
try {
@ -98,6 +100,39 @@ export const ProfileForm = ({ className, user }: ProfileFormProps) => {
}
};
const onDeleteAccount = async () => {
try {
await deleteAccount();
await signOut({ callbackUrl: '/' });
toast({
title: 'Account deleted',
description: 'Your account has been deleted successfully.',
duration: 5000,
});
// logout after deleting account
router.push('/');
} catch (err) {
if (err instanceof TRPCClientError && err.data?.code === 'BAD_REQUEST') {
toast({
title: 'An error occurred',
description: err.message,
variant: 'destructive',
});
} else {
toast({
title: 'An unknown error occurred',
variant: 'destructive',
description:
'We encountered an unknown error while attempting to delete your account. Please try again later.',
});
}
}
};
return (
<Form {...form}>
<form
@ -171,12 +206,19 @@ export const ProfileForm = ({ className, user }: ProfileFormProps) => {
<span className="font-semibold">all of your documents</span>, along with all of
your completed documents, signatures, and all other resources belonging to your
Account.
<AlertDestructive />
<Alert variant="destructive" className="mt-5">
<AlertDescription className="selection:bg-red-100">
This action is not reversible. Please be certain.
</AlertDescription>
</Alert>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction className="bg-destructive text-destructive-foreground hover:bg-destructive/90">
<AlertDialogAction
onClick={onDeleteAccount}
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
Delete Account
</AlertDialogAction>
</AlertDialogFooter>
@ -189,12 +231,6 @@ export const ProfileForm = ({ className, user }: ProfileFormProps) => {
);
};
export function AlertDestructive() {
return (
<Alert variant="destructive" className="mt-5">
<AlertDescription className="selection:bg-red-100">
This action is not reversible. Please be certain.
</AlertDescription>
</Alert>
);
}
// Cal.com Delete User TRPC = https://github.com/calcom/cal.com/blob/main/packages/trpc/server/routers/loggedInViewer/deleteMe.handler.ts#L11
// https://github.com/calcom/cal.com/blob/main/packages/features/users/lib/userDeletionService.ts#L7
// delete stripe: https://github.com/calcom/cal.com/blob/main/packages/app-store/stripepayment/lib/customer.ts#L72

View File

@ -0,0 +1,10 @@
import { stripe } from '@documenso/lib/server-only/stripe';
import type { User } from '@documenso/prisma/client';
export const deleteStripeCustomer = async (user: User) => {
if (!user.customerId) {
return null;
}
return await stripe.customers.del(user.customerId);
};

View File

@ -9,7 +9,7 @@ import SuperJSON from 'superjson';
import { getBaseUrl } from '@documenso/lib/universal/get-base-url';
import { AppRouter } from '../server/router';
import type { AppRouter } from '../server/router';
export const trpc = createTRPCReact<AppRouter>({
unstable_overrides: {

View File

@ -10,3 +10,9 @@ export const ZSignUpMutationSchema = z.object({
export type TSignUpMutationSchema = z.infer<typeof ZSignUpMutationSchema>;
export const ZVerifyPasswordMutationSchema = ZSignUpMutationSchema.pick({ password: true });
export const ZDeleteAccountMutationSchema = z.object({
email: z.string().email(),
});
export type TDeleteAccountMutationSchema = z.infer<typeof ZDeleteAccountMutationSchema>;

View File

@ -1,5 +1,7 @@
import { TRPCError } from '@trpc/server';
import { deleteStripeCustomer } from '@documenso/ee/server-only/stripe/delete-customer';
import { deleteUser } from '@documenso/lib/server-only/user/delete-user';
import { forgotPassword } from '@documenso/lib/server-only/user/forgot-password';
import { getUserById } from '@documenso/lib/server-only/user/get-user-by-id';
import { resetPassword } from '@documenso/lib/server-only/user/reset-password';
@ -133,4 +135,27 @@ export const profileRouter = router({
});
}
}),
deleteAccount: authenticatedProcedure.mutation(async ({ ctx }) => {
try {
const user = ctx.user;
const deletedUser = await deleteStripeCustomer(user);
console.log(deletedUser);
return await deleteUser(user);
} catch (err) {
let message = 'We were unable to delete your account. Please try again.';
if (err instanceof Error) {
message = err.message;
}
throw new TRPCError({
code: 'BAD_REQUEST',
message,
});
}
}),
});