Skip to content

Commit

Permalink
Add Approved Users (#66)
Browse files Browse the repository at this point in the history
* Add "approved" to schema

* Add user approval to config

* Add approval check to layout

* Add approval button to dash

* Add approval check to rsvp

* Add approval check to discord verify

* Make approval disabled by default

* Fix typing issue
  • Loading branch information
Lermatroid authored Jul 25, 2024
1 parent 2911823 commit 4039519
Show file tree
Hide file tree
Showing 12 changed files with 1,217 additions and 4 deletions.
16 changes: 16 additions & 0 deletions apps/web/src/actions/admin/user-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,19 @@ export const updateRole = adminAction(
return { success: true };
},
);

export const setUserApproval = adminAction(
z.object({
userIDToUpdate: z.string().min(1),
approved: z.boolean(),
}),

async ({ userIDToUpdate, approved }, { user, userId }) => {
await db
.update(users)
.set({ approved })
.where(eq(users.clerkID, userIDToUpdate));
revalidatePath(`/admin/users/${userIDToUpdate}`);
return { success: true };
},
);
12 changes: 10 additions & 2 deletions apps/web/src/app/admin/users/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import {
import { auth } from "@clerk/nextjs";
import { notFound } from "next/navigation";
import { isUserAdmin } from "@/lib/utils/server/admin";
import ApproveUserButton from "@/components/admin/users/ApproveUserButton";
import c from "config";

export default async function Page({ params }: { params: { slug: string } }) {
const { userId } = auth();
Expand Down Expand Up @@ -44,7 +46,7 @@ export default async function Page({ params }: { params: { slug: string } }) {

return (
<main className="mx-auto max-w-5xl pt-44">
<div className="mb-5 grid w-full grid-cols-2">
<div className="mb-5 grid w-full grid-cols-3">
<div className="flex items-center">
<div>
<h2 className="flex items-center gap-x-2 text-3xl font-bold tracking-tight">
Expand All @@ -54,7 +56,7 @@ export default async function Page({ params }: { params: { slug: string } }) {
{/* <p className="text-sm text-muted-foreground">{users.length} Total Users</p> */}
</div>
</div>
<div className="flex items-center justify-end gap-2">
<div className="col-span-2 flex items-center justify-end gap-2">
<Link href={`/@${user.hackerTag}`} target="_blank">
<Button variant={"outline"}>Hacker Profile</Button>
</Link>
Expand All @@ -65,6 +67,12 @@ export default async function Page({ params }: { params: { slug: string } }) {
currPermision={user.role}
userID={user.clerkID}
/>
{(c.featureFlags.core.requireUsersApproval as boolean) && (
<ApproveUserButton
userIDToUpdate={user.clerkID}
currentApproval={user.approved}
/>
)}
</div>
</div>
<div className="mt-20 grid min-h-[500px] w-full grid-cols-3">
Expand Down
21 changes: 19 additions & 2 deletions apps/web/src/app/dash/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,35 @@ import ProfileButton from "@/components/shared/ProfileButton";
import ClientToast from "@/components/shared/ClientToast";

import { TRPCReactProvider } from "@/trpc/react";
import { db } from "db";
import { eq } from "db/drizzle";
import { users } from "db/schema";

interface DashLayoutProps {
children: React.ReactNode;
}

export default async function DashLayout({ children }: DashLayoutProps) {
const user = await currentUser();
const clerkUser = await currentUser();

if (!user || !user.publicMetadata.registrationComplete) {
if (!clerkUser || !clerkUser.publicMetadata.registrationComplete) {
return redirect("/register");
}

const user = await db.query.users.findFirst({
where: eq(users.clerkID, clerkUser.id),
});

if (!user) return redirect("/register");

if (
(c.featureFlags.core.requireUsersApproval as boolean) === true &&
user.approved === false &&
user.role === "hacker"
) {
return redirect("/i/approval");
}

return (
<>
<TRPCReactProvider>
Expand Down
8 changes: 8 additions & 0 deletions apps/web/src/app/discord-verify/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ export default async function Page({
return redirect("/register");
}

if (
(c.featureFlags.core.requireUsersApproval as boolean) === true &&
user.approved === false &&
user.role === "hacker"
) {
return redirect("/i/approval");
}

if (user.discordVerification) {
await db
.update(discordVerification)
Expand Down
30 changes: 30 additions & 0 deletions apps/web/src/app/i/approval/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import c from "config";
import Link from "next/link";
import { Button } from "@/components/shadcn/ui/button";

export default function Page() {
return (
<main className="mx-auto flex min-h-screen w-full max-w-5xl flex-col items-center justify-center">
<div className="max-w-screen fixed left-1/2 top-[calc(50%+7rem)] h-[40vh] w-[800px] -translate-x-1/2 -translate-y-1/2 scale-150 overflow-x-hidden bg-hackathon opacity-30 blur-[100px] will-change-transform"></div>
<h1 className="mb-10 text-6xl font-extrabold text-hackathon dark:bg-gradient-to-t dark:from-hackathon/80 dark:to-white dark:bg-clip-text dark:text-transparent md:text-8xl">
{c.hackathonName}
</h1>
<div className="relative flex aspect-video w-full max-w-[500px] flex-col items-center justify-center rounded-xl bg-white p-5 backdrop-blur transition dark:bg-white/[0.08]">
<h1 className="flex items-center gap-x-2 text-2xl font-bold text-green-500">
{/* <CheckCircleIcon /> */}
Thanks for registering!
</h1>
<p className="pb-10 pt-5 text-center">
Your account is awaiting approval.
<br />
You will be notified when it is approved!
</p>
<Link href={"/"}>
<Button>Go Home</Button>
</Link>
</div>
</main>
);
}

export const runtime = "edge";
8 changes: 8 additions & 0 deletions apps/web/src/app/rsvp/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ export default async function RsvpPage({
return redirect("/register");
}

if (
(c.featureFlags.core.requireUsersApproval as boolean) === true &&
user.approved === false &&
user.role === "hacker"
) {
return redirect("/i/approval");
}

const rsvpEnabled = await kv.get("config:registration:allowRSVPs");

// TODO: fix type jank here
Expand Down
44 changes: 44 additions & 0 deletions apps/web/src/components/admin/users/ApproveUserButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"use client";

import { Button } from "@/components/shadcn/ui/button";
import { useAction } from "next-safe-action/hook";
import { setUserApproval } from "@/actions/admin/user-actions";
import { toast } from "sonner";

interface ApproveUserButtonProps {
userIDToUpdate: string;
currentApproval: boolean;
}

export default function ApproveUserButton({
userIDToUpdate,
currentApproval,
}: ApproveUserButtonProps) {
const { execute, status } = useAction(setUserApproval, {
onSuccess: () => {
toast.dismiss();
console.log("Success");
toast.success(`User ${currentApproval ? "Un-a" : "A"}pproved!`);
},
onError: (e) => {
toast.dismiss();
console.log("Error", e);
toast.error(
"An error occurred while changing user approval. Please try again.",
);
},
});

return (
<Button
onClick={() => {
toast.loading("Changing user approval...");
execute({ userIDToUpdate, approved: !currentApproval });
}}
variant={"outline"}
disabled={status === "executing"}
>
{currentApproval ? "Un-a" : "A"}pprove User
</Button>
);
}
5 changes: 5 additions & 0 deletions packages/config/hackkit.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,11 @@ export default {
},
},
maxTeamSize: 4,
featureFlags: {
core: {
requireUsersApproval: false,
},
},
} as const;

// Its important that this is kept in sync with the database schema.
Expand Down
1 change: 1 addition & 0 deletions packages/db/drizzle/0014_known_norman_osborn.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE "users" ADD COLUMN "approved" boolean DEFAULT false NOT NULL;
Loading

0 comments on commit 4039519

Please sign in to comment.