From 11d7908c238509ff7e5129a7c4cf3de1ccc6d431 Mon Sep 17 00:00:00 2001 From: joshuasilva414 Date: Fri, 27 Sep 2024 14:26:12 -0500 Subject: [PATCH] Basic upgrade --- apps/web/package.json | 2 +- apps/web/src/actions/admin/modify-nav-item.ts | 42 +- .../src/actions/admin/registration-actions.ts | 36 +- .../actions/admin/scanner-admin-actions.ts | 113 +++--- apps/web/src/actions/admin/user-actions.ts | 81 ++-- apps/web/src/actions/discord-verify.ts | 15 +- apps/web/src/actions/rsvp.ts | 5 +- apps/web/src/actions/teams.ts | 5 +- apps/web/src/actions/user-profile-mod.ts | 91 +++-- .../admin/scanner/CheckinScanner.tsx | 6 +- .../components/admin/scanner/PassScanner.tsx | 2 +- .../admin/users/ApproveUserButton.tsx | 2 +- .../admin/users/UpdateRoleDialog.tsx | 2 +- .../components/dash/team/LeaveTeamButton.tsx | 2 +- .../src/components/rsvp/ConfirmDialogue.tsx | 2 +- .../components/settings/AccountSettings.tsx | 2 +- .../settings/DiscordVerifyButton.tsx | 2 +- .../components/settings/ProfileSettings.tsx | 3 +- apps/web/src/lib/safe-action.ts | 41 +- pnpm-lock.yaml | 371 +++++++++++++----- 20 files changed, 512 insertions(+), 313 deletions(-) diff --git a/apps/web/package.json b/apps/web/package.json index aae8d62a..d2c3ed25 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -63,7 +63,7 @@ "lucide-react": "^0.411.0", "nanoid": "^5.0.7", "next": "14.2.5", - "next-safe-action": "^5.2.3", + "next-safe-action": "^7.9.3", "no-profanity": "^1.5.1", "pg": "^8.12.0", "postcss": "8.4.39", diff --git a/apps/web/src/actions/admin/modify-nav-item.ts b/apps/web/src/actions/admin/modify-nav-item.ts index b52e253b..5dc3a48e 100644 --- a/apps/web/src/actions/admin/modify-nav-item.ts +++ b/apps/web/src/actions/admin/modify-nav-item.ts @@ -13,9 +13,9 @@ const metadataSchema = z.object({ // Maybe a better way to do this for revalidation? Who knows. const navAdminPage = "/admin/toggles/landing"; -export const setItem = adminAction( - metadataSchema, - async ({ name, url }, { user, userId }) => { +export const setItem = adminAction + .schema(metadataSchema) + .action(async ({ parsedInput: { name, url }, ctx: { user, userId } }) => { await kv.sadd("config:navitemslist", encodeURIComponent(name)); await kv.hset(`config:navitems:${encodeURIComponent(name)}`, { url, @@ -24,12 +24,11 @@ export const setItem = adminAction( }); revalidatePath(navAdminPage); return { success: true }; - }, -); + }); -export const removeItem = adminAction( - z.string(), - async (name, { user, userId }) => { +export const removeItem = adminAction + .schema(z.string()) + .action(async ({ parsedInput: name, ctx: { user, userId } }) => { const pipe = kv.pipeline(); pipe.srem("config:navitemslist", encodeURIComponent(name)); pipe.del(`config:navitems:${encodeURIComponent(name)}`); @@ -37,16 +36,19 @@ export const removeItem = adminAction( // await new Promise((resolve) => setTimeout(resolve, 1500)); revalidatePath(navAdminPage); return { success: true }; - }, -); + }); -export const toggleItem = adminAction( - z.object({ name: z.string(), statusToSet: z.boolean() }), - async ({ name, statusToSet }, { user, userId }) => { - await kv.hset(`config:navitems:${encodeURIComponent(name)}`, { - enabled: statusToSet, - }); - revalidatePath(navAdminPage); - return { success: true, itemStatus: statusToSet }; - }, -); +export const toggleItem = adminAction + .schema(z.object({ name: z.string(), statusToSet: z.boolean() })) + .action( + async ({ + parsedInput: { name, statusToSet }, + ctx: { user, userId }, + }) => { + await kv.hset(`config:navitems:${encodeURIComponent(name)}`, { + enabled: statusToSet, + }); + revalidatePath(navAdminPage); + return { success: true, itemStatus: statusToSet }; + }, + ); diff --git a/apps/web/src/actions/admin/registration-actions.ts b/apps/web/src/actions/admin/registration-actions.ts index 9388bbba..f41c10d1 100644 --- a/apps/web/src/actions/admin/registration-actions.ts +++ b/apps/web/src/actions/admin/registration-actions.ts @@ -9,38 +9,34 @@ const defaultRegistrationToggleSchema = z.object({ enabled: z.boolean(), }); -export const toggleRegistrationEnabled = adminAction( - defaultRegistrationToggleSchema, - async ({ enabled }, { user, userId }) => { +export const toggleRegistrationEnabled = adminAction + .schema(defaultRegistrationToggleSchema) + .action(async ({ parsedInput: { enabled }, ctx: { user, userId } }) => { await kv.set("config:registration:registrationEnabled", enabled); revalidatePath("/admin/toggles/registration"); return { success: true, statusSet: enabled }; - }, -); + }); -export const toggleRegistrationMessageEnabled = adminAction( - defaultRegistrationToggleSchema, - async ({ enabled }, { user, userId }) => { +export const toggleRegistrationMessageEnabled = adminAction + .schema(defaultRegistrationToggleSchema) + .action(async ({ parsedInput: { enabled }, ctx: { user, userId } }) => { await kv.set("config:registration:registrationMessageEnabled", enabled); revalidatePath("/admin/toggles/registration"); return { success: true, statusSet: enabled }; - }, -); + }); -export const toggleSecretRegistrationEnabled = adminAction( - defaultRegistrationToggleSchema, - async ({ enabled }, { user, userId }) => { +export const toggleSecretRegistrationEnabled = adminAction + .schema(defaultRegistrationToggleSchema) + .action(async ({ parsedInput: { enabled }, ctx: { user, userId } }) => { await kv.set("config:registration:secretRegistrationEnabled", enabled); revalidatePath("/admin/toggles/registration"); return { success: true, statusSet: enabled }; - }, -); + }); -export const toggleRSVPs = adminAction( - defaultRegistrationToggleSchema, - async ({ enabled }, { user, userId }) => { +export const toggleRSVPs = adminAction + .schema(defaultRegistrationToggleSchema) + .action(async ({ parsedInput: { enabled }, ctx: { user, userId } }) => { await kv.set("config:registration:allowRSVPs", enabled); revalidatePath("/admin/toggles/registration"); return { success: true, statusSet: enabled }; - }, -); + }); diff --git a/apps/web/src/actions/admin/scanner-admin-actions.ts b/apps/web/src/actions/admin/scanner-admin-actions.ts index 2faeb2e3..fb5d46a2 100644 --- a/apps/web/src/actions/admin/scanner-admin-actions.ts +++ b/apps/web/src/actions/admin/scanner-admin-actions.ts @@ -5,51 +5,72 @@ import { z } from "zod"; import { db, sql } from "db"; import { scans, userCommonData } from "db/schema"; import { eq, and } from "db/drizzle"; -export const createScan = adminAction( - z.object({ - eventID: z.number(), - userID: z.string(), - creationTime: z.date(), - countToSet: z.number(), - alreadyExists: z.boolean(), - }), - async ( - { eventID, userID, creationTime, countToSet, alreadyExists }, - { user, userId }, - ) => { - if (alreadyExists) { - await db - .update(scans) - .set({ count: countToSet, updatedAt: creationTime }) - .where( - and(eq(scans.eventID, eventID), eq(scans.userID, userID)), - ); - } else { - await db.insert(scans).values({ - userID: userID, - updatedAt: creationTime, - count: 1, - eventID: eventID, - }); - } - return { success: true }; - }, -); +export const createScan = adminAction + .schema( + z.object({ + eventID: z.number(), + userID: z.string(), + creationTime: z.date(), + countToSet: z.number(), + alreadyExists: z.boolean(), + }), + ) + .action( + async ({ + parsedInput: { + eventID, + userID, + creationTime, + countToSet, + alreadyExists, + }, + ctx: { user, userId }, + }) => { + if (alreadyExists) { + await db + .update(scans) + .set({ count: countToSet, updatedAt: creationTime }) + .where( + and( + eq(scans.eventID, eventID), + eq(scans.userID, userID), + ), + ); + } else { + await db.insert(scans).values({ + userID: userID, + updatedAt: creationTime, + count: 1, + eventID: eventID, + }); + } + return { success: true }; + }, + ); -export const getScan = adminAction( - z.object({ eventID: z.number(), userID: z.string() }), - async ({ eventID, userID }, { user, userId: adminUserID }) => { - const scan = await db.query.scans.findFirst({ - where: and(eq(scans.eventID, eventID), eq(scans.userID, userID)), - }); - return scan; - }, -); +export const getScan = adminAction + .schema(z.object({ eventID: z.number(), userID: z.string() })) + .action( + async ({ + parsedInput: { eventID, userID }, + ctx: { user, userId: adminUserID }, + }) => { + const scan = await db.query.scans.findFirst({ + where: and( + eq(scans.eventID, eventID), + eq(scans.userID, userID), + ), + }); + return scan; + }, + ); -export const checkInUser = adminAction(z.string(), async (user) => { - // Set checkinTimestamp - return await db - .update(userCommonData) - .set({ checkinTimestamp: sql`now()` }) - .where(eq(userCommonData.clerkID, user)); -}); +export const checkInUser = adminAction + .schema(z.string()) + .action(async ({ parsedInput: user }) => { + // Set checkinTimestamp + return await db + .update(userCommonData) + .set({ checkinTimestamp: sql`now()` }) + .where(eq(userCommonData.clerkID, user)); + }); diff --git a/apps/web/src/actions/admin/user-actions.ts b/apps/web/src/actions/admin/user-actions.ts index 0e650697..ccf962b4 100644 --- a/apps/web/src/actions/admin/user-actions.ts +++ b/apps/web/src/actions/admin/user-actions.ts @@ -8,39 +8,50 @@ import { db } from "db"; import { eq } from "db/drizzle"; import { revalidatePath } from "next/cache"; -export const updateRole = adminAction( - z.object({ - userIDToUpdate: z.string(), - roleToSet: z.enum(perms), - }), - async ({ userIDToUpdate, roleToSet }, { user, userId }) => { - if ( - user.role !== "super_admin" && - (roleToSet === "super_admin" || roleToSet === "admin") - ) { - throw new Error("You are not allowed to do this"); - } - await db - .update(userCommonData) - .set({ role: roleToSet }) - .where(eq(userCommonData.clerkID, userIDToUpdate)); - revalidatePath(`/admin/users/${userIDToUpdate}`); - return { success: true }; - }, -); +export const updateRole = adminAction + .schema( + z.object({ + userIDToUpdate: z.string(), + roleToSet: z.enum(perms), + }), + ) + .action( + async ({ + parsedInput: { userIDToUpdate, roleToSet }, + ctx: { user, userId }, + }) => { + if ( + user.role !== "super_admin" && + (roleToSet === "super_admin" || roleToSet === "admin") + ) { + throw new Error("You are not allowed to do this"); + } + await db + .update(userCommonData) + .set({ role: roleToSet }) + .where(eq(userCommonData.clerkID, userIDToUpdate)); + revalidatePath(`/admin/users/${userIDToUpdate}`); + 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(userCommonData) - .set({ isApproved: approved }) - .where(eq(userCommonData.clerkID, userIDToUpdate)); - revalidatePath(`/admin/users/${userIDToUpdate}`); - return { success: true }; - }, -); +export const setUserApproval = adminAction + .schema( + z.object({ + userIDToUpdate: z.string().min(1), + approved: z.boolean(), + }), + ) + .action( + async ({ + parsedInput: { userIDToUpdate, approved }, + ctx: { user, userId }, + }) => { + await db + .update(userCommonData) + .set({ isApproved: approved }) + .where(eq(userCommonData.clerkID, userIDToUpdate)); + revalidatePath(`/admin/users/${userIDToUpdate}`); + return { success: true }; + }, + ); diff --git a/apps/web/src/actions/discord-verify.ts b/apps/web/src/actions/discord-verify.ts index c822885b..970cd8c1 100644 --- a/apps/web/src/actions/discord-verify.ts +++ b/apps/web/src/actions/discord-verify.ts @@ -7,11 +7,13 @@ import { eq, and } from "db/drizzle"; import { discordVerification } from "db/schema"; import { env } from "@/env"; -export const confirmVerifyDiscord = authenticatedAction( - z.object({ - code: z.string().min(20).max(20), - }), - async ({ code }, { userId }) => { +export const confirmVerifyDiscord = authenticatedAction + .schema( + z.object({ + code: z.string().min(20).max(20), + }), + ) + .action(async ({ parsedInput: { code }, ctx: { userId } }) => { const verification = await db.query.discordVerification.findFirst({ where: and( eq(discordVerification.code, code), @@ -49,5 +51,4 @@ export const confirmVerifyDiscord = authenticatedAction( return { success: true, }; - }, -); + }); diff --git a/apps/web/src/actions/rsvp.ts b/apps/web/src/actions/rsvp.ts index a4a30b6d..6b93a32b 100644 --- a/apps/web/src/actions/rsvp.ts +++ b/apps/web/src/actions/rsvp.ts @@ -7,9 +7,8 @@ import { eq } from "db/drizzle"; import { userCommonData } from "db/schema"; import { getUser } from "db/functions"; -export const rsvpMyself = authenticatedAction( - z.any(), - async (_, { userId }) => { +export const rsvpMyself = authenticatedAction.action( + async ({ ctx: { userId } }) => { const user = await getUser(userId); if (!user) throw new Error("User not found"); diff --git a/apps/web/src/actions/teams.ts b/apps/web/src/actions/teams.ts index 6adc78d0..5779ff1b 100644 --- a/apps/web/src/actions/teams.ts +++ b/apps/web/src/actions/teams.ts @@ -10,9 +10,8 @@ import { eq } from "db/drizzle"; import { revalidatePath } from "next/cache"; import { getHacker } from "db/functions"; -export const leaveTeam = authenticatedAction( - z.null(), - async (_, { userId }) => { +export const leaveTeam = authenticatedAction.action( + async ({ ctx: { userId } }) => { const user = await getHacker(userId, false); if (!user) throw new Error("User not found"); diff --git a/apps/web/src/actions/user-profile-mod.ts b/apps/web/src/actions/user-profile-mod.ts index fb6eb032..13dea87a 100644 --- a/apps/web/src/actions/user-profile-mod.ts +++ b/apps/web/src/actions/user-profile-mod.ts @@ -11,12 +11,14 @@ import { revalidatePath } from "next/cache"; import { getUser } from "db/functions"; // TODO: Add skill updating -export const modifyRegistrationData = authenticatedAction( - z.object({ - bio: z.string().max(500), - skills: z.string().max(100), - }), - async ({ bio, skills }, { userId }) => { +export const modifyRegistrationData = authenticatedAction + .schema( + z.object({ + bio: z.string().max(500), + skills: z.string().max(100), + }), + ) + .action(async ({ parsedInput: { bio, skills }, ctx: { userId } }) => { const user = await getUser(userId); if (!user) throw new Error("User not found"); @@ -25,43 +27,48 @@ export const modifyRegistrationData = authenticatedAction( .set({ bio }) .where(eq(userCommonData.clerkID, user.clerkID)); return { success: true, newbio: bio }; - }, -); + }); -export const modifyAccountSettings = authenticatedAction( - z.object({ - firstName: z.string().min(1).max(50), - lastName: z.string().min(1).max(50), - }), - async ({ firstName, lastName }, { userId }) => { - const user = await getUser(userId); - if (!user) throw new Error("User not found"); +export const modifyAccountSettings = authenticatedAction + .schema( + z.object({ + firstName: z.string().min(1).max(50), + lastName: z.string().min(1).max(50), + }), + ) + .action( + async ({ parsedInput: { firstName, lastName }, ctx: { userId } }) => { + const user = await getUser(userId); + if (!user) throw new Error("User not found"); - await db - .update(userCommonData) - .set({ firstName, lastName }) - .where(eq(userCommonData.clerkID, userId)); - return { - success: true, - newFirstName: firstName, - newLastName: lastName, - }; - }, -); + await db + .update(userCommonData) + .set({ firstName, lastName }) + .where(eq(userCommonData.clerkID, userId)); + return { + success: true, + newFirstName: firstName, + newLastName: lastName, + }; + }, + ); -export const updateProfileImage = authenticatedAction( - z.object({ fileBase64: z.string(), fileName: z.string() }), - async ({ fileBase64, fileName }, { userId }) => { - const image = await decodeBase64AsFile(fileBase64, fileName); - const user = await getUser(userId); - if (!user) throw new Error("User not found"); +export const updateProfileImage = authenticatedAction + .schema(z.object({ fileBase64: z.string(), fileName: z.string() })) + .action( + async ({ parsedInput: { fileBase64, fileName }, ctx: { userId } }) => { + const image = await decodeBase64AsFile(fileBase64, fileName); + const user = await getUser(userId); + if (!user) throw new Error("User not found"); - const blobUpload = await put(image.name, image, { access: "public" }); - await db - .update(userCommonData) - .set({ profilePhoto: blobUpload.url }) - .where(eq(userCommonData.clerkID, user.clerkID)); - revalidatePath("/settings/profile"); - return { success: true }; - }, -); + const blobUpload = await put(image.name, image, { + access: "public", + }); + await db + .update(userCommonData) + .set({ profilePhoto: blobUpload.url }) + .where(eq(userCommonData.clerkID, user.clerkID)); + revalidatePath("/settings/profile"); + return { success: true }; + }, + ); diff --git a/apps/web/src/components/admin/scanner/CheckinScanner.tsx b/apps/web/src/components/admin/scanner/CheckinScanner.tsx index b07abc5a..d13c2be4 100644 --- a/apps/web/src/components/admin/scanner/CheckinScanner.tsx +++ b/apps/web/src/components/admin/scanner/CheckinScanner.tsx @@ -4,7 +4,7 @@ import { useState, useEffect } from "react"; import { QrScanner } from "@yudiel/react-qr-scanner"; import superjson from "superjson"; import { checkInUser } from "@/actions/admin/scanner-admin-actions"; -import { useAction } from "next-safe-action/hook"; +import { useAction } from "next-safe-action/hooks"; import { type QRDataInterface } from "@/lib/utils/shared/qr"; import type { User } from "db/types"; @@ -44,7 +44,7 @@ export default function CheckinScanner({ hasRSVP, }: CheckinScannerProps) { const [scanLoading, setScanLoading] = useState(false); - const { execute: runScanAction } = useAction(checkInUser, {}); + // const { execute: runScanAction } = useAction(checkInUser, {}); const [proceed, setProceed] = useState(hasRSVP); useEffect(() => { if (hasScanned) { @@ -66,7 +66,7 @@ export default function CheckinScanner({ return alert("User Already Checked in!"); } else { // TODO: make this a little more typesafe - runScanAction(scanUser?.clerkID!); + checkInUser(scanUser?.clerkID!); } toast.success("Successfully Scanned User In"); router.replace(`${path}`); diff --git a/apps/web/src/components/admin/scanner/PassScanner.tsx b/apps/web/src/components/admin/scanner/PassScanner.tsx index adf5bf63..0c3214d9 100644 --- a/apps/web/src/components/admin/scanner/PassScanner.tsx +++ b/apps/web/src/components/admin/scanner/PassScanner.tsx @@ -4,7 +4,7 @@ import { useState, useEffect } from "react"; import { QrScanner } from "@yudiel/react-qr-scanner"; import superjson from "superjson"; import { createScan } from "@/actions/admin/scanner-admin-actions"; -import { useAction } from "next-safe-action/hook"; +import { useAction } from "next-safe-action/hooks"; import { type QRDataInterface } from "@/lib/utils/shared/qr"; import type { Scan, Event, Hacker } from "db/types"; import c from "config"; diff --git a/apps/web/src/components/admin/users/ApproveUserButton.tsx b/apps/web/src/components/admin/users/ApproveUserButton.tsx index 136cdc29..7267cc6b 100644 --- a/apps/web/src/components/admin/users/ApproveUserButton.tsx +++ b/apps/web/src/components/admin/users/ApproveUserButton.tsx @@ -1,7 +1,7 @@ "use client"; import { Button } from "@/components/shadcn/ui/button"; -import { useAction } from "next-safe-action/hook"; +import { useAction } from "next-safe-action/hooks"; import { setUserApproval } from "@/actions/admin/user-actions"; import { toast } from "sonner"; diff --git a/apps/web/src/components/admin/users/UpdateRoleDialog.tsx b/apps/web/src/components/admin/users/UpdateRoleDialog.tsx index edb2d349..edf59a25 100644 --- a/apps/web/src/components/admin/users/UpdateRoleDialog.tsx +++ b/apps/web/src/components/admin/users/UpdateRoleDialog.tsx @@ -18,7 +18,7 @@ import { import { Button } from "@/components/shadcn/ui/button"; import { perms } from "config"; import { toast } from "sonner"; -import { useAction } from "next-safe-action/hook"; +import { useAction } from "next-safe-action/hooks"; import { updateRole } from "@/actions/admin/user-actions"; import { useState } from "react"; import { titleCase } from "title-case"; diff --git a/apps/web/src/components/dash/team/LeaveTeamButton.tsx b/apps/web/src/components/dash/team/LeaveTeamButton.tsx index 30aef26e..d3223e0c 100644 --- a/apps/web/src/components/dash/team/LeaveTeamButton.tsx +++ b/apps/web/src/components/dash/team/LeaveTeamButton.tsx @@ -3,7 +3,7 @@ import { LogOut } from "lucide-react"; import { Button } from "@/components/shadcn/ui/button"; import { leaveTeam } from "@/actions/teams"; -import { useAction } from "next-safe-action/hook"; +import { useAction } from "next-safe-action/hooks"; import { toast } from "sonner"; interface LeaveTeamButtonProps { diff --git a/apps/web/src/components/rsvp/ConfirmDialogue.tsx b/apps/web/src/components/rsvp/ConfirmDialogue.tsx index 0c8ca30c..7c944fad 100644 --- a/apps/web/src/components/rsvp/ConfirmDialogue.tsx +++ b/apps/web/src/components/rsvp/ConfirmDialogue.tsx @@ -4,7 +4,7 @@ import { useWindowSize } from "usehooks-ts"; import Confetti from "react-confetti"; import { Button } from "@/components/shadcn/ui/button"; import { useState, useEffect } from "react"; -import { useAction } from "next-safe-action/hook"; +import { useAction } from "next-safe-action/hooks"; import { rsvpMyself } from "@/actions/rsvp"; import { CheckCircleIcon } from "lucide-react"; import { toast } from "sonner"; diff --git a/apps/web/src/components/settings/AccountSettings.tsx b/apps/web/src/components/settings/AccountSettings.tsx index ab7269c3..f05151f9 100644 --- a/apps/web/src/components/settings/AccountSettings.tsx +++ b/apps/web/src/components/settings/AccountSettings.tsx @@ -5,7 +5,7 @@ import { Button } from "@/components/shadcn/ui/button"; import { Label } from "@/components/shadcn/ui/label"; import { toast } from "sonner"; import { useState } from "react"; -import { useAction } from "next-safe-action/hook"; +import { useAction } from "next-safe-action/hooks"; import { modifyAccountSettings } from "@/actions/user-profile-mod"; interface UserProps { diff --git a/apps/web/src/components/settings/DiscordVerifyButton.tsx b/apps/web/src/components/settings/DiscordVerifyButton.tsx index df30e051..5a70f7b8 100644 --- a/apps/web/src/components/settings/DiscordVerifyButton.tsx +++ b/apps/web/src/components/settings/DiscordVerifyButton.tsx @@ -1,7 +1,7 @@ "use client"; import { Button } from "@/components/shadcn/ui/button"; -import { useAction } from "next-safe-action/hook"; +import { useAction } from "next-safe-action/hooks"; import { confirmVerifyDiscord } from "@/actions/discord-verify"; import { useRouter, useSearchParams } from "next/navigation"; import { toast } from "sonner"; diff --git a/apps/web/src/components/settings/ProfileSettings.tsx b/apps/web/src/components/settings/ProfileSettings.tsx index 067c4071..792a6a3b 100644 --- a/apps/web/src/components/settings/ProfileSettings.tsx +++ b/apps/web/src/components/settings/ProfileSettings.tsx @@ -9,7 +9,7 @@ import { updateProfileImage, } from "@/actions/user-profile-mod"; import { useUser } from "@clerk/nextjs"; -import { useAction } from "next-safe-action/hook"; +import { useAction } from "next-safe-action/hooks"; import { toast } from "sonner"; import { useState } from "react"; import { encodeFileAsBase64 } from "@/lib/utils/shared/files"; @@ -18,7 +18,6 @@ interface ProfileSettingsProps { bio: string; university: string; } - export default function ProfileSettings({ bio, university, diff --git a/apps/web/src/lib/safe-action.ts b/apps/web/src/lib/safe-action.ts index 969324d0..90fb4611 100644 --- a/apps/web/src/lib/safe-action.ts +++ b/apps/web/src/lib/safe-action.ts @@ -1,29 +1,32 @@ -import { createSafeActionClient } from "next-safe-action"; +import { + createSafeActionClient, + returnValidationErrors, +} from "next-safe-action"; import { auth } from "@clerk/nextjs"; import { getUser } from "db/functions"; +import { z } from "zod"; export const publicAction = createSafeActionClient(); -export const adminAction = createSafeActionClient({ - async middleware() { - const { userId } = auth(); - if (!userId) throw new Error("Unauthorized (No UserID)"); - - const user = await getUser(userId); - if (!user || (user.role !== "admin" && user.role !== "super_admin")) { - throw new Error("Unauthorized (Not Admin)"); - } - - return { user, userId }; - }, -}); - -export const authenticatedAction = createSafeActionClient({ +export const authenticatedAction = publicAction.use( // TODO: Add registration check here? - async middleware() { + async ({ next, ctx }) => { const { userId } = auth(); - if (!userId) throw new Error("Unauthorized"); + if (!userId) + returnValidationErrors(z.null(), { + _errors: ["Unauthorized (No User ID)"], + }); // TODO: add check for registration - return { userId }; + return next({ ctx: { userId } }); }, +); + +export const adminAction = authenticatedAction.use(async ({ next, ctx }) => { + const user = await getUser(ctx.userId); + if (!user || (user.role !== "admin" && user.role !== "super_admin")) { + returnValidationErrors(z.null(), { + _errors: ["Unauthorized (Not Admin)"], + }); + } + return next({ ctx: { user, ...ctx } }); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d6605f83..1da6f5ab 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,5 +1,9 @@ lockfileVersion: '6.0' +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + importers: .: @@ -236,8 +240,8 @@ importers: specifier: 14.2.5 version: 14.2.5(@babel/core@7.24.5)(react-dom@18.3.1)(react@18.3.1) next-safe-action: - specifier: ^5.2.3 - version: 5.2.3(next@14.2.5)(react@18.3.1)(zod@3.23.8) + specifier: ^7.9.3 + version: 7.9.3(next@14.2.5)(react-dom@18.3.1)(react@18.3.1)(zod@3.23.8) no-profanity: specifier: ^1.5.1 version: 1.5.1 @@ -273,7 +277,7 @@ importers: version: 14.2.3(react@18.3.1) react-email: specifier: ^2.1.5 - version: 2.1.5(eslint@9.7.0) + version: 2.1.5(eslint@9.11.1) react-fast-marquee: specifier: ^1.6.5 version: 1.6.5(react-dom@18.3.1)(react@18.3.1) @@ -337,7 +341,7 @@ importers: devDependencies: esbuild-register: specifier: ^3.5.0 - version: 3.5.0(esbuild@0.23.0) + version: 3.5.0(esbuild@0.24.0) packages/config: {} @@ -354,7 +358,7 @@ importers: version: 0.31.2(@planetscale/database@1.18.0)(@types/react@18.3.3)(@vercel/postgres@0.9.0)(pg@8.12.0)(postgres@3.4.4)(react@18.3.1) esbuild-register: specifier: ^3.5.0 - version: 3.5.0(esbuild@0.23.0) + version: 3.5.0(esbuild@0.24.0) postgres: specifier: ^3.4.4 version: 3.4.4 @@ -1511,8 +1515,8 @@ packages: dev: false optional: true - /@esbuild/aix-ppc64@0.23.0: - resolution: {integrity: sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ==} + /@esbuild/aix-ppc64@0.24.0: + resolution: {integrity: sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] @@ -1546,8 +1550,8 @@ packages: dev: true optional: true - /@esbuild/android-arm64@0.23.0: - resolution: {integrity: sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ==} + /@esbuild/android-arm64@0.24.0: + resolution: {integrity: sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==} engines: {node: '>=18'} cpu: [arm64] os: [android] @@ -1581,8 +1585,8 @@ packages: dev: true optional: true - /@esbuild/android-arm@0.23.0: - resolution: {integrity: sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g==} + /@esbuild/android-arm@0.24.0: + resolution: {integrity: sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==} engines: {node: '>=18'} cpu: [arm] os: [android] @@ -1616,8 +1620,8 @@ packages: dev: true optional: true - /@esbuild/android-x64@0.23.0: - resolution: {integrity: sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ==} + /@esbuild/android-x64@0.24.0: + resolution: {integrity: sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==} engines: {node: '>=18'} cpu: [x64] os: [android] @@ -1651,8 +1655,8 @@ packages: dev: true optional: true - /@esbuild/darwin-arm64@0.23.0: - resolution: {integrity: sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow==} + /@esbuild/darwin-arm64@0.24.0: + resolution: {integrity: sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] @@ -1686,8 +1690,8 @@ packages: dev: true optional: true - /@esbuild/darwin-x64@0.23.0: - resolution: {integrity: sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ==} + /@esbuild/darwin-x64@0.24.0: + resolution: {integrity: sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==} engines: {node: '>=18'} cpu: [x64] os: [darwin] @@ -1721,8 +1725,8 @@ packages: dev: true optional: true - /@esbuild/freebsd-arm64@0.23.0: - resolution: {integrity: sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw==} + /@esbuild/freebsd-arm64@0.24.0: + resolution: {integrity: sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] @@ -1756,8 +1760,8 @@ packages: dev: true optional: true - /@esbuild/freebsd-x64@0.23.0: - resolution: {integrity: sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ==} + /@esbuild/freebsd-x64@0.24.0: + resolution: {integrity: sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] @@ -1791,8 +1795,8 @@ packages: dev: true optional: true - /@esbuild/linux-arm64@0.23.0: - resolution: {integrity: sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw==} + /@esbuild/linux-arm64@0.24.0: + resolution: {integrity: sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==} engines: {node: '>=18'} cpu: [arm64] os: [linux] @@ -1826,8 +1830,8 @@ packages: dev: true optional: true - /@esbuild/linux-arm@0.23.0: - resolution: {integrity: sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw==} + /@esbuild/linux-arm@0.24.0: + resolution: {integrity: sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==} engines: {node: '>=18'} cpu: [arm] os: [linux] @@ -1861,8 +1865,8 @@ packages: dev: true optional: true - /@esbuild/linux-ia32@0.23.0: - resolution: {integrity: sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA==} + /@esbuild/linux-ia32@0.24.0: + resolution: {integrity: sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==} engines: {node: '>=18'} cpu: [ia32] os: [linux] @@ -1896,8 +1900,8 @@ packages: dev: true optional: true - /@esbuild/linux-loong64@0.23.0: - resolution: {integrity: sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A==} + /@esbuild/linux-loong64@0.24.0: + resolution: {integrity: sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==} engines: {node: '>=18'} cpu: [loong64] os: [linux] @@ -1931,8 +1935,8 @@ packages: dev: true optional: true - /@esbuild/linux-mips64el@0.23.0: - resolution: {integrity: sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w==} + /@esbuild/linux-mips64el@0.24.0: + resolution: {integrity: sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] @@ -1966,8 +1970,8 @@ packages: dev: true optional: true - /@esbuild/linux-ppc64@0.23.0: - resolution: {integrity: sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw==} + /@esbuild/linux-ppc64@0.24.0: + resolution: {integrity: sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] @@ -2001,8 +2005,8 @@ packages: dev: true optional: true - /@esbuild/linux-riscv64@0.23.0: - resolution: {integrity: sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw==} + /@esbuild/linux-riscv64@0.24.0: + resolution: {integrity: sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] @@ -2036,8 +2040,8 @@ packages: dev: true optional: true - /@esbuild/linux-s390x@0.23.0: - resolution: {integrity: sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg==} + /@esbuild/linux-s390x@0.24.0: + resolution: {integrity: sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==} engines: {node: '>=18'} cpu: [s390x] os: [linux] @@ -2071,8 +2075,8 @@ packages: dev: true optional: true - /@esbuild/linux-x64@0.23.0: - resolution: {integrity: sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ==} + /@esbuild/linux-x64@0.24.0: + resolution: {integrity: sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==} engines: {node: '>=18'} cpu: [x64] os: [linux] @@ -2106,16 +2110,16 @@ packages: dev: true optional: true - /@esbuild/netbsd-x64@0.23.0: - resolution: {integrity: sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw==} + /@esbuild/netbsd-x64@0.24.0: + resolution: {integrity: sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] requiresBuild: true optional: true - /@esbuild/openbsd-arm64@0.23.0: - resolution: {integrity: sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ==} + /@esbuild/openbsd-arm64@0.24.0: + resolution: {integrity: sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] @@ -2149,8 +2153,8 @@ packages: dev: true optional: true - /@esbuild/openbsd-x64@0.23.0: - resolution: {integrity: sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg==} + /@esbuild/openbsd-x64@0.24.0: + resolution: {integrity: sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] @@ -2184,8 +2188,8 @@ packages: dev: true optional: true - /@esbuild/sunos-x64@0.23.0: - resolution: {integrity: sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA==} + /@esbuild/sunos-x64@0.24.0: + resolution: {integrity: sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] @@ -2219,8 +2223,8 @@ packages: dev: true optional: true - /@esbuild/win32-arm64@0.23.0: - resolution: {integrity: sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ==} + /@esbuild/win32-arm64@0.24.0: + resolution: {integrity: sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==} engines: {node: '>=18'} cpu: [arm64] os: [win32] @@ -2254,8 +2258,8 @@ packages: dev: true optional: true - /@esbuild/win32-ia32@0.23.0: - resolution: {integrity: sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA==} + /@esbuild/win32-ia32@0.24.0: + resolution: {integrity: sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==} engines: {node: '>=18'} cpu: [ia32] os: [win32] @@ -2289,14 +2293,24 @@ packages: dev: true optional: true - /@esbuild/win32-x64@0.23.0: - resolution: {integrity: sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g==} + /@esbuild/win32-x64@0.24.0: + resolution: {integrity: sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==} engines: {node: '>=18'} cpu: [x64] os: [win32] requiresBuild: true optional: true + /@eslint-community/eslint-utils@4.4.0(eslint@9.11.1): + resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + dependencies: + eslint: 9.11.1(jiti@1.21.6) + eslint-visitor-keys: 3.4.3 + dev: false + /@eslint-community/eslint-utils@4.4.0(eslint@9.7.0): resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2305,10 +2319,17 @@ packages: dependencies: eslint: 9.7.0 eslint-visitor-keys: 3.4.3 + dev: true /@eslint-community/regexpp@4.11.0: resolution: {integrity: sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + dev: true + + /@eslint-community/regexpp@4.11.1: + resolution: {integrity: sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + dev: false /@eslint/config-array@0.17.0: resolution: {integrity: sha512-A68TBu6/1mHHuc5YJL0U0VVeGNiklLAL6rRmhTCP2B5XjWLMnrX+HkO+IAXyHvks5cyyY1jjK5ITPQ1HGS2EVA==} @@ -2319,6 +2340,23 @@ packages: minimatch: 3.1.2 transitivePeerDependencies: - supports-color + dev: true + + /@eslint/config-array@0.18.0: + resolution: {integrity: sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + dependencies: + '@eslint/object-schema': 2.1.4 + debug: 4.3.7 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + dev: false + + /@eslint/core@0.6.0: + resolution: {integrity: sha512-8I2Q8ykA4J0x0o7cg67FPVnehcqWTBehu/lmY+bolPFHGjh49YzGBMXTvpqVgEbBdvNCSxj6iFgiIyHzf03lzg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + dev: false /@eslint/eslintrc@3.1.0: resolution: {integrity: sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==} @@ -2336,14 +2374,27 @@ packages: transitivePeerDependencies: - supports-color + /@eslint/js@9.11.1: + resolution: {integrity: sha512-/qu+TWz8WwPWc7/HcIJKi+c+MOm46GdVaSlTTQcaqaL53+GsoA6MxWp5PtTx48qbSP7ylM1Kn7nhvkugfJvRSA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + dev: false + /@eslint/js@9.7.0: resolution: {integrity: sha512-ChuWDQenef8OSFnvuxv0TCVxEwmu3+hPNKvM9B34qpM0rDRbjL8t5QkQeHHeAfsKQjuH9wS82WeCi1J/owatng==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + dev: true /@eslint/object-schema@2.1.4: resolution: {integrity: sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + /@eslint/plugin-kit@0.2.0: + resolution: {integrity: sha512-vH9PiIMMwvhCx31Af3HiGzsVNULDbyVkHXwlemn/B0TFj/00ho3y55efXrUZTfQipxoHC5u4xq6zblww1zm1Ig==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + dependencies: + levn: 0.4.1 + dev: false + /@floating-ui/core@1.3.1: resolution: {integrity: sha512-Bu+AMaXNjrpjh41znzHqaz3r2Nr8hHuHZT6V2LBKMhyMl0FgKA62PNYbqnfgmzOhoWZj70Zecisbo4H1rotP5g==} dev: false @@ -6661,6 +6712,10 @@ packages: resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} dev: false + /@types/estree@1.0.6: + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + dev: false + /@types/express-serve-static-core@4.19.5: resolution: {integrity: sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg==} dependencies: @@ -7952,6 +8007,18 @@ packages: dependencies: ms: 2.1.2 + /debug@4.3.7: + resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.3 + dev: false + /decimal.js-light@2.5.1: resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} dev: false @@ -8410,13 +8477,13 @@ packages: - supports-color dev: true - /esbuild-register@3.5.0(esbuild@0.23.0): + /esbuild-register@3.5.0(esbuild@0.24.0): resolution: {integrity: sha512-+4G/XmakeBAsvJuDugJvtyF1x+XJT4FMocynNpxrvEBViirpfUn2PgNpCHedfWhF4WokNsO/OvMKrmJOIJsI5A==} peerDependencies: esbuild: '>=0.12 <1' dependencies: debug: 4.3.5 - esbuild: 0.23.0 + esbuild: 0.24.0 transitivePeerDependencies: - supports-color @@ -8511,36 +8578,36 @@ packages: '@esbuild/win32-x64': 0.19.8 dev: true - /esbuild@0.23.0: - resolution: {integrity: sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==} + /esbuild@0.24.0: + resolution: {integrity: sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==} engines: {node: '>=18'} hasBin: true requiresBuild: true optionalDependencies: - '@esbuild/aix-ppc64': 0.23.0 - '@esbuild/android-arm': 0.23.0 - '@esbuild/android-arm64': 0.23.0 - '@esbuild/android-x64': 0.23.0 - '@esbuild/darwin-arm64': 0.23.0 - '@esbuild/darwin-x64': 0.23.0 - '@esbuild/freebsd-arm64': 0.23.0 - '@esbuild/freebsd-x64': 0.23.0 - '@esbuild/linux-arm': 0.23.0 - '@esbuild/linux-arm64': 0.23.0 - '@esbuild/linux-ia32': 0.23.0 - '@esbuild/linux-loong64': 0.23.0 - '@esbuild/linux-mips64el': 0.23.0 - '@esbuild/linux-ppc64': 0.23.0 - '@esbuild/linux-riscv64': 0.23.0 - '@esbuild/linux-s390x': 0.23.0 - '@esbuild/linux-x64': 0.23.0 - '@esbuild/netbsd-x64': 0.23.0 - '@esbuild/openbsd-arm64': 0.23.0 - '@esbuild/openbsd-x64': 0.23.0 - '@esbuild/sunos-x64': 0.23.0 - '@esbuild/win32-arm64': 0.23.0 - '@esbuild/win32-ia32': 0.23.0 - '@esbuild/win32-x64': 0.23.0 + '@esbuild/aix-ppc64': 0.24.0 + '@esbuild/android-arm': 0.24.0 + '@esbuild/android-arm64': 0.24.0 + '@esbuild/android-x64': 0.24.0 + '@esbuild/darwin-arm64': 0.24.0 + '@esbuild/darwin-x64': 0.24.0 + '@esbuild/freebsd-arm64': 0.24.0 + '@esbuild/freebsd-x64': 0.24.0 + '@esbuild/linux-arm': 0.24.0 + '@esbuild/linux-arm64': 0.24.0 + '@esbuild/linux-ia32': 0.24.0 + '@esbuild/linux-loong64': 0.24.0 + '@esbuild/linux-mips64el': 0.24.0 + '@esbuild/linux-ppc64': 0.24.0 + '@esbuild/linux-riscv64': 0.24.0 + '@esbuild/linux-s390x': 0.24.0 + '@esbuild/linux-x64': 0.24.0 + '@esbuild/netbsd-x64': 0.24.0 + '@esbuild/openbsd-arm64': 0.24.0 + '@esbuild/openbsd-x64': 0.24.0 + '@esbuild/sunos-x64': 0.24.0 + '@esbuild/win32-arm64': 0.24.0 + '@esbuild/win32-ia32': 0.24.0 + '@esbuild/win32-x64': 0.24.0 /escalade@3.1.2: resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} @@ -8576,31 +8643,31 @@ packages: optionalDependencies: source-map: 0.6.1 - /eslint-config-prettier@9.0.0(eslint@9.7.0): + /eslint-config-prettier@9.0.0(eslint@9.11.1): resolution: {integrity: sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==} hasBin: true peerDependencies: eslint: '>=7.0.0' dependencies: - eslint: 9.7.0 + eslint: 9.11.1(jiti@1.21.6) dev: false - /eslint-config-turbo@1.10.12(eslint@9.7.0): + /eslint-config-turbo@1.10.12(eslint@9.11.1): resolution: {integrity: sha512-z3jfh+D7UGYlzMWGh+Kqz++hf8LOE96q3o5R8X4HTjmxaBWlLAWG+0Ounr38h+JLR2TJno0hU9zfzoPNkR9BdA==} peerDependencies: eslint: '>6.6.0' dependencies: - eslint: 9.7.0 - eslint-plugin-turbo: 1.10.12(eslint@9.7.0) + eslint: 9.11.1(jiti@1.21.6) + eslint-plugin-turbo: 1.10.12(eslint@9.11.1) dev: false - /eslint-plugin-turbo@1.10.12(eslint@9.7.0): + /eslint-plugin-turbo@1.10.12(eslint@9.11.1): resolution: {integrity: sha512-uNbdj+ohZaYo4tFJ6dStRXu2FZigwulR1b3URPXe0Q8YaE7thuekKNP+54CHtZPH9Zey9dmDx5btAQl9mfzGOw==} peerDependencies: eslint: '>6.6.0' dependencies: dotenv: 16.0.3 - eslint: 9.7.0 + eslint: 9.11.1(jiti@1.21.6) dev: false /eslint-scope@5.1.1: @@ -8617,6 +8684,15 @@ packages: dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 + dev: true + + /eslint-scope@8.1.0: + resolution: {integrity: sha512-14dSvlhaVhKKsa9Fx1l8A17s7ah7Ef7wCakJ10LYk6+GYmP9yDti2oq2SEwcyndt6knfcZyhyxwY3i9yL78EQw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + dev: false /eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} @@ -8626,6 +8702,63 @@ packages: resolution: {integrity: sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + /eslint-visitor-keys@4.1.0: + resolution: {integrity: sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + dev: false + + /eslint@9.11.1(jiti@1.21.6): + resolution: {integrity: sha512-MobhYKIoAO1s1e4VUrgx1l1Sk2JBR/Gqjjgw8+mfgoLE2xwsHur4gdfTxyTgShrhvdVFTaJSgMiQBl1jv/AWxg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@9.11.1) + '@eslint-community/regexpp': 4.11.1 + '@eslint/config-array': 0.18.0 + '@eslint/core': 0.6.0 + '@eslint/eslintrc': 3.1.0 + '@eslint/js': 9.11.1 + '@eslint/plugin-kit': 0.2.0 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.3.0 + '@nodelib/fs.walk': 1.2.8 + '@types/estree': 1.0.6 + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.3 + debug: 4.3.7 + escape-string-regexp: 4.0.0 + eslint-scope: 8.1.0 + eslint-visitor-keys: 4.1.0 + espree: 10.2.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + jiti: 1.21.6 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + strip-ansi: 6.0.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + dev: false + /eslint@9.7.0: resolution: {integrity: sha512-FzJ9D/0nGiCGBf8UXO/IGLTgLVzIxze1zpfA8Ton2mjLovXdAPlYDv+MQDcqj3TmrhAGYfOpz9RfR+ent0AgAw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -8667,6 +8800,7 @@ packages: text-table: 0.2.0 transitivePeerDependencies: - supports-color + dev: true /espree@10.1.0: resolution: {integrity: sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==} @@ -8676,6 +8810,15 @@ packages: acorn-jsx: 5.3.2(acorn@8.12.1) eslint-visitor-keys: 4.0.0 + /espree@10.2.0: + resolution: {integrity: sha512-upbkBJbckcCNBDBDXEbuhjbP68n+scUd3k/U2EkyM9nw+I/jPiL4cLF/Al06CF96wRltFda16sxDFrxsI1v0/g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + dependencies: + acorn: 8.12.1 + acorn-jsx: 5.3.2(acorn@8.12.1) + eslint-visitor-keys: 4.1.0 + dev: false + /esprima@4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} engines: {node: '>=4'} @@ -9091,7 +9234,7 @@ packages: source-map: 0.6.1 wordwrap: 1.0.0 optionalDependencies: - uglify-js: 3.19.0 + uglify-js: 3.19.3 dev: true /has-flag@3.0.0: @@ -9226,6 +9369,11 @@ packages: resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} engines: {node: '>= 4'} + /ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + dev: false + /import-fresh@3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} @@ -9870,6 +10018,10 @@ packages: /ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + /ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + dev: false + /mute-stream@0.0.8: resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} dev: true @@ -9910,16 +10062,30 @@ packages: engines: {node: '>= 0.4.0'} dev: true - /next-safe-action@5.2.3(next@14.2.5)(react@18.3.1)(zod@3.23.8): - resolution: {integrity: sha512-djTDE9CkTH/EVzhIJBovlU9kJ7twy7gVC1HFsuQzhMe2d5V90IXlZLmpvnt0c6D5rMjFBrBwd2VVQnyVQcWoHQ==} - engines: {node: '>=16'} + /next-safe-action@7.9.3(next@14.2.5)(react-dom@18.3.1)(react@18.3.1)(zod@3.23.8): + resolution: {integrity: sha512-2GH7/iRiM5R/y6sIQZsNHGeRr/iKQJsg8ejP63WhTS7fXS9KzxVbEKrWwLNNhL33V9cn0448cPSI/aiSK/PUbA==} + engines: {node: '>=18.17'} peerDependencies: + '@sinclair/typebox': '>= 0.33.3' next: '>= 14.0.0' react: '>= 18.2.0' + react-dom: '>= 18.2.0' + valibot: '>= 0.36.0' + yup: '>= 1.0.0' zod: '>= 3.0.0' + peerDependenciesMeta: + '@sinclair/typebox': + optional: true + valibot: + optional: true + yup: + optional: true + zod: + optional: true dependencies: next: 14.2.5(@babel/core@7.24.5)(react-dom@18.3.1)(react@18.3.1) react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) zod: 3.23.8 dev: false @@ -10832,7 +10998,7 @@ packages: react: 18.3.1 dev: false - /react-email@2.1.5(eslint@9.7.0): + /react-email@2.1.5(eslint@9.11.1): resolution: {integrity: sha512-SjGt5XiqNwrC6FT0rAxERj0MC9binUOVZDzspAxcRHpxjZavvePAHvV29uROWNQ1Ha7ssg1sfy4dTQi7bjCXrg==} engines: {node: '>=18.0.0'} hasBin: true @@ -10856,8 +11022,8 @@ packages: commander: 11.1.0 debounce: 2.0.0 esbuild: 0.19.11 - eslint-config-prettier: 9.0.0(eslint@9.7.0) - eslint-config-turbo: 1.10.12(eslint@9.7.0) + eslint-config-prettier: 9.0.0(eslint@9.11.1) + eslint-config-turbo: 1.10.12(eslint@9.11.1) framer-motion: 10.17.4(react-dom@18.3.1)(react@18.3.1) glob: 10.3.4 log-symbols: 4.1.0 @@ -11793,7 +11959,7 @@ packages: engines: {node: '>=6'} dev: false - /terser-webpack-plugin@5.3.10(@swc/core@1.3.101)(esbuild@0.19.11)(webpack@5.93.0): + /terser-webpack-plugin@5.3.10(esbuild@0.24.0)(webpack@5.93.0): resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} engines: {node: '>= 10.13.0'} peerDependencies: @@ -11810,8 +11976,7 @@ packages: optional: true dependencies: '@jridgewell/trace-mapping': 0.3.25 - '@swc/core': 1.3.101 - esbuild: 0.19.11 + esbuild: 0.24.0 jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.2 @@ -12091,8 +12256,8 @@ packages: engines: {node: '>=14.17'} hasBin: true - /uglify-js@3.19.0: - resolution: {integrity: sha512-wNKHUY2hYYkf6oSFfhwwiHo4WCHzHmzcXsqXYTN9ja3iApYIFbb2U6ics9hBcYLHcYGQoAlwnZlTrf3oF+BL/Q==} + /uglify-js@3.19.3: + resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} engines: {node: '>=0.8.0'} hasBin: true requiresBuild: true @@ -12413,7 +12578,7 @@ packages: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.10(@swc/core@1.3.101)(esbuild@0.19.11)(webpack@5.93.0) + terser-webpack-plugin: 5.3.10(esbuild@0.24.0)(webpack@5.93.0) watchpack: 2.4.1 webpack-sources: 3.2.3 transitivePeerDependencies: @@ -12581,7 +12746,3 @@ packages: /zod@3.23.8: resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} dev: false - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false