Skip to content

Commit

Permalink
feat: delete account, remove cron job, misc
Browse files Browse the repository at this point in the history
  • Loading branch information
ajayvignesh01 committed May 20, 2024
1 parent d324d97 commit a638b9e
Show file tree
Hide file tree
Showing 15 changed files with 326 additions and 73 deletions.
67 changes: 67 additions & 0 deletions app/actions/deleteAccount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"use server";

import { createAdminClient } from "@/lib/supabase/admin";
import { createClient } from "@/lib/supabase/server";
import { cookies } from "next/headers";
import { z } from "zod";
import { redirect } from "next/navigation";

type FormState = {
message: string;
status: number;
};

export async function deleteAccount(prevState: FormState, formData: FormData) {
const cookieStore = cookies();
const supabase = createClient(cookieStore);
const supabaseAdmin = createAdminClient();

// get user object
const {
data: { user },
error: userError,
} = await supabase.auth.getUser();
if (userError)
return {
message: `Unable to get user object: ${userError.message}`,
status: 400,
};
if (!user) return { message: `Unable to get user object`, status: 400 };

// delete user input storage items
const { error: storageInputError } = await supabaseAdmin.storage
.from("input")
.remove([`${user.id}`]);
if (storageInputError)
return {
message: `Unable to delete user input images: ${storageInputError.message}`,
status: 400,
};

// delete user output storage items
const { error: storageOutputError } = await supabaseAdmin.storage
.from("output")
.remove([`${user.id}`]);
if (storageOutputError)
return {
message: `Unable to delete user output images: ${storageOutputError.message}`,
status: 400,
};

// delete user data
const { error } = await supabase.from("data").delete().eq("user_id", user.id);

// delete user
const { data: deleteData, error: deleteError } =
await supabaseAdmin.auth.admin.deleteUser(user.id);
if (error)
return {
message: `Unable to delete user: ${deleteError?.message}`,
status: 400,
};

return {
message: `Successfully deleted account for ${user.email}. Sign out or refresh the page`,
status: 200,
};
}
44 changes: 0 additions & 44 deletions app/api/cron/cleanup/route.ts

This file was deleted.

4 changes: 2 additions & 2 deletions components/gallery-page.tsx → app/gallery/gallery-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

import Balancer from "react-wrap-balancer";
import PhotoBooth from "@/components/home/photo-booth";
import { Tables } from "@/lib/supabase/types_db";
import { useRouter } from "next/navigation";
import { DataProps } from "@/lib/types";

