From 9cbb43204222b28ab1ff9e6bbfb5dc4f4566a457 Mon Sep 17 00:00:00 2001 From: mickasmt Date: Fri, 21 Jun 2024 11:10:05 +0200 Subject: [PATCH] feat: add delete account section --- actions/open-customer-portal.ts | 1 - .../dashboard/settings/loading.tsx | 11 +- app/(dashboard)/dashboard/settings/page.tsx | 4 +- app/api/user/route.ts | 26 ++++ components/dashboard/delete-account.tsx | 42 ++++++ components/forms/user-name-form.tsx | 4 +- components/modals/delete-account-modal.tsx | 135 ++++++++++++++++++ 7 files changed, 214 insertions(+), 9 deletions(-) create mode 100644 app/api/user/route.ts create mode 100644 components/dashboard/delete-account.tsx create mode 100644 components/modals/delete-account-modal.tsx diff --git a/actions/open-customer-portal.ts b/actions/open-customer-portal.ts index 812e97b3..25b059fc 100644 --- a/actions/open-customer-portal.ts +++ b/actions/open-customer-portal.ts @@ -4,7 +4,6 @@ import { redirect } from "next/navigation"; import { auth } from "@/auth"; import { stripe } from "@/lib/stripe"; -import { getUserSubscriptionPlan } from "@/lib/subscription"; import { absoluteUrl } from "@/lib/utils"; export type responseAction = { diff --git a/app/(dashboard)/dashboard/settings/loading.tsx b/app/(dashboard)/dashboard/settings/loading.tsx index 1b0a1389..9304c36c 100644 --- a/app/(dashboard)/dashboard/settings/loading.tsx +++ b/app/(dashboard)/dashboard/settings/loading.tsx @@ -1,6 +1,6 @@ -import { CardSkeleton } from "@/components/shared/card-skeleton" -import { DashboardHeader } from "@/components/dashboard/header" -import { DashboardShell } from "@/components/dashboard/shell" +import { DashboardHeader } from "@/components/dashboard/header"; +import { DashboardShell } from "@/components/dashboard/shell"; +import { CardSkeleton } from "@/components/shared/card-skeleton"; export default function DashboardSettingsLoading() { return ( @@ -9,9 +9,10 @@ export default function DashboardSettingsLoading() { heading="Settings" text="Manage account and website settings." /> -
+
+
- ) + ); } diff --git a/app/(dashboard)/dashboard/settings/page.tsx b/app/(dashboard)/dashboard/settings/page.tsx index 60bf3668..4ecbb0a4 100644 --- a/app/(dashboard)/dashboard/settings/page.tsx +++ b/app/(dashboard)/dashboard/settings/page.tsx @@ -2,6 +2,7 @@ import { redirect } from "next/navigation"; import { getCurrentUser } from "@/lib/session"; import { constructMetadata } from "@/lib/utils"; +import { DeleteAccountSection } from "@/components/dashboard/delete-account"; import { DashboardHeader } from "@/components/dashboard/header"; import { DashboardShell } from "@/components/dashboard/shell"; import { UserNameForm } from "@/components/forms/user-name-form"; @@ -24,8 +25,9 @@ export default async function SettingsPage() { heading="Settings" text="Manage account and website settings." /> -
+
+
); diff --git a/app/api/user/route.ts b/app/api/user/route.ts new file mode 100644 index 00000000..79375766 --- /dev/null +++ b/app/api/user/route.ts @@ -0,0 +1,26 @@ +import { auth } from "@/auth"; + +import { prisma } from "@/lib/db"; + +export const DELETE = auth(async (req) => { + if (!req.auth) { + return new Response("Not authenticated", { status: 401 }); + } + + const currentUser = req.auth.user; + if (!currentUser) { + return new Response("Invalid user", { status: 401 }); + } + + try { + await prisma.user.delete({ + where: { + id: currentUser.id, + }, + }); + } catch (error) { + return new Response("Internal server error", { status: 500 }); + } + + return new Response("User deleted successfully!", { status: 200 }); +}); diff --git a/components/dashboard/delete-account.tsx b/components/dashboard/delete-account.tsx new file mode 100644 index 00000000..ec0b020d --- /dev/null +++ b/components/dashboard/delete-account.tsx @@ -0,0 +1,42 @@ +"use client"; + +import { useDeleteAccountModal } from "@/components/modals/delete-account-modal"; +import { Button } from "@/components/ui/button"; +import { + Card, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { siteConfig } from "@/config/site"; + +export function DeleteAccountSection() { + const { setShowDeleteAccountModal, DeleteAccountModal } = + useDeleteAccountModal(); + + return ( + <> + + + + Delete Account + + Permanently delete your {siteConfig.name} account and your + subscription. This action cannot be undone - please proceed with + caution. + + + + + + + + ); +} diff --git a/components/forms/user-name-form.tsx b/components/forms/user-name-form.tsx index 6b1e4d20..405f7425 100644 --- a/components/forms/user-name-form.tsx +++ b/components/forms/user-name-form.tsx @@ -67,9 +67,9 @@ export function UserNameForm({ user }: UserNameFormProps) { return (
- + Your Name - + Please enter your full name or a display name you are comfortable with. diff --git a/components/modals/delete-account-modal.tsx b/components/modals/delete-account-modal.tsx new file mode 100644 index 00000000..7aa0856c --- /dev/null +++ b/components/modals/delete-account-modal.tsx @@ -0,0 +1,135 @@ +import { + Dispatch, + SetStateAction, + useCallback, + useMemo, + useState, +} from "react"; +import { signOut, useSession } from "next-auth/react"; +import { toast } from "sonner"; + +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Modal } from "@/components/ui/modal"; +import { UserAvatar } from "@/components/shared/user-avatar"; + +function DeleteAccountModal({ + showDeleteAccountModal, + setShowDeleteAccountModal, +}: { + showDeleteAccountModal: boolean; + setShowDeleteAccountModal: Dispatch>; +}) { + const { data: session } = useSession(); + const [deleting, setDeleting] = useState(false); + + async function deleteAccount() { + setDeleting(true); + await fetch(`/api/user`, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + }).then(async (res) => { + if (res.status === 200) { + // delay to allow for the route change to complete + await new Promise((resolve) => + setTimeout(() => { + signOut({ + callbackUrl: `${window.location.origin}/`, + }); + resolve(null); + }, 500), + ); + } else { + setDeleting(false); + const error = await res.text(); + throw error; + } + }); + } + + return ( + +
+ +

Delete Account

+

+ Warning: This will permanently delete your account and your + active subscription! +

+ + {/* TODO: Use getUserSubscriptionPlan(session.user.id) to display the user's subscription if he have a paid plan */} +
+ + { + e.preventDefault(); + toast.promise(deleteAccount(), { + loading: "Deleting account...", + success: "Account deleted successfully!", + error: (err) => err, + }); + }} + className="flex flex-col space-y-6 bg-accent px-4 py-8 text-left sm:px-16" + > +
+ + +
+ + + +
+ ); +} + +export function useDeleteAccountModal() { + const [showDeleteAccountModal, setShowDeleteAccountModal] = useState(false); + + const DeleteAccountModalCallback = useCallback(() => { + return ( + + ); + }, [showDeleteAccountModal, setShowDeleteAccountModal]); + + return useMemo( + () => ({ + setShowDeleteAccountModal, + DeleteAccountModal: DeleteAccountModalCallback, + }), + [setShowDeleteAccountModal, DeleteAccountModalCallback], + ); +}