Skip to content

Commit

Permalink
feat: delete account
Browse files Browse the repository at this point in the history
  • Loading branch information
Razvy13 committed Sep 12, 2024
1 parent 55ed15c commit 998769a
Show file tree
Hide file tree
Showing 5 changed files with 265 additions and 53 deletions.
26 changes: 26 additions & 0 deletions src/components/ProfileDeleteButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"use client"

import * as React from "react"
import { ProfileDeleteDialog } from "./ProfileDeleteDialog"
import { Button } from "./ui/button"

export default function ProfileDeleteButton() {
const [showDeleteDialog, setShowDeleteDialog] = React.useState(false)
return (
<>
<ProfileDeleteDialog
open={showDeleteDialog}
onOpenChange={setShowDeleteDialog}
showTrigger={false}
onSuccess={() => setShowDeleteDialog(false)}
/>
<Button
onClick={() => setShowDeleteDialog(true)}
size={"sm"}
variant={"destructive"}
>
Delete
</Button>
</>
)
}
18 changes: 18 additions & 0 deletions src/components/ProfileDeleteCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"use client"

import ProfileDeleteButton from "./ProfileDeleteButton"
import { Card, CardContent, CardFooter, CardHeader } from "./ui/card"

export function ProfileDeleteCard() {
return (
<Card>
<CardHeader className="text-red-400">Delete account</CardHeader>
<CardContent className="flex flex-col gap-4">
Once you delete your account, there is no going back. Please be certain.
</CardContent>
<CardFooter>
<ProfileDeleteButton />
</CardFooter>
</Card>
)
}
144 changes: 144 additions & 0 deletions src/components/ProfileDeleteDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
"use client"

