Skip to content

Commit

Permalink
feat: add account deletion flow (#1990)
Browse files Browse the repository at this point in the history
  • Loading branch information
nickytonline authored Oct 31, 2023
2 parents 295e2b0 + 2d7c6bc commit daeb10e
Show file tree
Hide file tree
Showing 4 changed files with 208 additions and 32 deletions.
163 changes: 131 additions & 32 deletions components/organisms/UserSettingsPage/user-settings-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { useFetchUser } from "lib/hooks/useFetchUser";
import { getInterestOptions } from "lib/utils/getInterestOptions";
import { useToast } from "lib/hooks/useToast";
import { validateTwitterUsername } from "lib/utils/validate-twitter-username";
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "components/molecules/Dialog/dialog";
import CouponForm from "./coupon-form";

interface userSettingsPageProps {
Expand All @@ -32,7 +33,65 @@ type EmailPreferenceType = {
display_email?: boolean;
receive_collaboration?: boolean;
};

interface DeleteAccountModalProps {
open: boolean;
setOpen: (open: boolean) => void;
onDelete: () => void;
}

const DeleteAccountModal = ({ open, setOpen, onDelete }: DeleteAccountModalProps) => {
const [confirmText, setConfirmText] = useState("");
const disabled = confirmText !== "DELETE";

return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent className="p-4">
<DialogHeader>
<DialogTitle className="text-left">Delete Account</DialogTitle>
</DialogHeader>
<div className="flex flex-col gap-4">
<Text>Are you sure you want to delete your account?</Text>
<Text>
Type <span className="font-bold text-light-red-10">DELETE</span> in all caps to confirm
</Text>
<TextInput
onChange={(e) => {
setConfirmText(e.target.value);
}}
/>
<div className="flex gap-4">
<Button
type="submit"
className="w-max border-dark-red-8 bg-dark-red-8 text-white hover:border-dark-red-7 hover:bg-dark-red-7"
variant="primary"
onClick={() => {
if (!disabled) {
onDelete();
}
}}
disabled={disabled}
>
Delete
</Button>
<Button
variant="default"
onClick={() => {
setOpen(false);
}}
>
Cancel
</Button>
</div>
</div>
</DialogContent>
</Dialog>
);
};

const UserSettingsPage = ({ user }: userSettingsPageProps) => {
const [isModalOpen, setIsModalOpen] = useState(false);
const deleteFormRef = useRef<HTMLFormElement>(null);
const { data: insightsUser, mutate } = useFetchUser(user?.user_metadata.user_name, {
revalidateOnFocus: false,
});
Expand Down Expand Up @@ -389,44 +448,84 @@ const UserSettingsPage = ({ user }: userSettingsPageProps) => {
</Button>
</div>
{userInfo && (
<div>
{!hasReports && !coupon ? (
<div className="flex flex-col order-first gap-6 md:order-last">
<div className="flex flex-col gap-3">
<label className="text-2xl font-normal text-light-slate-11">Upgrade Access</label>
<div className="w-full sm:max-w-80">
<Text>Upgrade to a subscription to gain access to generate custom reports!</Text>
</div>
</div>
<StripeCheckoutButton variant="primary" />

{!coupon && <CouponForm refreshUser={mutate} />}
</div>
) : (
<div>
<>
<div>
{!hasReports && !coupon ? (
<div className="flex flex-col order-first gap-6 md:order-last">
<div className="flex flex-col gap-3">
<label className="text-2xl font-normal text-light-slate-11">Manage Subscriptions</label>
<div className="w-full md:w-96">
<Text>
You are currently subscribed to the Pro plan and currently have access to all premium
features.
</Text>
<label className="text-2xl font-normal text-light-slate-11">Upgrade Access</label>
<div className="w-full sm:max-w-80">
<Text>Upgrade to a subscription to gain access to generate custom reports!</Text>
</div>
</div>
<Button
rel="noopener noreferrer"
target="_blank"
href={process.env.NEXT_PUBLIC_STRIPE_SUB_CANCEL_URL}
className="w-max"
variant="primary"
>
Cancel Subscription
</Button>
<StripeCheckoutButton variant="primary" />

{!coupon && <CouponForm refreshUser={mutate} />}
</div>
) : (
<div>
<div className="flex flex-col order-first gap-6 md:order-last">
<div className="flex flex-col gap-3">
<label className="text-2xl font-normal text-light-slate-11">Manage Subscriptions</label>
<div className="w-full md:w-96">
<Text>
You are currently subscribed to the Pro plan and currently have access to all premium
features.
</Text>
</div>
</div>
<Button
rel="noopener noreferrer"
target="_blank"
href={process.env.NEXT_PUBLIC_STRIPE_SUB_CANCEL_URL}
className="w-max"
variant="primary"
>
Cancel Subscription
</Button>
</div>
</div>
)}
</div>
<form
name="delete-account"
action="/api/delete-account"
method="POST"
className="flex flex-col order-first gap-6 md:order-last"
ref={deleteFormRef}
onSubmit={(e) => {
setIsModalOpen(true);
e.preventDefault();
}}
>
<div className="flex flex-col gap-3">
<label className="text-2xl font-normal text-light-slate-11">Delete Account</label>
<div className="w-full md:w-96">
<Text>
Please note that account deletion is irreversible. Proceed only if you are certain about this
action.
</Text>
</div>
</div>
)}
</div>
<Button
type="submit"
rel="noopener noreferrer"
target="_blank"
className="w-max border-dark-red-8 bg-dark-red-8 text-white hover:border-dark-red-7 hover:bg-dark-red-7"
variant="primary"
>
Delete Account
</Button>
<DeleteAccountModal
open={isModalOpen}
setOpen={setIsModalOpen}
onDelete={() => {
setIsModalOpen(false);
deleteFormRef.current?.submit();
}}
/>
</form>
</>
)}
</div>
</div>
Expand Down
9 changes: 9 additions & 0 deletions middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const pathsToMatch = [
"/feed/",
"/user/notifications",
"/user/settings",
"/account-deleted"
];

export async function middleware(req: NextRequest) {
Expand All @@ -28,6 +29,14 @@ export async function middleware(req: NextRequest) {
data: { session },
} = await supabase.auth.getSession();

if (session?.user && req.nextUrl.pathname === "/account-deleted") {
// Delete the account from Supabase and log the user out.
await supabase.auth.admin.deleteUser(session.user.id);
await supabase.auth.signOut();

return res;
}

// Check auth condition
if (session?.user || req.nextUrl.searchParams.has("login")) {
// Authentication successful, forward request to protected route.
Expand Down
23 changes: 23 additions & 0 deletions pages/account-deleted.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import Head from "next/head";
import TopNav from "components/organisms/TopNav/top-nav";

const AccountDeletedPage = () => {
return (
<>
<Head>
<meta http-equiv="refresh" content="3;url=/" />
</Head>
<div className="min-h-screen flex flex-col">
<TopNav />
<main className="page-container flex flex-col m-4 pt-28 items-center">
<div className="info-container container w-full">
<h1 className="text-2xl">Account Deleted</h1>
<p>Your account has been deleted.</p>
</div>
</main>
</div>
</>
);
};

export default AccountDeletedPage;
45 changes: 45 additions & 0 deletions pages/api/delete-account.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { NextApiRequest, NextApiResponse } from "next";
import { createPagesServerClient } from "@supabase/auth-helpers-nextjs";
import { fetchApiData } from "helpers/fetchApiData";

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== "POST") {
res.status(405).json({
message: "Method not allowed",
});
}

const supabaseServerClient = createPagesServerClient({
req,
res,
});

try {
const {
data: { session },
} = await supabaseServerClient.auth.getSession();

if (session) {
const { error } = await fetchApiData({
path: "/profile",
method: "DELETE",
bearerToken: session.access_token,
pathValidator: () => true,
});

if (error) {
throw error;
}
} else {
res.status(401).json({
message: "Unauthorized",
});
}

res.redirect("/account-deleted");
} catch (error) {
res.status(500).json({
message: "Internal server error",
});
}
}

0 comments on commit daeb10e

Please sign in to comment.