diff --git a/apps/web/package.json b/apps/web/package.json index d2c3ed25..05c2903d 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -17,6 +17,7 @@ "@internationalized/date": "^3.5.4", "@planetscale/database": "^1.18.0", "@radix-ui/react-accordion": "^1.1.2", + "@radix-ui/react-alert-dialog": "^1.1.1", "@radix-ui/react-avatar": "^1.0.3", "@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-dialog": "^1.0.4", diff --git a/apps/web/src/actions/registration.ts b/apps/web/src/actions/registration.ts new file mode 100644 index 00000000..2510903b --- /dev/null +++ b/apps/web/src/actions/registration.ts @@ -0,0 +1,141 @@ +"use server" +import { authenticatedAction } from "@/lib/safe-action" +import { db,sql } from "db" +import { put } from "@vercel/blob" +import { bucketResumeBaseUploadUrl } from "config" +import z from "zod" +import { returnValidationErrors } from "next-safe-action" +import { updateUserResume } from "db/functions" +import { hackerRegistrationFormValidator } from "@/validators/shared/registration" +import { userCommonData, userHackerData } from "db/schema" +import { currentUser } from "@clerk/nextjs/server" +import c from "config" +import { DatabaseError } from "db/types" +import { DUPLICATE_KEY_ERROR_CODE,UNIQUE_KEY_MAPPER_DEFAULT_KEY } from "@/lib/constants" + + +const uploadResumeSchema = z.object({ + resumeFile: z.any() + // .instanceof(File, { message: "Required" }) + // .refine((file) => file.size > 0, "Required"), +}); + +const registerUserSchema = hackerRegistrationFormValidator; + + +export const uploadResume = authenticatedAction + .schema(uploadResumeSchema). + action( + async ({ ctx:{ userId}, parsedInput:{resumeFile} }) =>{ + const fileLocation = `${bucketResumeBaseUploadUrl}/${resumeFile.name}`; + const newBlob = await put(fileLocation, resumeFile, { + access: "public", + }); + + await updateUserResume(userId,newBlob.url); + + return { + success:true, + resume_url:newBlob.url + } + } +); + +export const registerUser = authenticatedAction + .schema(registerUserSchema) + .action( + async ( { ctx:{ userId }, parsedInput}) =>{ + // Reccomended: Destructure out your unique constraints / primary keys ahead of time to ensure that they can be cause short circuit logic if a unique constraint is violated + const { resumeFile, hackerTag, email, + university,major,schoolID,levelOfStudy,hackathonsAttended,softwareExperience, heardFrom,GitHub,LinkedIn,PersonalWebsite,hasAcceptedMLHCoC,hasSharedDataWithMLH,isEmailable, + ...userData + } = parsedInput; + + const currUser = await currentUser(); + + console.log('resume file is',resumeFile); + throw new Error('test'); + if (!currUser) { + returnValidationErrors(z.null(), { + _errors: ["User does not exist"], + }); + } + const totalUserCount = await db + .select({ count: sql`count(*)`.mapWith(Number) }) + .from(userCommonData); + + try{ + await db.transaction(async (tx) => { + // Add user common insertion + await tx.insert(userCommonData).values({ + // Slight optimization to short circuit is to add all of your unique keys at the top + clerkID: userId, + hackerTag: hackerTag.toLocaleLowerCase(), + email, + ...userData, + profilePhoto: currUser.imageUrl, + skills: userData.skills.map((v) => v.text.toLowerCase()), + isFullyRegistered: true, + dietRestrictions: userData.dietRestrictions, + }); + + await tx + .insert(userHackerData) + .values({ + clerkID: userId, + university, + major, + schoolID, + levelOfStudy, + hackathonsAttended, + softwareExperience, + heardFrom, + GitHub, + LinkedIn, + PersonalWebsite, + resume:undefined, + group: + totalUserCount[0].count % Object.keys(c.groups).length, + hasAcceptedMLHCoC, + hasSharedDataWithMLH, + isEmailable, + }) + }); + }catch(e){ + // Catch duplicates because they will be based off of the error code 23505 + if (e instanceof DatabaseError && e.code === DUPLICATE_KEY_ERROR_CODE){ + console.error(e); + const constraintKeyIndex = e.constraint as keyof typeof c.db.UniqueKeyMapper; + return { + success: false, + message: c.db.UniqueKeyMapper[constraintKeyIndex ?? UNIQUE_KEY_MAPPER_DEFAULT_KEY] ?? e.detail, + }; + }else{ + throw e; + } + } + console.log('Contents of resumeFile',resumeFile); + // Determine upload stuff + // if (resumeFile){ + // console.log("Uploading blob"); + // const res = await uploadResume({resumeFile}); + // if (res?.serverError){ + // return { + // success: true, + // message: + // "Registration created successfully but there was an error uploading your resume.", + // }; + // } + // console.log("Success posting"); + // } + + return { + success:true, + message:"Registration created successfully" + } + + } + ) + + + diff --git a/apps/web/src/actions/user-profile-mod.ts b/apps/web/src/actions/user-profile-mod.ts index 66693bce..31f8ee81 100644 --- a/apps/web/src/actions/user-profile-mod.ts +++ b/apps/web/src/actions/user-profile-mod.ts @@ -10,6 +10,7 @@ import { decodeBase64AsFile } from "@/lib/utils/shared/files"; import { returnValidationErrors } from "next-safe-action"; import { revalidatePath } from "next/cache"; import { getUser } from "db/functions"; +import c from "config"; // TODO: Add skill updating export const modifyRegistrationData = authenticatedAction diff --git a/apps/web/src/app/api/registration/create/route.ts b/apps/web/src/app/api/registration/create/route.ts index f41821af..f40e9f8a 100644 --- a/apps/web/src/app/api/registration/create/route.ts +++ b/apps/web/src/app/api/registration/create/route.ts @@ -3,128 +3,159 @@ import { NextResponse } from "next/server"; import { db } from "db"; import { sql } from "db/drizzle"; import { userCommonData, userHackerData } from "db/schema"; -import { RegisterFormValidator } from "@/validators/shared/RegisterForm"; +import { hackerRegistrationFormValidator } from "@/validators/shared/registration"; import c from "config"; import { z } from "zod"; import { getUser, getUserByTag } from "db/functions"; +import { del } from "@vercel/blob"; export async function POST(req: Request) { - const rawBody = await req.json(); - const parsedBody = RegisterFormValidator.merge( - z.object({ resume: z.string().url() }), - ).safeParse(rawBody); + let resume: string | undefined; + try { + const rawBody = await req.json(); + const parsedBody = hackerRegistrationFormValidator + .merge(z.object({ resume: z.string().url() })) + .safeParse(rawBody); - if (!parsedBody.success) { - return NextResponse.json( - { - success: false, - message: "Malformed request body.", - }, - { status: 400 }, - ); - } + if (!parsedBody.success) { + return NextResponse.json( + { + success: false, + message: "Malformed request body.", + }, + { status: 400 }, + ); + } - const body = parsedBody.data; - const user = await currentUser(); + const body = parsedBody.data; + resume = body.resume; + const user = await currentUser(); - if (!user) { - console.log("no user"); - return NextResponse.json( - { - success: false, - message: "You must be logged in to register.", - }, - { status: 401 }, + if (!user) { + if (resume !== c.noResumeProvidedURL) { + await del(body.resume); + } + console.log("no user"); + return NextResponse.json( + { + success: false, + message: "You must be logged in to register.", + }, + { status: 401 }, + ); + } + + const lookupByUserID = await getUser(user.id); + + if (lookupByUserID) { + if (resume !== c.noResumeProvidedURL) { + await del(body.resume); + } + return NextResponse.json( + { + success: false, + message: "You are already registered.", + }, + { status: 400 }, + ); + } + + const lookupByHackerTag = await getUserByTag( + body.hackerTag.toLowerCase(), ); - } - const lookupByUserID = await getUser(user.id); + if (lookupByHackerTag) { + if (body.resume !== c.noResumeProvidedURL) { + await del(body.resume); + } + return NextResponse.json({ + success: false, + message: "hackertag_not_unique", + }); + } - if (lookupByUserID) { - return NextResponse.json( - { + if (!body.hasAcceptedMLHCoC || !body.hasSharedDataWithMLH) { + if (body.resume !== c.noResumeProvidedURL) { + await del(body.resume); + } + return NextResponse.json({ success: false, - message: "You are already registered.", - }, - { status: 400 }, - ); - } + message: + "You must accept the MLH Code of Conduct and Privacy Policy.", + }); + } - const lookupByHackerTag = await getUserByTag(body.hackerTag.toLowerCase()); + const totalUserCount = await db + .select({ count: sql`count(*)`.mapWith(Number) }) + .from(userCommonData); - if (lookupByHackerTag) { - return NextResponse.json({ - success: false, - message: "hackertag_not_unique", + await db.transaction(async (tx) => { + await tx.insert(userCommonData).values({ + clerkID: user.id, + firstName: body.firstName, + lastName: body.lastName, + email: body.email, + hackerTag: body.hackerTag.toLowerCase(), + age: body.age, + gender: body.gender, + race: body.race, + ethnicity: body.ethnicity, + shirtSize: body.shirtSize, + dietRestrictions: body.dietRestrictions, + accommodationNote: body.accommodationNote || null, + discord: body.discord, + pronouns: body.pronouns, + bio: body.bio, + skills: body.skills.map((v) => v.text.toLowerCase()), + profilePhoto: user.imageUrl, + isFullyRegistered: true, + phoneNumber: body.phoneNumber, + isSearchable: body.isSearchable, + countryOfResidence: body.countryOfResidence, + }); + + await tx.insert(userHackerData).values({ + clerkID: user.id, + university: body.university, + major: body.major, + schoolID: body.schoolID, + levelOfStudy: body.levelOfStudy, + hackathonsAttended: body.hackathonsAttended, + softwareExperience: body.softwareExperience, + heardFrom: body.heardFrom || null, + GitHub: body.GitHub, + LinkedIn: body.LinkedIn, + PersonalWebsite: body.PersonalWebsite, + resume: body.resume, + group: totalUserCount[0].count % Object.keys(c.groups).length, + hasAcceptedMLHCoC: body.hasAcceptedMLHCoC, + hasSharedDataWithMLH: body.hasSharedDataWithMLH, + isEmailable: body.isEmailable, + }); }); - } - const totalUserCount = await db - .select({ count: sql`count(*)`.mapWith(Number) }) - .from(userCommonData); + // sendEmail({ + // to: body.email, + // subject: `You are now registered for ${c.hackathonName} ${c.itteration}!`, + // }); - if (!body.hasAcceptedMLHCoC || !body.hasSharedDataWithMLH) { return NextResponse.json({ - success: false, - message: - "You must accept the MLH Code of Conduct and Privacy Policy.", + success: true, + message: "Successfully created registration!", }); + } catch (e) { + console.error(`A fatal error occured: `, e); + if (resume) { + await del(resume); + } + return NextResponse.json( + { + success: false, + message: "A fatal error occured.", + }, + { status: 500 }, + ); } - - await db.transaction(async (tx) => { - await tx.insert(userCommonData).values({ - clerkID: user.id, - firstName: body.firstName, - lastName: body.lastName, - email: body.email, - hackerTag: body.hackerTag.toLowerCase(), - age: body.age, - gender: body.gender, - race: body.race, - ethnicity: body.ethnicity, - shirtSize: body.shirtSize, - dietRestrictions: body.dietaryRestrictions, - accommodationNote: body.accommodationNote || null, - discord: body.profileDiscordName, - pronouns: body.pronouns, - bio: body.bio, - skills: body.skills.map((v) => v.text.toLowerCase()), - profilePhoto: user.imageUrl, - isFullyRegistered: true, - phoneNumber: body.phoneNumber, - isSearchable: body.profileIsSearchable, - countryOfResidence: body.countryOfResidence, - }); - - await tx.insert(userHackerData).values({ - clerkID: user.id, - university: body.university, - major: body.major, - schoolID: body.schoolID, - levelOfStudy: body.levelOfStudy, - hackathonsAttended: body.hackathonsAttended, - softwareExperience: body.softwareBuildingExperience, - heardFrom: body.heardAboutEvent || null, - GitHub: body.github, - LinkedIn: body.linkedin, - PersonalWebsite: body.personalWebsite, - resume: body.resume, - group: totalUserCount[0].count % Object.keys(c.groups).length, - hasAcceptedMLHCoC: body.hasAcceptedMLHCoC, - hasSharedDataWithMLH: body.hasSharedDataWithMLH, - isEmailable: body.isEmailable, - }); - }); - - // sendEmail({ - // to: body.email, - // subject: `You are now registered for ${c.hackathonName} ${c.itteration}!`, - // }); - - return NextResponse.json({ - success: true, - message: "Successfully created registration!", - }); } export const runtime = "edge"; diff --git a/apps/web/src/components/landing/MLHBadge.tsx b/apps/web/src/components/landing/MLHBadge.tsx index ac22242b..7c0e49c6 100644 --- a/apps/web/src/components/landing/MLHBadge.tsx +++ b/apps/web/src/components/landing/MLHBadge.tsx @@ -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" > Major League Hacking 2024 Hackathon Season Major League Hacking 2024 Hackathon Season -

Creating Your Registration!

- +

+ {message} +

+ {hasSuccessState ? ( + + ) : ( + + )} ); } diff --git a/apps/web/src/components/registration/RegisterForm.tsx b/apps/web/src/components/registration/RegisterForm.tsx index 85feddfe..821fc736 100644 --- a/apps/web/src/components/registration/RegisterForm.tsx +++ b/apps/web/src/components/registration/RegisterForm.tsx @@ -19,8 +19,7 @@ import { } from "@/components/shadcn/ui/select"; import { Input } from "@/components/shadcn/ui/input"; import { Button } from "@/components/shadcn/ui/button"; -import { z } from "zod"; -import { RegisterFormValidator } from "@/validators/shared/RegisterForm"; +import { string, z } from "zod"; import { zodResolver } from "@hookform/resolvers/zod"; import FormGroupWrapper from "./FormGroupWrapper"; import { Checkbox } from "@/components/shadcn/ui/checkbox"; @@ -48,26 +47,33 @@ import { useAuth } from "@clerk/nextjs"; import { BasicServerValidator } from "@/validators/shared/basic"; import { useRouter } from "next/navigation"; import { FileRejection, useDropzone } from "react-dropzone"; -import { put, type PutBlobResult } from "@vercel/blob"; +import { put } from "@vercel/blob"; import { Tag, TagInput } from "@/components/shadcn/ui/tag/tag-input"; import CreatingRegistration from "./CreatingRegistration"; import { bucketResumeBaseUploadUrl } from "config"; -import { count } from "console"; -interface RegisterFormProps { - defaultEmail: string; -} +import { hackerRegistrationFormValidator } from "@/validators/shared/registration"; +import { formatRegistrationField } from "@/lib/utils/client/shared"; +import clsx from "clsx"; +import { capitalizeFirstLetter } from "@/lib/utils/client/shared"; +import RegistrationFeedbackAlert from "./RegistrationFeedbackAlert"; +import { registerUser,uploadResume } from "@/actions/registration"; +import { useAction } from "next-safe-action/hooks"; -export default function RegisterForm({ defaultEmail }: RegisterFormProps) { - const { isLoaded, userId } = useAuth(); +export default function RegisterForm({ + defaultEmail, +}: { + defaultEmail: string; +}) { + const { isLoaded } = useAuth(); const router = useRouter(); - const form = useForm>({ - resolver: zodResolver(RegisterFormValidator), + const form = useForm>({ + resolver: zodResolver(hackerRegistrationFormValidator), defaultValues: { email: defaultEmail, hackathonsAttended: 0, - dietaryRestrictions: [], - profileIsSearchable: true, + dietRestrictions: [], + isSearchable: true, bio: "", isEmailable: false, // The rest of these are default values to prevent the controller / uncontrolled input warning from React @@ -79,113 +85,101 @@ export default function RegisterForm({ defaultEmail }: RegisterFormProps) { age: 0, ethnicity: "" as any, gender: "" as any, - major: "", - github: "", + major: "" as any, + GitHub: "", hackerTag: "", - heardAboutEvent: "" as any, + heardFrom: "" as any, levelOfStudy: "" as any, - linkedin: "", - personalWebsite: "", - profileDiscordName: "", + LinkedIn: "", + PersonalWebsite: "", + discord: "", pronouns: "", race: "" as any, shirtSize: "" as any, schoolID: "", - university: "", + university: "" as any, phoneNumber: "", countryOfResidence: "", + softwareExperience: "" as any, + resumeFile: null, }, }); + + const { + execute: runRegisterUser, + status: registerUserStatus, + reset: resetRegisterUser, + } = useAction(registerUser,{ + onSuccess: ({ data}) => { + console.log("data is: ",data); + // Come back and uncoment after testing + if (data?.success){ + // setHasSuccess(true); + // setTimeout(() => { + // router.push("/dash"); + // }, 1000); + } + else{ + console.error("Error data:",data); + setErrorMessage(data?.message ?? 'Unexpected error occured'); + } + }, + onError: ({ error })=>{ + console.log("Error is: ",error); + resetRegisterUser(); + } + }); + + useEffect(() => { + const { unsubscribe } = form.watch((value) => { + console.log(value); + }); + return () => unsubscribe(); + }, [form.watch]); - const { isSubmitSuccessful, isSubmitted, errors } = form.formState; + const isLoading = registerUserStatus === 'executing'; + + const { isSubmitSuccessful, isSubmitted, } = form.formState; const hasErrors = !isSubmitSuccessful && isSubmitted; const [uploadedFile, setUploadedFile] = useState(null); const [skills, setSkills] = useState([]); - const [isLoading, setIsLoading] = useState(false); + const [hasSuccess, setHasSuccess] = useState(false); + const [errorMessage, setErrorMessage] = useState(null); + const universityValue = form.watch("university"); const bioValue = form.watch("bio"); - const countryValue = form.watch("countryOfResidence"); - + const classificationValue = form.watch("levelOfStudy"); + const isLocalUniversitySelected = + universityValue === c.localUniversityName && + classificationValue !== "Recent Grad"; + useEffect(() => { - if (universityValue != c.localUniversityName) { + if (universityValue !== c.localUniversityName || classificationValue === 'Recent Grad') { form.setValue("schoolID", "NOT_LOCAL_SCHOOL"); } else { form.setValue("schoolID", ""); } }, [universityValue]); - useEffect(() => { - console.log(countryValue); - }, [countryValue]); - - async function onSubmit(data: z.infer) { - console.log(data); - setIsLoading(true); - if (!userId || !isLoaded) { - setIsLoading(false); - return alert( - `Auth has not loaded yet. Please try again! If this is a repeating issue, please contact us at ${c.issueEmail}.`, - ); - } - - if ( - data.hasAcceptedMLHCoC !== true || - data.hasSharedDataWithMLH !== true - ) { - setIsLoading(false); - return alert( - "You must accept the MLH Code of Conduct and Privacy Policy to continue.", - ); - } - - let resume: string = c.noResumeProvidedURL; - - if (uploadedFile) { - const fileLocation = `${bucketResumeBaseUploadUrl}/${uploadedFile.name}`; - const newBlob = await put(fileLocation, uploadedFile, { - access: "public", - handleBlobUploadUrl: "/api/upload/resume/register", - }); - resume = newBlob.url; - } - - const res = await zpostSafe({ - url: "/api/registration/create", - body: { ...data, resume }, - vRes: BasicServerValidator, - }); - - if (res.success) { - if (res.data.success) { - alert( - "Registration successfully created! Redirecting to the dashboard.", - ); - router.push("/dash"); - } else { - if (res.data.message == "hackertag_not_unique") { - setIsLoading(false); - return alert( - "The HackerTag you chose has already been taken. Please change it and then resubmit the form.", - ); - } - setIsLoading(false); - return alert( - `Registration not created. Error message: \n\n ${res.data.message} \n\n Please try again. If this is a continuing issue, please reach out to us at ${c.issueEmail}.`, + function onSubmit( + data: z.infer, + ) { + console.log(data); + setErrorMessage(null); + if (!isLoaded) { + setErrorMessage( + `Auth has not loaded yet. Please try again! If this is a repeating issue, please contact us at ${c.issueEmail}.`, ); + return; } - } else { - setIsLoading(false); - alert( - `Something went wrong while attempting to register. Please try again. If this is a continuing issue, please reach out to us at ${c.issueEmail}.`, - ); - return console.log( - `Recieved a unexpected response from the server. Please try again. If this is a continuing issue, please reach out to us at ${c.issueEmail}.`, - ); - } + const stringiedUpload = JSON.stringify(uploadedFile); + console.log('stringiedUpload is',stringiedUpload); + runRegisterUser({...data,resumeFile:stringiedUpload}); + } - + const onDrop = useCallback( (acceptedFiles: File[], fileRejections: FileRejection[]) => { if (fileRejections.length > 0) { @@ -195,6 +189,7 @@ export default function RegisterForm({ defaultEmail }: RegisterFormProps) { } if (acceptedFiles.length > 0) { setUploadedFile(acceptedFiles[0]); + // form.setValue('resumeFile',acceptedFiles[0]); } }, [], @@ -208,1137 +203,1530 @@ export default function RegisterForm({ defaultEmail }: RegisterFormProps) { noDrag: uploadedFile != null, }); - if (isLoading) { - return ; - } - return ( -
-
- - -
- ( - - First Name - - - - - - )} - /> - ( - - Last Name - - - - - - )} - /> - ( - - Email - - 0 - } - {...field} - /> - - - - )} - /> - ( - - Phone Number - - - - - - )} - /> -
-
- ( - - Age - - - - - - )} - /> - ( - - Gender - + + + + )} + /> + ( + + + {formatRegistrationField( + "Last Name", + hackerRegistrationFormValidator.shape[ + field.name + ].isOptional(), + )} + + + + + + + )} + /> + ( + + + {formatRegistrationField( + "Email", + hackerRegistrationFormValidator.shape[ + field.name + ].isOptional(), + )} + + + + 0 + } + {...field} + /> + + + + )} + /> + ( + + + {formatRegistrationField( + "Phone Number", + hackerRegistrationFormValidator.shape[ + field.name + ].isOptional(), + )} + + + + + + + )} + /> +
+
+ ( + + + {formatRegistrationField( + "Age", + hackerRegistrationFormValidator.shape[ + field.name + ].isOptional(), + )} + + + + + + + )} + /> + ( + + + {formatRegistrationField( + "Gender", + hackerRegistrationFormValidator.shape[ + field.name + ].isOptional(), + )} + + + + + )} + /> + ( + + + {formatRegistrationField( + "Race", + hackerRegistrationFormValidator.shape[ + field.name + ].isOptional(), + )} + + + + + )} + /> + ( + + + {formatRegistrationField( + "Ethnicity", + hackerRegistrationFormValidator.shape[ + field.name + ].isOptional(), + )} + + + + + )} + /> + ( + + + {formatRegistrationField( + "Country of Residence", + hackerRegistrationFormValidator.shape[ + field.name + ].isOptional(), + )} + + + + + + + + + + + + + No country + found. + + + {c.registration.countries.map( + ( + country, + ) => ( + { + const countryResult = + c.registration.countries.find( + ( + countryObject, + ) => + countryObject.name === + country.name, + ); + field.onChange( + countryResult?.code ?? + "00", + ); + }} + className="cursor-pointer" + > + + { + country.name + } + + ), + )} + + + + + + + + )} + /> +
+
+ + ( + - - - + - - - - Male - - - Female - - - Non-binary - - - Other - - - Prefer not to say - - - - - - - )} - /> - ( - - Race - - - - )} - /> - ( - - Ethnicity - - - - )} - /> - ( - - - Country of Residence - -
- - - - + + + + + + + + No + university + found. + + + {c.registration.schools.map( ( - selectedCountry, - ) => - selectedCountry.code === - field.value, - )?.name - : "Select a Country"} - - - - - - - - - - No country - found. - - - {c.registration.countries.map( - ( - country, - ) => ( - { - const countryResult = - c.registration.countries.find( - ( - countryObject, - ) => - countryObject.name === - country.name, + school, + ) => ( + { + field.onChange( + value, ); - form.setValue( - "countryOfResidence", - countryResult?.code ?? - "00", - ); - }} - className="cursor-pointer" - > - - { - country.name - } - - ), - )} - - - - - -
- -
- )} - /> -
- - - ( - - - - -
- - I accept the{" "} - - MLH Code of Conduct - - - - This is required of all attendees. - -
-
- )} - /> - ( - - - - -
- - I authorize you to share my - application/registration information - with Major League Hacking for event - administration, ranking, and MLH - administration in-line with the MLH - Privacy Policy. I further agree to - the terms of both the{" "} - - MLH Contest Terms and Conditions - {" "} - and the{" "} - + + { + school + } + + ), + )} + + + + + + + + )} + /> + ( + - MLH Privacy Policy - - . - - - This is required of all attendees. - -
-
- )} - /> - ( - - - - -
- - I authorize MLH to send me an email - where I can further opt into the MLH - Hacker, Events, or Organizer - Newsletters and other communications - from MLH. - - - This is optional. - -
-
- )} - /> -
- -
- ( - - University - - + + {formatRegistrationField( + `${c.localUniversitySchoolIDName}`, + hackerRegistrationFormValidator.shape[ + field.name + ].isOptional(), + )} + - + - - - - - - - No university found. - - - {c.registration.schools.map( - (school) => ( - + + )} + /> + ( + + + {formatRegistrationField( + "Level of Study", + hackerRegistrationFormValidator.shape[ + field.name + ].isOptional(), + )} + + + + + )} + /> + + ( + + + {formatRegistrationField( + "Major", + hackerRegistrationFormValidator.shape[ + field.name + ].isOptional(), + )} + + + + + + + + + + + + + No major + found. + + + {c.registration.majors.map( + ( + major, + ) => ( + { + field.onChange( + value, + ); + }} + className="cursor-pointer" + > + + { + major + } + + ), + )} + + + + + + + + )} + /> +
+
+ +
+ ( + + + {formatRegistrationField( + "# of hackathons attended", + hackerRegistrationFormValidator.shape[ + field.name + ].isOptional(), + )} + - + - - - - - - - No major found. - - - {c.registration.majors.map( - (major) => ( - + + )} + /> + ( + + + {formatRegistrationField( + "Coding Experience", + hackerRegistrationFormValidator.shape[ + field.name + ].isOptional(), + )} + + - - - - - - - - - Freshman - - - Sophomore - - - Junior - - - Senior - - - Recent Grad - - - Other - - - - - - - )} - /> - ( - - - {c.localUniversitySchoolIDName} - - - - - - - )} - /> -
-
- -
- ( - - - # of Hackathons Attended - - - - - - - )} - /> - ( - - - Software Building Experience - - - - - )} - /> - ( - - - Where did you hear about{" "} - {c.hackathonName}? - - - - - )} - /> -
-
- -
- ( - - Shirt Size - - - - )} - /> - ( - -
- - Dietary Restrictions - - - Please select which dietary - restrictions you have so we can - best accomodate you at the - event! - -
- {c.registration.dietaryRestrictionOptions.map( - (item) => ( - { - return ( - + + + + + )} + /> + ( + + + {formatRegistrationField( + `Where did you hear about ${c.hackathonName}?`, + hackerRegistrationFormValidator.shape[ + field.name + ].isOptional(), + )} + + + + )} - -
- )} - /> - ( - - - Anything else we can do to better - accommodate you at our hackathon? - - -