Skip to content

Commit

Permalink
Merge pull request #132 from edinstance/main
Browse files Browse the repository at this point in the history
Delete account functionality
  • Loading branch information
webdevcody authored Oct 30, 2024
2 parents 20e6ab2 + 03317aa commit 144d7cc
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 15 deletions.
46 changes: 32 additions & 14 deletions app/header.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
"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,
GitHubLogoIcon,
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" },
Expand Down Expand Up @@ -109,11 +109,26 @@ export default function Header() {
</Button>
</Link>
</Unauthenticated>
<div className="hidden md:block">
<Authenticated>
<Button className="hidden md:block" onClick={() => void signOut()}>
Sign Out
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost">
<UserCircleIcon className="h-6 w-6" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem asChild>
<Link href="/settings">Settings</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<button onClick={() => void signOut()}>Sign Out</button>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</Authenticated>
</div>

<Button
className="md:hidden"
variant="ghost"
Expand All @@ -124,7 +139,7 @@ export default function Header() {
</div>

{isMobileMenuOpen && (
<div className="absolute left-0 top-16 w-full border-b bg-white shadow-md dark:bg-slate-950 md:hidden">
<div className="absolute left-0 top-16 w-full border-b bg-white shadow-md md:hidden dark:bg-slate-950">
<nav className="flex flex-col items-start space-y-2 p-4">
{links.map((link) => (
<Button key={link.href} variant="ghost" asChild>
Expand Down Expand Up @@ -153,6 +168,9 @@ export default function Header() {
<Button variant="ghost" onClick={() => void signOut()}>
Sign Out
</Button>
<Button variant="ghost" asChild>
<Link href="/settings">Settings</Link>
</Button>
</Authenticated>
</nav>
</div>
Expand Down
48 changes: 48 additions & 0 deletions app/settings/page.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Page>
<PageTitle>Settings</PageTitle>
<div>
<h1 className="pb-4 text-2xl">Delete your account</h1>
<p className="text-md">
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.
</p>
<div className="flex justify-end pt-4">
<Button variant="destructive" onClick={handleDelete}>
Confirm
</Button>
</div>
</div>
</Page>
);
}
51 changes: 50 additions & 1 deletion convex/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down Expand Up @@ -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<void>[] = [];

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);
},
});

0 comments on commit 144d7cc

Please sign in to comment.