From 680c0ed1f56ec222b1a06b0da54eaf269c40329f Mon Sep 17 00:00:00 2001 From: Emre Ozden Date: Thu, 17 Jul 2025 23:40:57 +0300 Subject: [PATCH 01/12] Added PATCH route to API for pizza. Started working on handler --- server/src/routes/application/application-handlers.ts | 10 ++++++++++ server/src/routes/application/index.ts | 7 +++++++ 2 files changed, 17 insertions(+) diff --git a/server/src/routes/application/application-handlers.ts b/server/src/routes/application/application-handlers.ts index 2dbbc257..795f7214 100644 --- a/server/src/routes/application/application-handlers.ts +++ b/server/src/routes/application/application-handlers.ts @@ -633,6 +633,16 @@ class ApplicationHandlers { response.sendStatus(200) } } + + @onlyKnownUsers() + patchPizza(): Middleware { + return async (request, _response) => { + const { user } = request + assert(user) + + // Do some magic here + } + } } const applicationHandlers = new ApplicationHandlers() diff --git a/server/src/routes/application/index.ts b/server/src/routes/application/index.ts index abe6c739..4e0f4e44 100644 --- a/server/src/routes/application/index.ts +++ b/server/src/routes/application/index.ts @@ -63,6 +63,13 @@ applicationApp .patch(applicationHandlers.patchCv()) .all(forbiddenOrUnauthorised()) +applicationApp + .route("/pizza") + .all(methodNotAllowed(["PATCH"])) + .all(authenticate()) + .patch(applicationHandlers.patchPizza()) + .all(forbiddenOrUnauthorised()) + applicationApp .route("/submit") .all(methodNotAllowed(["POST"])) From 1de25a23ac6cddbd02a200281bf81f84d66a2c89 Mon Sep 17 00:00:00 2001 From: Emre Ozden Date: Fri, 18 Jul 2025 00:10:14 +0300 Subject: [PATCH 02/12] Defined pizza choice schema and types in zod --- common/src/input/pizza-flavor.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 common/src/input/pizza-flavor.ts diff --git a/common/src/input/pizza-flavor.ts b/common/src/input/pizza-flavor.ts new file mode 100644 index 00000000..2a565754 --- /dev/null +++ b/common/src/input/pizza-flavor.ts @@ -0,0 +1,21 @@ +import { z } from "zod/v4" +import { recordEntries } from "@/util/record-entries" + +export const pizzaFlavorSchema = z.enum(["nothing", "alternative", "margherita", "pepperoni"]) + +export type PizzaFlavor = z.output + +const PizzaFlavourMetadata: Record = { + nothing: { label: "Nothing" }, + alternative: { label: "Alternative" }, + margherita: { label: "Margherita" }, + pepperoni: { label: "Pepperoni" }, +} + +export const pizzaFlavourOptions = recordEntries(PizzaFlavourMetadata).map(([value, metadata]) => ({ + value, + label: metadata.label, +})) satisfies Array<{ + value: PizzaFlavor + label: string +}> From 9b8d3d6567c17b4abfafe3e41d28738ba96447ce Mon Sep 17 00:00:00 2001 From: Emre Ozden Date: Fri, 18 Jul 2025 00:52:13 +0300 Subject: [PATCH 03/12] Thought it may be a better idea to include pizza flavor under extra details, removed API route for pizza, started working on adding it to PATCH /applications/personal --- common/src/types/application.ts | 2 ++ .../src/routes/application/application-handlers.ts | 12 ++++++++++++ server/src/routes/application/index.ts | 7 ------- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/common/src/types/application.ts b/common/src/types/application.ts index a39d249c..8b1d32a3 100644 --- a/common/src/types/application.ts +++ b/common/src/types/application.ts @@ -1,5 +1,6 @@ import type { DisciplineOfStudy } from "@/input/discipline-of-study" import type { DietaryRequirement } from "@/input/dietary-requirement" +import { PizzaFlavor } from "@/input/pizza-flavor"; export type { DisciplineOfStudy, DietaryRequirement } @@ -23,6 +24,7 @@ export type Application = { university: string | null graduationYear: number | null disciplinesOfStudy: null | DisciplineOfStudy[] + pizzaFlavors: null | PizzaFlavor[] levelOfStudy: | null | "secondary" diff --git a/server/src/routes/application/application-handlers.ts b/server/src/routes/application/application-handlers.ts index 795f7214..e91ee277 100644 --- a/server/src/routes/application/application-handlers.ts +++ b/server/src/routes/application/application-handlers.ts @@ -2,6 +2,7 @@ import assert from "node:assert/strict" import { parse as parsePath } from "node:path/posix" import { type DietaryRequirement, dietaryRequirementSchema } from "@durhack/durhack-common/input/dietary-requirement" import { type DisciplineOfStudy, disciplineOfStudySchema } from "@durhack/durhack-common/input/discipline-of-study" +import { type PizzaFlavor, pizzaFlavorSchema } from "@durhack/durhack-common/input/pizza-flavor" import type { Application } from "@durhack/durhack-common/types/application" import { ClientError, HttpStatus } from "@otterhttp/errors" import type { ContentType, ParsedFormFieldFile } from "@otterhttp/parsec" @@ -49,6 +50,13 @@ const personalFormSchema = z.object({ message: "Please select an ethnicity", }) .transform(adaptEthnicityToDatabase), + pizza: z + .array(pizzaFlavorSchema) + .min(1, { message: "Please select at least one pizza flavor." }) + .refine((list) => { + const mutuallyExclusivePreferences = list.filter((item) => item === "alternative" || item === "nothing") + return mutuallyExclusivePreferences.length <= 1 + }, "If you don't want pizza, please choose one of 'nothing' or 'alternative'."), }) const contactFormSchema = z.object({ @@ -154,6 +162,9 @@ class ApplicationHandlers { const dietaryRequirements = userFlags .filter((flag) => flag.flagName.startsWith("dietary-requirement:")) .map((flag) => flag.flagName.slice(20) as DietaryRequirement) + const pizzaFlavors = userFlags + .filter((flag) => flag.flagName.startsWith("pizza-flavor:")) + .map((flag) => flag.flagName.slice(13) as PizzaFlavor) const { phone_number, @@ -190,6 +201,7 @@ class ApplicationHandlers { disciplinesOfStudy: disciplinesOfStudy, tShirtSize: (userInfo?.tShirtSize?.trimEnd() as Application["tShirtSize"] | null | undefined) ?? null, dietaryRequirements: dietaryRequirements, + pizzaFlavors: pizzaFlavors, accessRequirements: userInfo?.accessRequirements ?? null, countryOfResidence: userInfo?.countryOfResidence ?? null, consents: userConsents.map((consent) => ({ name: consent.consentName, choice: consent.choice })), diff --git a/server/src/routes/application/index.ts b/server/src/routes/application/index.ts index 4e0f4e44..abe6c739 100644 --- a/server/src/routes/application/index.ts +++ b/server/src/routes/application/index.ts @@ -63,13 +63,6 @@ applicationApp .patch(applicationHandlers.patchCv()) .all(forbiddenOrUnauthorised()) -applicationApp - .route("/pizza") - .all(methodNotAllowed(["PATCH"])) - .all(authenticate()) - .patch(applicationHandlers.patchPizza()) - .all(forbiddenOrUnauthorised()) - applicationApp .route("/submit") .all(methodNotAllowed(["POST"])) From a5401e81cffcae1968a7dab5bd1cc24542496748 Mon Sep 17 00:00:00 2001 From: Emre Ozden Date: Fri, 18 Jul 2025 15:32:33 +0300 Subject: [PATCH 04/12] Added pizza flavour to application and extraDetails schema --- .../application/application-handlers.ts | 36 +++++++++---------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/server/src/routes/application/application-handlers.ts b/server/src/routes/application/application-handlers.ts index e91ee277..9f337a72 100644 --- a/server/src/routes/application/application-handlers.ts +++ b/server/src/routes/application/application-handlers.ts @@ -50,13 +50,6 @@ const personalFormSchema = z.object({ message: "Please select an ethnicity", }) .transform(adaptEthnicityToDatabase), - pizza: z - .array(pizzaFlavorSchema) - .min(1, { message: "Please select at least one pizza flavor." }) - .refine((list) => { - const mutuallyExclusivePreferences = list.filter((item) => item === "alternative" || item === "nothing") - return mutuallyExclusivePreferences.length <= 1 - }, "If you don't want pizza, please choose one of 'nothing' or 'alternative'."), }) const contactFormSchema = z.object({ @@ -104,6 +97,12 @@ const extraDetailsFormSchema = z.object({ ) return mutuallyExclusivePreferences.length <= 1 }, "Please select at most one of 'vegan', 'vegetarian', 'pescatarian'."), + pizza: z + .array(pizzaFlavorSchema) + .refine((list) => { + const mutuallyExclusivePreferences = list.filter((item) => item === "alternative" || item === "nothing") + return mutuallyExclusivePreferences.length <= 1 + }, "If you don't want pizza, please choose one of 'nothing' or 'alternative'.").min(1, { message: "Please select at least one pizza flavor." }), accessRequirements: z.string().trim(), }) @@ -325,9 +324,10 @@ class ApplicationHandlers { prisma.userFlag.deleteMany({ where: { userId: user.keycloakUserId, - flagName: { - startsWith: "dietary-requirement:", - }, + OR: [ + { flagName: { startsWith: "dietary-requirement:" } }, + { flagName: { startsWith: "pizza-flavor:" } } + ], }, }), prisma.user.update({ @@ -349,6 +349,12 @@ class ApplicationHandlers { }, }), ), + ...payload.pizza.map((item) => prisma.userFlag.create({ + data: { + userId: user.keycloakUserId, + flagName: `pizza-flavor:${item}`, + }, + })), ]) response.sendStatus(200) } @@ -645,16 +651,6 @@ class ApplicationHandlers { response.sendStatus(200) } } - - @onlyKnownUsers() - patchPizza(): Middleware { - return async (request, _response) => { - const { user } = request - assert(user) - - // Do some magic here - } - } } const applicationHandlers = new ApplicationHandlers() From 51616e3a07c6f2079e52a4fb327b861fc39be0e8 Mon Sep 17 00:00:00 2001 From: Emre Ozden Date: Mon, 21 Jul 2025 16:24:16 +0300 Subject: [PATCH 05/12] Added pizza form to dashboard, still needs changing up --- .../dashboard/(application)/extra/page.tsx | 25 ++++++++++++++ .../application/application-handlers.ts | 33 +++++++++++++++---- 2 files changed, 51 insertions(+), 7 deletions(-) diff --git a/client/src/app/dashboard/(application)/extra/page.tsx b/client/src/app/dashboard/(application)/extra/page.tsx index 6aade8a6..091ab6e6 100644 --- a/client/src/app/dashboard/(application)/extra/page.tsx +++ b/client/src/app/dashboard/(application)/extra/page.tsx @@ -31,11 +31,13 @@ import type { Application } from "@/hooks/use-application" import { useApplicationContext } from "@/hooks/use-application-context" import { isLoaded } from "@/lib/is-loaded" import { updateApplication } from "@/lib/update-application" +import {pizzaFlavorSchema, pizzaFlavourOptions} from "@durhack/durhack-common/input/pizza-flavor" type ExtraDetailsFormFields = { tShirtSize: string hackathonExperience: string dietaryRequirements: string[] + pizzaFlavors: string[] accessRequirements: string } @@ -52,6 +54,12 @@ const extraDetailsFormSchema = z.object({ ) return mutuallyExclusivePreferences.length <= 1 }, "Please select at most one of 'vegan', 'vegetarian', 'pescatarian'."), + pizzaFlavors: z.array(pizzaFlavorSchema).refine((list) => { + const mutuallyExclusivePreferences = list.filter( + (item) => item === "alternative" || item === "nothing", + ) + return mutuallyExclusivePreferences.length <= 1 + }), accessRequirements: z.string().trim(), }) @@ -72,6 +80,7 @@ function ExtraDetailsForm({ application }: { application: Application }) { hackathonExperience: application.hackathonExperience ?? "", dietaryRequirements: application.dietaryRequirements ?? [], accessRequirements: application.accessRequirements ?? "", + pizzaFlavors: application.pizzaFlavors ?? [], }, }) @@ -169,6 +178,22 @@ function ExtraDetailsForm({ application }: { application: Application }) { /> +
+ ( + + Midnight Snack (on us) + We normally offer pizza as a midnight snack. If you want something else, select "alternative" and we will get in touch with you! + + + + + + )} /> +
+
{ const mutuallyExclusivePreferences = list.filter((item) => item === "alternative" || item === "nothing") @@ -313,6 +313,7 @@ class ApplicationHandlers { const body = await json(request, response) const payload = extraDetailsFormSchema.parse(body) + console.log(payload) const prismaUserInfoFields = { tShirtSize: payload.tShirtSize, @@ -349,13 +350,31 @@ class ApplicationHandlers { }, }), ), - ...payload.pizza.map((item) => prisma.userFlag.create({ - data: { - userId: user.keycloakUserId, - flagName: `pizza-flavor:${item}`, - }, - })), + ...payload.pizzaFlavors.map((item) => + prisma.userFlag.create({ + data: { + userId: user.keycloakUserId, + flagName: `pizza-flavor:${item}`, + }, + }), + ), ]) + + // Check pizza role in db + + const flavors = await prisma.userFlag.findMany({ + where: { + userId: user.keycloakUserId, + flagName: { + startsWith: "pizza-flavor:" + } + } + }) + + // It saves properly :-/ + + console.log(flavors) + response.sendStatus(200) } } From abf88e99be5be08c47d84485dbaee8bc46bd1f16 Mon Sep 17 00:00:00 2001 From: Emre Ozden Date: Wed, 23 Jul 2025 00:13:56 +0300 Subject: [PATCH 06/12] Made the pizza form better. User can choose snack type first, then pizza flavor --- .../dashboard/(application)/extra/page.tsx | 76 ++++++++++++++++--- 1 file changed, 65 insertions(+), 11 deletions(-) diff --git a/client/src/app/dashboard/(application)/extra/page.tsx b/client/src/app/dashboard/(application)/extra/page.tsx index 091ab6e6..c03c8347 100644 --- a/client/src/app/dashboard/(application)/extra/page.tsx +++ b/client/src/app/dashboard/(application)/extra/page.tsx @@ -1,6 +1,7 @@ "use client" import { dietaryRequirementOptions, dietaryRequirementSchema } from "@durhack/durhack-common/input/dietary-requirement" +import { pizzaFlavorSchema, pizzaFlavourOptions } from "@durhack/durhack-common/input/pizza-flavor" import { Form, FormControl, @@ -22,16 +23,15 @@ import { import { Textarea } from "@durhack/web-components/ui/textarea" import { zodResolver } from "@hookform/resolvers/zod" import { useRouter } from "next/navigation" +import { useState } from "react" import { useForm } from "react-hook-form" import { z } from "zod/v4" - import { FormSkeleton } from "@/components/dashboard/form-skeleton" import { FormSubmitButton } from "@/components/dashboard/form-submit-button" import type { Application } from "@/hooks/use-application" import { useApplicationContext } from "@/hooks/use-application-context" import { isLoaded } from "@/lib/is-loaded" import { updateApplication } from "@/lib/update-application" -import {pizzaFlavorSchema, pizzaFlavourOptions} from "@durhack/durhack-common/input/pizza-flavor" type ExtraDetailsFormFields = { tShirtSize: string @@ -55,9 +55,7 @@ const extraDetailsFormSchema = z.object({ return mutuallyExclusivePreferences.length <= 1 }, "Please select at most one of 'vegan', 'vegetarian', 'pescatarian'."), pizzaFlavors: z.array(pizzaFlavorSchema).refine((list) => { - const mutuallyExclusivePreferences = list.filter( - (item) => item === "alternative" || item === "nothing", - ) + const mutuallyExclusivePreferences = list.filter((item) => item === "alternative" || item === "nothing") return mutuallyExclusivePreferences.length <= 1 }), accessRequirements: z.string().trim(), @@ -90,6 +88,32 @@ function ExtraDetailsForm({ application }: { application: Application }) { if (application.tShirtSize == null) router.push("/dashboard/education") } + const [snackChoice, setSnackChoice] = useState( + !application.pizzaFlavors?.includes("alternative") && !application.pizzaFlavors?.includes("nothing") + ? "pizza" + : application.pizzaFlavors[0], + ) + + function handleSnackChoice(value: string): void { + if (value === "pizza") { + setSnackChoice("pizza") + form.setValue("pizzaFlavors", []) + return + } + + if (value === "alternative") { + setSnackChoice("alternative") + form.setValue("pizzaFlavors", ['alternative']) + return + } + + if (value === "nothing") { + setSnackChoice("nothing") + form.setValue("pizzaFlavors", ['nothing']) + return + } + } + return (
@@ -182,16 +206,46 @@ function ExtraDetailsForm({ application }: { application: Application }) { ( + render={({ field: { ref, ...field } }) => ( Midnight Snack (on us) - We normally offer pizza as a midnight snack. If you want something else, select "alternative" and we will get in touch with you! - - - + + We normally offer pizza as a midnight snack. If you want something else, select "alternative" and we + will get in touch with you! + + + + {snackChoice === "pizza" && ( + <> + Please choose the flavours of pizza you would be okay to have + + item.value !== "nothing" && item.value !== "alternative", + )} + hidePlaceholderWhenSelected + /> + + + )} - )} /> + )} + />
From 44488c3b52ff216056c16aef7377f82da83fd1e7 Mon Sep 17 00:00:00 2001 From: Emre Ozden Date: Wed, 23 Jul 2025 00:14:42 +0300 Subject: [PATCH 07/12] Fixed formatting --- client/src/app/dashboard/(application)/extra/page.tsx | 4 ++-- client/src/app/profile/[userId]/page.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/src/app/dashboard/(application)/extra/page.tsx b/client/src/app/dashboard/(application)/extra/page.tsx index c03c8347..63da02b9 100644 --- a/client/src/app/dashboard/(application)/extra/page.tsx +++ b/client/src/app/dashboard/(application)/extra/page.tsx @@ -103,13 +103,13 @@ function ExtraDetailsForm({ application }: { application: Application }) { if (value === "alternative") { setSnackChoice("alternative") - form.setValue("pizzaFlavors", ['alternative']) + form.setValue("pizzaFlavors", ["alternative"]) return } if (value === "nothing") { setSnackChoice("nothing") - form.setValue("pizzaFlavors", ['nothing']) + form.setValue("pizzaFlavors", ["nothing"]) return } } diff --git a/client/src/app/profile/[userId]/page.tsx b/client/src/app/profile/[userId]/page.tsx index 1feb8b16..c0295c5d 100644 --- a/client/src/app/profile/[userId]/page.tsx +++ b/client/src/app/profile/[userId]/page.tsx @@ -42,7 +42,7 @@ function UserAttribute({ children, className, ...props }: React.HTMLAttributes }) { - const params = React.use(props.params); + const params = React.use(props.params) const { toast } = useToast() const { From 47dad30a9c047511cc85bfb936356935895a9714 Mon Sep 17 00:00:00 2001 From: Emre Ozden Date: Wed, 23 Jul 2025 00:22:31 +0300 Subject: [PATCH 08/12] Removed debug output from server-side --- .../application/application-handlers.ts | 25 +++---------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/server/src/routes/application/application-handlers.ts b/server/src/routes/application/application-handlers.ts index f532824a..933a7abf 100644 --- a/server/src/routes/application/application-handlers.ts +++ b/server/src/routes/application/application-handlers.ts @@ -102,7 +102,8 @@ const extraDetailsFormSchema = z.object({ .refine((list) => { const mutuallyExclusivePreferences = list.filter((item) => item === "alternative" || item === "nothing") return mutuallyExclusivePreferences.length <= 1 - }, "If you don't want pizza, please choose one of 'nothing' or 'alternative'.").min(1, { message: "Please select at least one pizza flavor." }), + }, "If you don't want pizza, please choose one of 'nothing' or 'alternative'.") + .min(1, { message: "Please select at least one pizza flavor." }), accessRequirements: z.string().trim(), }) @@ -313,7 +314,6 @@ class ApplicationHandlers { const body = await json(request, response) const payload = extraDetailsFormSchema.parse(body) - console.log(payload) const prismaUserInfoFields = { tShirtSize: payload.tShirtSize, @@ -325,10 +325,7 @@ class ApplicationHandlers { prisma.userFlag.deleteMany({ where: { userId: user.keycloakUserId, - OR: [ - { flagName: { startsWith: "dietary-requirement:" } }, - { flagName: { startsWith: "pizza-flavor:" } } - ], + OR: [{ flagName: { startsWith: "dietary-requirement:" } }, { flagName: { startsWith: "pizza-flavor:" } }], }, }), prisma.user.update({ @@ -359,22 +356,6 @@ class ApplicationHandlers { }), ), ]) - - // Check pizza role in db - - const flavors = await prisma.userFlag.findMany({ - where: { - userId: user.keycloakUserId, - flagName: { - startsWith: "pizza-flavor:" - } - } - }) - - // It saves properly :-/ - - console.log(flavors) - response.sendStatus(200) } } From f70f5cbc2997d10883d46215bbb6c9197bcc8f57 Mon Sep 17 00:00:00 2001 From: Emre Ozden Date: Mon, 28 Jul 2025 15:29:57 +0300 Subject: [PATCH 09/12] Updated pizza flalvor schema and made relevant migrations for midnight snack choice --- common/src/input/pizza-flavor.ts | 4 +--- common/src/types/application.ts | 1 + .../migrations/20250728121812_midnight_snack/migration.sql | 5 +++++ server/prisma/schema.prisma | 7 +++++++ 4 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 server/prisma/migrations/20250728121812_midnight_snack/migration.sql diff --git a/common/src/input/pizza-flavor.ts b/common/src/input/pizza-flavor.ts index 2a565754..ec8f913e 100644 --- a/common/src/input/pizza-flavor.ts +++ b/common/src/input/pizza-flavor.ts @@ -1,13 +1,11 @@ import { z } from "zod/v4" import { recordEntries } from "@/util/record-entries" -export const pizzaFlavorSchema = z.enum(["nothing", "alternative", "margherita", "pepperoni"]) +export const pizzaFlavorSchema = z.enum(["margherita", "pepperoni"]) export type PizzaFlavor = z.output const PizzaFlavourMetadata: Record = { - nothing: { label: "Nothing" }, - alternative: { label: "Alternative" }, margherita: { label: "Margherita" }, pepperoni: { label: "Pepperoni" }, } diff --git a/common/src/types/application.ts b/common/src/types/application.ts index 8b1d32a3..75f3fa5f 100644 --- a/common/src/types/application.ts +++ b/common/src/types/application.ts @@ -24,6 +24,7 @@ export type Application = { university: string | null graduationYear: number | null disciplinesOfStudy: null | DisciplineOfStudy[] + midnightSnack: "pizza" | "alternative" | "nothing" pizzaFlavors: null | PizzaFlavor[] levelOfStudy: | null diff --git a/server/prisma/migrations/20250728121812_midnight_snack/migration.sql b/server/prisma/migrations/20250728121812_midnight_snack/migration.sql new file mode 100644 index 00000000..044982ac --- /dev/null +++ b/server/prisma/migrations/20250728121812_midnight_snack/migration.sql @@ -0,0 +1,5 @@ +-- CreateEnum +CREATE TYPE "MidnightSnack" AS ENUM ('pizza', 'alternative', 'nothing'); + +-- AlterTable +ALTER TABLE "UserInfo" ADD COLUMN "midnight_snack" "MidnightSnack"; diff --git a/server/prisma/schema.prisma b/server/prisma/schema.prisma index 0ea8d237..fb73d806 100644 --- a/server/prisma/schema.prisma +++ b/server/prisma/schema.prisma @@ -62,6 +62,7 @@ model UserInfo { ethnicity Ethnicity? hackathonExperience HackathonExperience? @map("hackathon_experience") accessRequirements String? @map("access_requirements") @db.Text() + midnightSnack MidnightSnack? @map("midnight_snack") updatedAt DateTime @updatedAt @map("updated_at") } @@ -151,3 +152,9 @@ enum HackathonExperience { threeToSeven @map("three_to_seven") // "hack wizard" eightOrMore @map("eight_or_more") // "hackathon guru" } + +enum MidnightSnack { + pizza + alternative + nothing +} From 5e38f186c0e36c42246cb104366bbd9dd328f71e Mon Sep 17 00:00:00 2001 From: Emre Ozden Date: Mon, 4 Aug 2025 14:15:32 +0300 Subject: [PATCH 10/12] Refactored relevant code to handle midnightSnack --- .../dashboard/(application)/extra/page.tsx | 66 +++++++------------ common/src/types/application.ts | 2 +- .../application/application-handlers.ts | 13 ++-- 3 files changed, 31 insertions(+), 50 deletions(-) diff --git a/client/src/app/dashboard/(application)/extra/page.tsx b/client/src/app/dashboard/(application)/extra/page.tsx index 63da02b9..4a20542c 100644 --- a/client/src/app/dashboard/(application)/extra/page.tsx +++ b/client/src/app/dashboard/(application)/extra/page.tsx @@ -39,6 +39,7 @@ type ExtraDetailsFormFields = { dietaryRequirements: string[] pizzaFlavors: string[] accessRequirements: string + midnightSnack: string } const extraDetailsFormSchema = z.object({ @@ -54,10 +55,8 @@ const extraDetailsFormSchema = z.object({ ) return mutuallyExclusivePreferences.length <= 1 }, "Please select at most one of 'vegan', 'vegetarian', 'pescatarian'."), - pizzaFlavors: z.array(pizzaFlavorSchema).refine((list) => { - const mutuallyExclusivePreferences = list.filter((item) => item === "alternative" || item === "nothing") - return mutuallyExclusivePreferences.length <= 1 - }), + midnightSnack: z.enum(["pizza", "alternative", "nothing"], { message: "Please select a midnight snack." }), + pizzaFlavors: z.array(pizzaFlavorSchema), accessRequirements: z.string().trim(), }) @@ -78,6 +77,7 @@ function ExtraDetailsForm({ application }: { application: Application }) { hackathonExperience: application.hackathonExperience ?? "", dietaryRequirements: application.dietaryRequirements ?? [], accessRequirements: application.accessRequirements ?? "", + midnightSnack: application.midnightSnack ?? "", pizzaFlavors: application.pizzaFlavors ?? [], }, }) @@ -88,30 +88,11 @@ function ExtraDetailsForm({ application }: { application: Application }) { if (application.tShirtSize == null) router.push("/dashboard/education") } - const [snackChoice, setSnackChoice] = useState( - !application.pizzaFlavors?.includes("alternative") && !application.pizzaFlavors?.includes("nothing") - ? "pizza" - : application.pizzaFlavors[0], - ) + const [snackChoice, setSnackChoice] = useState(application.midnightSnack ?? "") function handleSnackChoice(value: string): void { - if (value === "pizza") { - setSnackChoice("pizza") - form.setValue("pizzaFlavors", []) - return - } - - if (value === "alternative") { - setSnackChoice("alternative") - form.setValue("pizzaFlavors", ["alternative"]) - return - } - - if (value === "nothing") { - setSnackChoice("nothing") - form.setValue("pizzaFlavors", ["nothing"]) - return - } + setSnackChoice(value) + form.setValue("midnightSnack", value) } return ( @@ -205,7 +186,7 @@ function ExtraDetailsForm({ application }: { application: Application }) {
( Midnight Snack (on us) @@ -213,7 +194,7 @@ function ExtraDetailsForm({ application }: { application: Application }) { We normally offer pizza as a midnight snack. If you want something else, select "alternative" and we will get in touch with you! - @@ -228,24 +209,25 @@ function ExtraDetailsForm({ application }: { application: Application }) { - {snackChoice === "pizza" && ( - <> - Please choose the flavours of pizza you would be okay to have - - item.value !== "nothing" && item.value !== "alternative", - )} - hidePlaceholderWhenSelected - /> - - - )} )} /> + + {snackChoice === "pizza" && ( + ( + + Please choose the flavours of pizza you would be okay to have + + + + + )} + /> + )}
diff --git a/common/src/types/application.ts b/common/src/types/application.ts index 75f3fa5f..9ffaf1d1 100644 --- a/common/src/types/application.ts +++ b/common/src/types/application.ts @@ -24,7 +24,7 @@ export type Application = { university: string | null graduationYear: number | null disciplinesOfStudy: null | DisciplineOfStudy[] - midnightSnack: "pizza" | "alternative" | "nothing" + midnightSnack: null | "pizza" | "alternative" | "nothing" pizzaFlavors: null | PizzaFlavor[] levelOfStudy: | null diff --git a/server/src/routes/application/application-handlers.ts b/server/src/routes/application/application-handlers.ts index 933a7abf..20c4e261 100644 --- a/server/src/routes/application/application-handlers.ts +++ b/server/src/routes/application/application-handlers.ts @@ -97,13 +97,10 @@ const extraDetailsFormSchema = z.object({ ) return mutuallyExclusivePreferences.length <= 1 }, "Please select at most one of 'vegan', 'vegetarian', 'pescatarian'."), - pizzaFlavors: z - .array(pizzaFlavorSchema) - .refine((list) => { - const mutuallyExclusivePreferences = list.filter((item) => item === "alternative" || item === "nothing") - return mutuallyExclusivePreferences.length <= 1 - }, "If you don't want pizza, please choose one of 'nothing' or 'alternative'.") - .min(1, { message: "Please select at least one pizza flavor." }), + midnightSnack: z.enum(["pizza", "alternative", "nothing"], { + message: "Please select your midnight snack preference.", + }), + pizzaFlavors: z.array(pizzaFlavorSchema).min(1, { message: "Please select at least one pizza flavor." }), accessRequirements: z.string().trim(), }) @@ -201,6 +198,7 @@ class ApplicationHandlers { disciplinesOfStudy: disciplinesOfStudy, tShirtSize: (userInfo?.tShirtSize?.trimEnd() as Application["tShirtSize"] | null | undefined) ?? null, dietaryRequirements: dietaryRequirements, + midnightSnack: userInfo?.midnightSnack ?? null, pizzaFlavors: pizzaFlavors, accessRequirements: userInfo?.accessRequirements ?? null, countryOfResidence: userInfo?.countryOfResidence ?? null, @@ -319,6 +317,7 @@ class ApplicationHandlers { tShirtSize: payload.tShirtSize, hackathonExperience: payload.hackathonExperience, accessRequirements: payload.accessRequirements || undefined, + midnightSnack: payload.midnightSnack, } await prisma.$transaction([ From e0faf56b01e37e6105d930513130bbf70cf18897 Mon Sep 17 00:00:00 2001 From: Emre Ozden Date: Mon, 4 Aug 2025 14:17:50 +0300 Subject: [PATCH 11/12] Changed wording on the form --- client/src/app/dashboard/(application)/extra/page.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/app/dashboard/(application)/extra/page.tsx b/client/src/app/dashboard/(application)/extra/page.tsx index 4a20542c..0062077d 100644 --- a/client/src/app/dashboard/(application)/extra/page.tsx +++ b/client/src/app/dashboard/(application)/extra/page.tsx @@ -191,7 +191,7 @@ function ExtraDetailsForm({ application }: { application: Application }) { Midnight Snack (on us) - We normally offer pizza as a midnight snack. If you want something else, select "alternative" and we + We normally offer pizza as a midnight snack. If you are not able to have pizza, select "alternative" and we will get in touch with you! diff --git a/server/src/routes/application/application-handlers.ts b/server/src/routes/application/application-handlers.ts index 20c4e261..101d88af 100644 --- a/server/src/routes/application/application-handlers.ts +++ b/server/src/routes/application/application-handlers.ts @@ -320,6 +320,13 @@ class ApplicationHandlers { midnightSnack: payload.midnightSnack, } + if (payload.midnightSnack !== "pizza") { + prisma.userFlag.deleteMany({ + where: { userId: user.keycloakUserId, flagName: { startsWith: "pizza-flavor:" } }, + }) + payload.pizzaFlavors = [] + } + await prisma.$transaction([ prisma.userFlag.deleteMany({ where: {