diff --git a/src/app/account/page.tsx b/src/app/account/page.tsx
index dbec318..4e98014 100644
--- a/src/app/account/page.tsx
+++ b/src/app/account/page.tsx
@@ -5,11 +5,11 @@ import { redirect } from 'next/navigation';
import signOut from '@/lib/actions/signOut';
import { Label } from '@/components/ui/label';
import { Input } from '@/components/ui/input';
-import SignOutForm from '@/components/form/signOutForm';
import { URL_SIGN_IN } from '@/lib/constants';
-import GenerateSampleDataForm from '@/components/form/generateSampleDataForm';
import generateSampleData from '@/lib/actions/generateSampleData';
import { prismaClient } from '@/prisma';
+import { ServerActionTrigger } from '@/components/form/serverActionTrigger';
+import accountDelete from '@/lib/actions/accountDelete';
export default async function AccountPage() {
@@ -81,18 +81,32 @@ export default async function AccountPage() {
- {
- process.env.NODE_ENV === 'development' ? (
-
-
-
-
- ) : (
-
-
-
- )
- }
+
+
+ Delete Account
+
+
+ Sign Out
+
+ {
+ process.env.NODE_ENV === 'development' && (
+
+ Generate sample data
+
+ )
+ }
+
Version {process.env.appVersion}
diff --git a/src/components/form/generateSampleDataForm.tsx b/src/components/form/generateSampleDataForm.tsx
deleted file mode 100644
index c8677be..0000000
--- a/src/components/form/generateSampleDataForm.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-'use client';
-
-import { Button } from '@/components/ui/button';
-import React from 'react';
-import { useRouter } from 'next/navigation';
-import { toast } from 'sonner';
-import { sonnerContent } from '@/components/ui/sonner';
-import { ActionResponse } from '@/lib/types/actionResponse';
-
-export default function GenerateSampleDataForm({onSubmit}: { onSubmit: () => Promise
}) {
-
- const router = useRouter();
-
- const handleSubmit = async () => {
- const response = await onSubmit();
- toast(sonnerContent(response));
- router.refresh();
- };
-
- return (
-
- );
-}
diff --git a/src/components/form/serverActionTrigger.tsx b/src/components/form/serverActionTrigger.tsx
new file mode 100644
index 0000000..252b70f
--- /dev/null
+++ b/src/components/form/serverActionTrigger.tsx
@@ -0,0 +1,97 @@
+'use client';
+
+import { buttonVariants } from '@/components/ui/button';
+import React from 'react';
+import { Slot } from '@radix-ui/react-slot';
+import { cn } from '@/lib/utils';
+import { useRouter } from 'next/navigation';
+import { toast } from 'sonner';
+import { sonnerContent } from '@/components/ui/sonner';
+import type { VariantProps } from 'class-variance-authority';
+import { ActionResponse } from '@/lib/types/actionResponse';
+import {
+ AlertDialog,
+ AlertDialogAction,
+ AlertDialogCancel,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogTitle,
+ AlertDialogTrigger,
+} from '@/components/ui/alert-dialog';
+
+export interface ConfirmationDialogProps {
+ title: string;
+ description?: string;
+ actionText?: string;
+}
+
+export interface ButtonWithActionProps
+ extends React.ButtonHTMLAttributes,
+ VariantProps {
+ asChild?: boolean;
+ dialog?: ConfirmationDialogProps;
+ action: () => Promise>;
+ callback?: (data: T) => void;
+}
+
+const ServerActionTrigger = React.forwardRef(
+ ({className, variant, size, asChild = false, ...props}, ref) => {
+
+ const router = useRouter();
+
+ const Comp = asChild ? Slot : 'button';
+
+ const handleSubmit = async () => {
+ const response = await props.action();
+ toast(sonnerContent(response));
+ if (props.callback) {
+ props.callback(response);
+ }
+ if (response.redirect) {
+ router.push(response.redirect);
+ }
+ };
+
+ return props.dialog ? (
+
+
+
+
+
+
+ {props.dialog.title}
+ {props.dialog?.description && (
+
+ {props.dialog.description}
+
+ )}
+
+
+
+ Cancel
+
+
+ {props.dialog.actionText || 'Confirm'}
+
+
+
+
+ ) : (
+
+ );
+ },
+);
+ServerActionTrigger.displayName = 'ServerActionTrigger';
+
+export { ServerActionTrigger };
diff --git a/src/components/form/signOutForm.tsx b/src/components/form/signOutForm.tsx
deleted file mode 100644
index 9dd51fe..0000000
--- a/src/components/form/signOutForm.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-'use client';
-
-import { ActionResponse } from '@/lib/types/actionResponse';
-import { Button } from '@/components/ui/button';
-import React from 'react';
-import { useRouter } from 'next/navigation';
-import { toast } from 'sonner';
-import { sonnerContent } from '@/components/ui/sonner';
-
-export default function SignOutForm({onSubmit}: { onSubmit: () => Promise }) {
-
- const router = useRouter();
-
- const handleSignOut = async () => {
- const response = await onSubmit();
- toast(sonnerContent(response));
- if (response.redirect) {
- router.push(response.redirect);
- }
- };
-
- return (
-
- );
-}
diff --git a/src/lib/actions/accountDelete.ts b/src/lib/actions/accountDelete.ts
new file mode 100644
index 0000000..28cf745
--- /dev/null
+++ b/src/lib/actions/accountDelete.ts
@@ -0,0 +1,58 @@
+import { ActionResponse } from '@/lib/types/actionResponse';
+import { URL_SIGN_IN } from '@/lib/constants';
+import { getUser, lucia } from '@/auth';
+import { prismaClient } from '@/prisma';
+import { cookies } from 'next/headers';
+
+export default async function accountDelete(): Promise {
+ 'use server';
+
+ const user = await getUser();
+
+ if (!user) {
+ return {
+ type: 'error',
+ message: 'You aren\'t signed in.',
+ redirect: URL_SIGN_IN,
+ };
+ }
+
+ await prismaClient.payment.deleteMany({
+ where: {
+ userId: user.id,
+ },
+ });
+
+ await prismaClient.entity.deleteMany({
+ where: {
+ userId: user.id,
+ },
+ });
+
+ await prismaClient.category.deleteMany({
+ where: {
+ userId: user.id,
+ },
+ });
+
+ await prismaClient.session.deleteMany({
+ where: {
+ userId: user.id,
+ },
+ });
+
+ await prismaClient.user.delete({
+ where: {
+ id: user.id,
+ },
+ });
+
+ const sessionCookie = lucia.createBlankSessionCookie();
+ cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);
+
+ return {
+ type: 'success',
+ message: 'Your account was removed.',
+ redirect: URL_SIGN_IN,
+ };
+}
diff --git a/src/lib/types/actionResponse.ts b/src/lib/types/actionResponse.ts
index 3a436c6..a4787da 100644
--- a/src/lib/types/actionResponse.ts
+++ b/src/lib/types/actionResponse.ts
@@ -1,5 +1,6 @@
-export interface ActionResponse {
+export interface ActionResponse {
type: 'success' | 'info' | 'warning' | 'error';
message: string;
redirect?: string;
+ data?: T;
}