diff --git a/app/header.tsx b/app/header.tsx index c95af52..d6160ed 100644 --- a/app/header.tsx +++ b/app/header.tsx @@ -1,6 +1,13 @@ "use client"; -import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { api } from "@/convex/_generated/api"; import { useAuthActions } from "@convex-dev/auth/react"; import { ChevronDownIcon, @@ -8,17 +15,10 @@ import { HamburgerMenuIcon, } from "@radix-ui/react-icons"; import { Authenticated, Unauthenticated, useQuery } from "convex/react"; -import dynamic from "next/dynamic"; +import { UserCircleIcon } from "lucide-react"; import Image from "next/image"; import Link from "next/link"; -import { Button } from "@/components/ui/button"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; -import { api } from "@/convex/_generated/api"; +import { useState } from "react"; const links = [ { href: "/", label: "Watch" }, @@ -109,11 +109,26 @@ export default function Header() { +
- + + + + + + + Settings + + + + + + +
+ + diff --git a/app/settings/page.tsx b/app/settings/page.tsx new file mode 100644 index 0000000..4a4dd5a --- /dev/null +++ b/app/settings/page.tsx @@ -0,0 +1,48 @@ +"use client"; + +import { Page, PageTitle } from "@/components/Page"; +import { Button } from "@/components/ui/button"; +import { api } from "@/convex/_generated/api"; +import { useAuthActions } from "@convex-dev/auth/react"; +import { useMutation, useQuery } from "convex/react"; +import { redirect, useRouter } from "next/navigation"; + +export default function Settings() { + const { signOut } = useAuthActions(); + const deleteAccount = useMutation(api.users.deleteUserById); + const user = useQuery(api.users.getUserOrNull); + + const router = useRouter(); + + const handleDelete = async () => { + if (confirm("Are you sure you want to delete your account?")) { + if (user) { + await signOut(); + await deleteAccount({ userId: user._id }); + + router.push("/"); + } + } + }; + + return ( + + Settings +
+

Delete your account

+

+ If you delete your account, all of your data will be permanently + removed. This includes your game results, and your user information. + Any maps you created and that were approved will not be removed but + you will no longer be the creator of them. This action cannot be + undone. +

+
+ +
+
+
+ ); +} \ No newline at end of file diff --git a/convex/users.ts b/convex/users.ts index 396a284..932b42e 100644 --- a/convex/users.ts +++ b/convex/users.ts @@ -4,7 +4,7 @@ import { customMutation, customQuery, } from "convex-helpers/server/customFunctions"; -import { ConvexError } from "convex/values"; +import { ConvexError, v } from "convex/values"; import { mutation, query } from "./_generated/server"; export const viewer = query({ @@ -118,3 +118,52 @@ export const authenticatedMutation = customMutation( }; }), ); + +export const deleteUserById = authenticatedMutation({ + handler: async (ctx, args) => { + + const userId = ctx.userId; + if (userId === null) { + throw new ConvexError(SIGN_IN_ERROR_MESSAGE); + } + + const userResults = await ctx.db + .query("userResults") + .filter((q) => q.eq(q.field("userId"), userId)) + .collect(); + + const promises: Promise[] = []; + + for (const result of userResults) { + promises.push(ctx.db.delete(result._id)); + } + + const maps = await ctx.db + .query("maps") + .filter((q) => q.eq(q.field("submittedBy"), userId)) + .collect(); + for (const map of maps) { + promises.push(ctx.db.patch(map._id, { submittedBy: undefined })); + } + + const authAccounts = await ctx.db.query("authAccounts").filter(q => q.eq(q.field("userId"), userId)).collect(); + + // Iterate over the queried documents and delete each one + for (const account of authAccounts) { + promises.push(ctx.db.delete(account._id)); + } + + const admin = await ctx.db + .query("admins") + .withIndex("by_userId", (q) => q.eq("userId", userId)) + .first(); + + if (admin) { + promises.push(ctx.db.delete(admin._id)); + } + + promises.push(ctx.db.delete(userId)); + + await Promise.all(promises); + }, +});