Skip to content

Commit

Permalink
Merge branch 'HK-180-no-duplicate-resumes' of https://github.com/acmu…
Browse files Browse the repository at this point in the history
…tsa/HackKit into HK-180-no-duplicate-resumes
  • Loading branch information
jacobellerbrock committed Dec 30, 2024
2 parents e229323 + fd3dd3d commit e883938
Show file tree
Hide file tree
Showing 15 changed files with 193 additions and 45 deletions.
12 changes: 12 additions & 0 deletions apps/web/src/actions/admin/registration-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ const defaultRegistrationToggleSchema = z.object({
enabled: z.boolean(),
});

const defaultRSVPLimitSchema = z.object({
rsvpLimit: z.number(),
});

export const toggleRegistrationEnabled = adminAction
.schema(defaultRegistrationToggleSchema)
.action(async ({ parsedInput: { enabled }, ctx: { user, userId } }) => {
Expand Down Expand Up @@ -40,3 +44,11 @@ export const toggleRSVPs = adminAction
revalidatePath("/admin/toggles/registration");
return { success: true, statusSet: enabled };
});

export const setRSVPLimit = adminAction
.schema(defaultRSVPLimitSchema)
.action(async ({ parsedInput: { rsvpLimit }, ctx: { user, userId } }) => {
await kv.set("config:registration:maxRSVPs", rsvpLimit);
revalidatePath("/admin/toggles/registration");
return { success: true, statusSet: rsvpLimit };
});
18 changes: 10 additions & 8 deletions apps/web/src/app/admin/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,16 @@ export default async function AdminLayout({ children }: AdminLayoutProps) {
<ClientToast />
<div className="fixed z-20 grid h-16 w-full grid-cols-2 bg-nav px-5">
<div className="flex items-center gap-x-4">
<Image
src={c.icon.svg}
alt={c.hackathonName + " Logo"}
width={32}
height={32}
/>
<div className="h-[45%] w-[2px] rotate-[25deg] bg-muted-foreground" />
<h2 className="font-bold tracking-tight">Admin</h2>
<Link href={"/"} className="mr-5 flex items-center gap-x-2">
<Image
src={c.icon.svg}
alt={c.hackathonName + " Logo"}
width={32}
height={32}
/>
<div className="h-[45%] w-[2px] rotate-[25deg] bg-muted-foreground" />
<h2 className="font-bold tracking-tight">Admin</h2>
</Link>
</div>
<div className="hidden items-center justify-end gap-x-4 md:flex">
<Link href={"/"}>
Expand Down
9 changes: 8 additions & 1 deletion apps/web/src/app/admin/toggles/registration/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { RegistrationToggles } from "@/components/admin/toggles/RegistrationSettings";
import { kv } from "@vercel/kv";
import { parseRedisBoolean } from "@/lib/utils/server/redis";
import { parseRedisBoolean, parseRedisNumber } from "@/lib/utils/server/redis";
import c from "config";

export default async function Page() {
const pipe = kv.pipeline();
Expand All @@ -12,10 +13,12 @@ export default async function Page() {
defaultRegistrationEnabled,
defaultSecretRegistrationEnabled,
defaultRSVPsEnabled,
defaultRSVPLimit,
]: (string | null)[] = await kv.mget(
"config:registration:registrationEnabled",
"config:registration:secretRegistrationEnabled",
"config:registration:allowRSVPs",
"config:registration:maxRSVPs",
);

return (
Expand All @@ -38,6 +41,10 @@ export default async function Page() {
defaultRSVPsEnabled,
true,
)}
defaultRSVPLimit={parseRedisNumber(
defaultRSVPLimit,
c.rsvpDefaultLimit,
)}
/>
</div>
);
Expand Down
8 changes: 7 additions & 1 deletion apps/web/src/app/admin/users/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Image from "next/image";
import { Button } from "@/components/shadcn/ui/button";
import { Badge } from "@/components/shadcn/ui/badge";
import { Info } from "lucide-react";
import { Info, CalendarCheck } from "lucide-react";
import Link from "next/link";
import UpdateRoleDialog from "@/components/admin/users/UpdateRoleDialog";
import {
Expand Down Expand Up @@ -88,6 +88,12 @@ export default async function Page({ params }: { params: { slug: string } }) {
.slice(1)
.join(" ")}
</Badge>
{user.isRSVPed && (
<Badge className="no-select bg-gradient-to-r from-teal-400 to-blue-500 bg-clip-padding text-center hover:from-teal-500 hover:to-blue-600">
<CalendarCheck className="mr-1 h-3 w-3" />
RSVP
</Badge>
)}
</div>
</div>
<div className="col-span-2 overflow-x-hidden">
Expand Down
40 changes: 29 additions & 11 deletions apps/web/src/app/rsvp/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import ConfirmDialogue from "@/components/rsvp/ConfirmDialogue";
import c from "config";
import { auth } from "@clerk/nextjs";
import { redirect } from "next/navigation";
import { db } from "db";
import { count, db } from "db";
import { eq } from "db/drizzle";
import { userCommonData } from "db/schema";
import ClientToast from "@/components/shared/ClientToast";
import { SignedOut, RedirectToSignIn } from "@clerk/nextjs";
import { kv } from "@vercel/kv";
import { parseRedisBoolean } from "@/lib/utils/server/redis";
import { parseRedisBoolean, parseRedisNumber } from "@/lib/utils/server/redis";
import Link from "next/link";
import { Button } from "@/components/shadcn/ui/button";
import { getUser } from "db/functions";
Expand Down Expand Up @@ -40,16 +40,34 @@ export default async function RsvpPage({
return redirect("/i/approval");
}

const rsvpEnabled = await kv.get("config:registration:allowRSVPs");
const rsvpEnabled = parseRedisBoolean(
(await kv.get("config:registration:allowRSVPs")) as
| string
| boolean
| null
| undefined,
true,
);

// TODO: fix type jank here
if (
parseRedisBoolean(
rsvpEnabled as string | boolean | null | undefined,
true,
) === true ||
user.isRSVPed === true
) {
let isRsvpPossible = false;

if (rsvpEnabled === true) {
const rsvpLimit = parseRedisNumber(
await kv.get("config:registration:maxRSVPs"),
c.rsvpDefaultLimit,
);

const rsvpUserCount = await db
.select({ count: count() })
.from(userCommonData)
.where(eq(userCommonData.isRSVPed, true))
.limit(rsvpLimit)
.then((result) => result[0].count);

isRsvpPossible = rsvpUserCount < rsvpLimit;
}

if (isRsvpPossible || user.isRSVPed === true) {
return (
<>
<ClientToast />
Expand Down
28 changes: 28 additions & 0 deletions apps/web/src/components/admin/toggles/RegistrationSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,22 @@ import {
toggleRegistrationMessageEnabled,
toggleSecretRegistrationEnabled,
toggleRSVPs,
setRSVPLimit,
} from "@/actions/admin/registration-actions";
import { UpdateItemWithConfirmation } from "./UpdateItemWithConfirmation";

interface RegistrationTogglesProps {
defaultRegistrationEnabled: boolean;
defaultSecretRegistrationEnabled: boolean;
defaultRSVPsEnabled: boolean;
defaultRSVPLimit: number;
}

export function RegistrationToggles({
defaultSecretRegistrationEnabled,
defaultRegistrationEnabled,
defaultRSVPsEnabled,
defaultRSVPLimit,
}: RegistrationTogglesProps) {
const {
execute: executeToggleSecretRegistrationEnabled,
Expand Down Expand Up @@ -57,6 +61,16 @@ export function RegistrationToggles({
},
});

const {
execute: executeSetRSVPLimit,
optimisticState: SetRSVPLimitOptimisticData,
} = useOptimisticAction(setRSVPLimit, {
currentState: { success: true, statusSet: defaultRSVPLimit },
updateFn: (state, { rsvpLimit }) => {
return { statusSet: rsvpLimit, success: true };
},
});

return (
<>
<div className="rounded-lg border-2 border-muted px-5 py-10">
Expand Down Expand Up @@ -116,6 +130,20 @@ export function RegistrationToggles({
}}
/>
</div>
<div className="flex items-center border-b border-t border-t-muted py-4">
<p className="mr-auto text-sm font-bold">RSVP Limit</p>
<UpdateItemWithConfirmation
defaultValue={SetRSVPLimitOptimisticData.statusSet}
enabled={toggleRSVPsOptimisticData.statusSet}
type="number"
onSubmit={(newLimit) => {
toast.success(
`Hacker RSVP limit successfully changed to ${newLimit}!`,
);
executeSetRSVPLimit({ rsvpLimit: newLimit });
}}
/>
</div>
</div>
</div>
</>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { useState } from "react";
import { Input } from "@/components/shadcn/ui/input";
import { Button } from "@/components/shadcn/ui/button";

interface UpdateItemWithConfirmationBaseProps<T extends number | string> {
defaultValue: T;
enabled: boolean;
onSubmit: (value: T) => void;
}

type UpdateItemWithConfirmationProps =
| ({ type: "string" } & UpdateItemWithConfirmationBaseProps<string>)
| ({ type: "number" } & UpdateItemWithConfirmationBaseProps<number>);

export function UpdateItemWithConfirmation({
type,
defaultValue,
onSubmit,
enabled,
}: UpdateItemWithConfirmationProps) {
const [valueUpdated, setValueUpdated] = useState(false);
const [value, setValue] = useState(defaultValue.toString());

return (
<div className="flex max-h-8 items-center gap-2">
<Input
className="text-md w-24 text-center font-bold sm:w-40"
value={value}
disabled={!enabled}
onChange={({ target: { value: updated } }) => {
// Ignore the change if the value is a non numeric character.
if (type === "number" && /[^0-9]/.test(updated)) {
setValue(value);
return;
}

setValue(updated);

/* Avoid allowing the user to update the default value to itself.
* Also disallow the user from sending a zero length input. */
setValueUpdated(
updated !== defaultValue.toString() &&
updated.length !== 0,
);
}}
/>
<Button
className="text-sm font-bold"
type="button"
variant="default"
title="Apply Changes"
disabled={!valueUpdated || !enabled}
onClick={() => {
if (type === "number") {
onSubmit(parseInt(value));
} else {
onSubmit(value);
}

setValueUpdated(false);
}}
>
Apply
</Button>
</div>
);
}
9 changes: 5 additions & 4 deletions apps/web/src/components/dash/shared/ProfileButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { auth, SignOutButton } from "@clerk/nextjs";
import Link from "next/link";
import { DropdownSwitcher } from "@/components/shared/ThemeSwitcher";
import { getUser } from "db/functions";
import { clientLogOut } from "@/lib/utils/client/shared";

export default async function ProfileButton() {
const clerkUser = auth();
Expand Down Expand Up @@ -68,9 +69,9 @@ export default async function ProfileButton() {
</Link>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<SignOutButton>
<SignOutButton signOutCallback={clientLogOut}>
<DropdownMenuItem className="cursor-pointer hover:!bg-destructive">
Log out
Sign out
</DropdownMenuItem>
</SignOutButton>
</DropdownMenuContent>
Expand Down Expand Up @@ -122,9 +123,9 @@ export default async function ProfileButton() {
</Link>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<SignOutButton>
<SignOutButton signOutCallback={clientLogOut}>
<DropdownMenuItem className="cursor-pointer hover:!bg-destructive">
Log out
Sign out
</DropdownMenuItem>
</SignOutButton>
</DropdownMenuContent>
Expand Down
12 changes: 6 additions & 6 deletions apps/web/src/components/landing/MLHBadge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ export default function MLHBadge() {
id="mlh-trust-badge"
className="absolute right-5 top-0 z-50 w-[10%] min-w-[60px] max-w-[100px]"
// style="display:block;max-width:100px;min-width:60px;position:fixed;right:50px;top:0;width:10%;z-index:10000"
href="https://mlh.io/na?utm_source=na-hackathon&utm_medium=TrustBadge&utm_campaign=2024-season&utm_content=black"
href="https://mlh.io/na?utm_source=na-hackathon&utm_medium=TrustBadge&utm_campaign=2025-season&utm_content=black"
target="_blank"
>
<Image
src="https://s3.amazonaws.com/logged-assets/trust-badge/2024/mlh-trust-badge-2024-black.svg"
alt="Major League Hacking 2024 Hackathon Season"
src="https://s3.amazonaws.com/logged-assets/trust-badge/2025/mlh-trust-badge-2025-black.svg"
alt="Major League Hacking 2025 Hackathon Season"
width={0}
height={0}
className="aspect-auto h-auto w-full"
Expand All @@ -27,12 +27,12 @@ export default function MLHBadge() {
id="mlh-trust-badge"
className="absolute right-5 top-0 z-50 w-[10%] min-w-[60px] max-w-[100px]"
// style="display:block;max-width:100px;min-width:60px;position:fixed;right:50px;top:0;width:10%;z-index:10000"
href="https://mlh.io/na?utm_source=na-hackathon&utm_medium=TrustBadge&utm_campaign=2024-season&utm_content=white"
href="https://mlh.io/na?utm_source=na-hackathon&utm_medium=TrustBadge&utm_campaign=2025-season&utm_content=white"
target="_blank"
>
<Image
src="https://s3.amazonaws.com/logged-assets/trust-badge/2024/mlh-trust-badge-2024-white.svg"
alt="Major League Hacking 2024 Hackathon Season"
src="https://s3.amazonaws.com/logged-assets/trust-badge/2025/mlh-trust-badge-2025-white.svg"
alt="Major League Hacking 2025 Hackathon Season"
width={0}
height={0}
className="aspect-auto h-auto w-full"
Expand Down
12 changes: 6 additions & 6 deletions apps/web/src/components/shared/ProfileButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { DropdownSwitcher } from "@/components/shared/ThemeSwitcher";
import DefaultDropdownTrigger from "../dash/shared/DefaultDropDownTrigger";
import MobileNavBarLinks from "./MobileNavBarLinks";
import { getUser } from "db/functions";
import { redirect } from "next/navigation";
import { clientLogOut } from "@/lib/utils/client/shared";

export default async function ProfileButton() {
const clerkUser = await auth();
Expand Down Expand Up @@ -102,9 +102,9 @@ export default async function ProfileButton() {
</DropdownMenuGroup>
<DropdownMenuSeparator className="bg-[rgb(228,228,231)] dark:bg-[rgb(39,39,42)]" />
<DropdownSwitcher />
<SignOutButton>
<SignOutButton signOutCallback={clientLogOut}>
<DropdownMenuItem className="cursor-pointer hover:!bg-destructive">
Log out
Sign out
</DropdownMenuItem>
</SignOutButton>
</DropdownMenuContent>
Expand Down Expand Up @@ -176,9 +176,9 @@ export default async function ProfileButton() {
</DropdownMenuGroup>
<DropdownMenuSeparator className="bg-[rgb(228,228,231)] dark:bg-[rgb(39,39,42)]" />
<DropdownSwitcher />
<SignOutButton>
<DropdownMenuItem className="cursor-pointer hover:!bg-destructive">
Log out
<SignOutButton signOutCallback={clientLogOut}>
<DropdownMenuItem className="cursor-pointer text-red-500 hover:!bg-destructive hover:text-muted">
Sign out
</DropdownMenuItem>
</SignOutButton>
</DropdownMenuContent>
Expand Down
5 changes: 5 additions & 0 deletions apps/web/src/lib/utils/client/shared.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import { redirect } from "next/navigation";
export function getClientTimeZone(vercelIPTimeZone: string | null) {
return vercelIPTimeZone ?? Intl.DateTimeFormat().resolvedOptions().timeZone;
}
export async function clientLogOut() {
"use server";
redirect("/");
}
Loading

0 comments on commit e883938

Please sign in to comment.