export function GalleryPage({ data }: { data: Tables<"data">[] | null }) {
export function GalleryPage({ data }: { data: DataProps[] | null }) {
const router = useRouter();
return (
<div className="flex flex-col items-center justify-center">
Expand Down
2 changes: 1 addition & 1 deletion app/gallery/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createClient } from "@/lib/supabase/server";
import { cookies } from "next/headers";
import { GalleryPage } from "@/components/gallery-page";
import { GalleryPage } from "@/app/gallery/gallery-page";

export default async function Gallery() {
const cookieStore = cookies();
Expand Down
3 changes: 1 addition & 2 deletions app/p/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { getPlaiceholder } from "plaiceholder";
import PhotoPage from "@/components/photo-page";
import PhotoPage from "@/app/p/[id]/photo-page";
import { createClient } from "@/lib/supabase/server";
import { cookies } from "next/headers";
import { notFound } from "next/navigation";
Expand Down
File renamed without changes.
11 changes: 3 additions & 8 deletions components/home/upload-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,7 @@ import { Button } from "@/components/ui/button";
import { create } from "zustand";
import Image from "next/image";
import { Separator } from "@/components/ui/separator";
import {
ChangeEvent,
useCallback,
useMemo,
useState,
useTransition,
} from "react";
import { ChangeEvent, useCallback, useMemo, useState } from "react";
import { LoadingDots } from "@/components/shared/icons";
import { UploadCloud } from "lucide-react";
import { useFormState, useFormStatus } from "react-dom";
Expand Down Expand Up @@ -155,7 +149,7 @@ export function UploadForm() {
return (
<form
action={uploadFormAction}
className="bg-muted grid gap-6 px-4 py-8 md:px-16"
className="grid gap-6 bg-muted px-4 py-8 md:px-16"
>
<div>
<div className="flex items-center justify-between">
Expand Down Expand Up @@ -248,6 +242,7 @@ export function UploadForm() {
onChange={onChangePicture}
/>
</div>
{state?.message && <p className="text-destructive">{state.message}</p>}
</div>

<UploadButton data={data} />
Expand Down
168 changes: 168 additions & 0 deletions components/layout/delete-account-dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
"use client";

import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import {
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
} from "@/components/ui/drawer";
import { useMediaQuery } from "@/lib/hooks/use-media-query";
import { Button } from "@/components/ui/button";
import { create } from "zustand";
import Image from "next/image";
import { Separator } from "@/components/ui/separator";
import { useEffect } from "react";
import { LoadingDots } from "@/components/shared/icons";
import { deleteAccount } from "@/app/actions/deleteAccount";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { useFormState, useFormStatus } from "react-dom";

type DeleteDialogStore = {
open: boolean;
setOpen: (isOpen: boolean) => void;
};

export const useDeleteAccountDialog = create<DeleteDialogStore>((set) => ({
open: false,
setOpen: (open) => set(() => ({ open: open })),
}));

export function DeleteAccountDialog() {
const [open, setOpen] = useDeleteAccountDialog((s) => [s.open, s.setOpen]);
const isDesktop = useMediaQuery("(min-width: 768px)");

if (isDesktop) {
return (
<Dialog open={open} onOpenChange={setOpen} modal={true}>
<DialogContent className="gap-0 overflow-hidden p-0 md:rounded-2xl">
<DialogHeader className="items-center justify-center space-y-3 px-16 py-8">
<a href="https://precedent.dev">
<Image
src="/logo.png"
alt="Logo"
className="h-10 w-10 rounded-full"
width={20}
height={20}
/>
</a>
<DialogTitle className="font-display text-2xl font-bold leading-normal tracking-normal">
Delete Account
</DialogTitle>
<DialogDescription className="text-center">
This account will be deleted along with all your uploaded images
and AI generated images/gifs.
</DialogDescription>
</DialogHeader>

<Separator />

{/* Buttons */}
<div className="flex flex-col space-y-4 bg-muted px-16 py-8">
<DeleteAccountForm />
</div>
</DialogContent>
</Dialog>
);
}

return (
<Drawer open={open} onOpenChange={setOpen}>
<DrawerContent className="rounded-t-2xl">
<DrawerHeader className="flex flex-col items-center justify-center space-y-3 px-4 py-8">
<a href="https://precedent.dev">
<Image
src="/logo.png"
alt="Logo"
className="h-10 w-10 rounded-full"
width={20}
height={20}
/>
</a>
<DrawerTitle className="font-display text-2xl font-bold leading-normal tracking-normal">
Delete Account
</DrawerTitle>
<DrawerDescription className="text-center">
This account will be deleted along with all your uploaded images and
AI generated images/gifs.
</DrawerDescription>
</DrawerHeader>

<Separator />

{/* Buttons */}
<div className="flex flex-col space-y-4 bg-muted px-4 py-8">
<DeleteAccountForm />
</div>

<DrawerFooter className="bg-muted">
<DrawerClose asChild>
<Button variant="outline">Cancel</Button>
</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>
);
}

function DeleteAccountForm() {
const [state, deleteAccountFormAction] = useFormState(deleteAccount, {
message: "",
status: 0,
});

useEffect(() => {
if (state.message.includes("Successfully deleted account for")) {
window.location.reload();
}
}, [state]);

return (
<form action={deleteAccountFormAction} className="flex flex-col space-y-4">
<div className="grid w-full items-center gap-1.5">
<Label htmlFor="deleteConfirmation">
To verify, type <b>delete my account</b> below:
</Label>
<Input
type="text"
id="deleteConfirmation"
required
pattern="delete my account"
className="w-full"
/>
{state?.message && <p className="text-destructive">{state.message}</p>}
</div>
<DeleteAccountButton />
</form>
);
}

function DeleteAccountButton() {
const { pending } = useFormStatus();
return (
<Button
type="submit"
variant="destructive"
className="w-full focus-visible:ring-0 focus-visible:ring-transparent focus-visible:ring-offset-0"
disabled={pending}
>
{pending ? (
<LoadingDots color="#808080" />
) : (
<>
<p>Delete Account</p>
</>
)}
</Button>
);
}
19 changes: 18 additions & 1 deletion components/layout/user-dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,17 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Coins, CreditCard, LogOut, Receipt } from "lucide-react";
import { Coins, CreditCard, LogOut, Receipt, Trash2 } from "lucide-react";
import { billing } from "@/app/actions/billing";
import { LoadingDots } from "@/components/shared/icons";
import {
CheckoutDialog,
useCheckoutDialog,
} from "@/components/layout/checkout-dialog";
import {
DeleteAccountDialog,
useDeleteAccountDialog,
} from "@/components/layout/delete-account-dialog";

export function UserDropdown({ userData }: { userData: UserData | null }) {
const supabase = createClient();
Expand All @@ -35,6 +39,8 @@ export function UserDropdown({ userData }: { userData: UserData | null }) {
credits: number;
}>();

const setShowDeleteAccountModal = useDeleteAccountDialog((s) => s.setOpen);

useEffect(() => {
// TODO: display stripe success or failure modal
const stripeSuccess = searchParams.get("success") === "true";
Expand All @@ -58,6 +64,7 @@ export function UserDropdown({ userData }: { userData: UserData | null }) {
return (
<>
<CheckoutDialog />
<DeleteAccountDialog />
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Avatar className="size-9 cursor-pointer">
Expand Down Expand Up @@ -137,6 +144,16 @@ export function UserDropdown({ userData }: { userData: UserData | null }) {
<LogOut className="h-4 w-4" />
<p className="text-sm">Logout</p>
</DropdownMenuItem>

<DropdownMenuSeparator />

<DropdownMenuItem
className="space-x-2 focus:bg-destructive focus:text-destructive-foreground"
onClick={() => setShowDeleteAccountModal(true)}
>
<Trash2 className="h-4 w-4" />
<p className="text-sm">Delete Account</p>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</>
Expand Down
Loading

0 comments on commit a638b9e

Please sign in to comment.