import { Button } from "@/components/ui/button"
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import {
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from "@/components/ui/drawer"
import { useMediaQuery } from "@/hooks/useMediaQuery"
import { deleteOwnAccount } from "@/services/settings/api"
import { TrashIcon } from "@radix-ui/react-icons"
import { useRouter } from "next/navigation"
import * as React from "react"
import { toast } from "sonner"
import { Icons } from "./icons"

interface DeleteProfileDialogProps
extends React.ComponentPropsWithoutRef<typeof Dialog> {
showTrigger?: boolean
onSuccess?: () => void
}

export function ProfileDeleteDialog({
showTrigger = true,
onSuccess,
...props
}: DeleteProfileDialogProps) {
const router = useRouter()
const [isDeletePending, startDeleteTransition] = React.useTransition()
const isDesktop = useMediaQuery("(min-width: 640px)")
const DRAWER_TITLE = "Are you absolutely sure?"
const DRAWER_DESCRIPTION =
"This action cannot be undone. It will permanently delete your account from your Myntenance. This action will not affect your GitHub repository."

function onDelete() {
startDeleteTransition(async () => {
try {
const res = await deleteOwnAccount()
router.push("/")
onSuccess?.()
toast.success(`Your account was successfully deleted!`)
} catch (error: unknown) {
const message =
error instanceof Error
? error.message
: "An error occurred while deleting your account."
toast.error(message)
}
})
}

if (isDesktop) {
return (
<Dialog {...props}>
{showTrigger ? (
<DialogTrigger asChild>
<Button variant="outline" size="sm">
<TrashIcon className="mr-2 size-4" aria-hidden="true" />
Delete
</Button>
</DialogTrigger>
) : null}
<DialogContent>
<DialogHeader>
<DialogTitle>{DRAWER_TITLE}</DialogTitle>
<DialogDescription>{DRAWER_DESCRIPTION}</DialogDescription>
</DialogHeader>
<DialogFooter className="gap-2 sm:space-x-0">
<DialogClose asChild>
<Button variant="outline">Cancel</Button>
</DialogClose>
<Button
aria-label="Delete your account"
variant="destructive"
onClick={onDelete}
disabled={isDeletePending}
>
{isDeletePending && (
<Icons.spinner
className="mr-2 size-4 animate-spin"
aria-hidden="true"
/>
)}
Delete
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}

return (
<Drawer {...props}>
{showTrigger ? (
<DrawerTrigger asChild>
<Button variant="outline" size="sm">
<TrashIcon className="mr-2 size-4" aria-hidden="true" />
Delete
</Button>
</DrawerTrigger>
) : null}
<DrawerContent>
<DrawerHeader>
<DrawerTitle>{DRAWER_TITLE}</DrawerTitle>
<DrawerDescription>{DRAWER_DESCRIPTION}</DrawerDescription>
</DrawerHeader>
<DrawerFooter className="gap-2 sm:space-x-0">
<DrawerClose asChild>
<Button variant="outline">Cancel</Button>
</DrawerClose>
<Button
aria-label="Delete selected repository"
variant="destructive"
onClick={onDelete}
disabled={isDeletePending}
>
{isDeletePending && (
<Icons.spinner
className="mr-2 size-4 animate-spin"
aria-hidden="true"
/>
)}
Delete
</Button>
</DrawerFooter>
</DrawerContent>
</Drawer>
)
}
111 changes: 58 additions & 53 deletions src/components/ProfileSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { getOwnSettings, updateOwnSettings } from "@/services/settings/api"
import { useForm } from "@tanstack/react-form"
import { use, useTransition } from "react"
import { toast } from "sonner"
import { ProfileDeleteCard } from "./ProfileDeleteCard"
import { Button } from "./ui/button"
import { Card, CardContent, CardFooter, CardHeader } from "./ui/card"
import { Checkbox } from "./ui/checkbox"
Expand Down Expand Up @@ -48,58 +49,62 @@ export function ProfileSettings({ settingsPromise, profilePromise }: Props) {
})

return (
<form
onSubmit={(e) => {
e.preventDefault()
form.handleSubmit()
}}
>
<Card>
<CardHeader>Public Profile</CardHeader>
<CardContent className="flex flex-col gap-4">
<form.Field name="fullName">
{(field) => (
<div>
<Label htmlFor={field.name}>Full Name</Label>
<Input
id={field.name}
type="text"
value={field.state.value}
onChange={(e) => field.handleChange(e.target.value)}
/>
</div>
)}
</form.Field>
<form.Field name="showPublicActivity">
{(field) => (
<div className="flex items-center gap-2">
<Checkbox
id={field.name}
checked={field.state.value}
onCheckedChange={(checked) =>
field.handleChange(checked === true)
}
/>
<Label htmlFor={field.name}>Show Activity</Label>
</div>
)}
</form.Field>
</CardContent>
<CardFooter>
<form.Subscribe
selector={(state) => [state.canSubmit, state.isSubmitting]}
>
{([canSubmit, isSubmitting]) => {
const isSubmitPending = isUpdatePending || isSubmitting
return (
<Button disabled={!canSubmit || isSubmitPending}>
{isSubmitPending ? "Saving..." : "Save"}
</Button>
)
}}
</form.Subscribe>
</CardFooter>
</Card>
</form>
<>
<form
onSubmit={(e) => {
e.preventDefault()
form.handleSubmit()
}}
>
<Card>
<CardHeader>Public Profile</CardHeader>
<CardContent className="flex flex-col gap-4">
<form.Field name="fullName">
{(field) => (
<div>
<Label htmlFor={field.name}>Full Name</Label>
<Input
id={field.name}
type="text"
value={field.state.value}
onChange={(e) => field.handleChange(e.target.value)}
/>
</div>
)}
</form.Field>
<form.Field name="showPublicActivity">
{(field) => (
<div className="flex items-center gap-2">
<Checkbox
id={field.name}
checked={field.state.value}
onCheckedChange={(checked) =>
field.handleChange(checked === true)
}
/>
<Label htmlFor={field.name}>Show Activity</Label>
</div>
)}
</form.Field>
</CardContent>
<CardFooter>
<form.Subscribe
selector={(state) => [state.canSubmit, state.isSubmitting]}
>
{([canSubmit, isSubmitting]) => {
const isSubmitPending = isUpdatePending || isSubmitting
return (
<Button disabled={!canSubmit || isSubmitPending}>
{isSubmitPending ? "Saving..." : "Save"}
</Button>
)
}}
</form.Subscribe>
</CardFooter>
</Card>
</form>

<ProfileDeleteCard />
</>
)
}
19 changes: 19 additions & 0 deletions src/services/settings/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { createClient } from "@/lib/supabase/server"
import { ProfileUpdate, SettingsUpdate } from "@/lib/supabase/types"
import { getCurrentUserId } from "@/lib/supabase/utils"
import { revalidatePath } from "next/cache"

export async function getOwnSettings() {
const supabase = createClient()
Expand Down Expand Up @@ -38,3 +39,21 @@ export async function updateOwnSettings(

return { error: profileError }
}

// TODO Set right parameters for the delete
export async function deleteOwnAccount() {
const supabase = createClient()
const userId = await getCurrentUserId(supabase)
const result = await supabase
.from("user_profiles")
.delete()
.eq("id", userId!)
.select()
.single()
.throwOnError()

await supabase.auth.signOut()
revalidatePath("/")

return result
}

0 comments on commit 998769a

Please sign in to comment.