diff --git a/apps/site/src/app/apply/page.tsx b/apps/site/src/app/apply/page.tsx new file mode 100644 index 00000000..3e30cec0 --- /dev/null +++ b/apps/site/src/app/apply/page.tsx @@ -0,0 +1,13 @@ +import Form from "./sections/Form/Form"; +import Title from "./sections/Title/Title"; + +export const revalidate = 60; + +export default function Apply() { + return ( +
+ + <Form /> + </div> + ); +} diff --git a/apps/site/src/app/apply/sections/Components/DropdownSelect.tsx b/apps/site/src/app/apply/sections/Components/DropdownSelect.tsx new file mode 100644 index 00000000..4c04a0ea --- /dev/null +++ b/apps/site/src/app/apply/sections/Components/DropdownSelect.tsx @@ -0,0 +1,71 @@ +"use client"; + +import { useState } from "react"; +import RequiredAsterisk from "./RequiredAsterisk"; + +interface SelectProps { + labelStyle: string; + inputStyle: string; + name: string; + labelText: string; + values: Array<{ value: string; text: string }>; + containerClass: string; +} + +interface OtherProps { + value: string; + name: string; +} + +const OtherPopup = ({ value, name }: OtherProps) => { + if (value == "other") { + return ( + <div className="mt-2 flex gap-2"> + <label htmlFor={`${name}-other-input`} className="text-lg"> + Other: <RequiredAsterisk /> + </label> + <input + type="text" + name={`${name}-other-input`} + id={`${name}-other-input`} + className="border-b-2 p-1 h-6 border-black w-6/12" + required + /> + </div> + ); + } +}; + +export default function DropdownSelect({ + labelStyle, + inputStyle, + name, + labelText, + values, + containerClass, +}: SelectProps) { + const [value, setValue] = useState(""); + + return ( + <div className={containerClass}> + <label className={`${labelStyle}`} htmlFor={name}> + {labelText} <RequiredAsterisk /> + </label> + <select + className={`${inputStyle}`} + name={name} + id={name} + onChange={(e) => setValue(e.target.value)} + > + {values.map((item, i) => { + return ( + <option key={`option-${i}`} value={item.value}> + {item.text} + </option> + ); + })} + </select> + <OtherPopup value={value} name={name} /> + </div> + ); +} diff --git a/apps/site/src/app/apply/sections/Components/RadioSelect.tsx b/apps/site/src/app/apply/sections/Components/RadioSelect.tsx new file mode 100644 index 00000000..4d74647a --- /dev/null +++ b/apps/site/src/app/apply/sections/Components/RadioSelect.tsx @@ -0,0 +1,105 @@ +"use client"; + +import { useState, useEffect, useRef, forwardRef } from "react"; + +interface RadioInputs { + name: string; + labelText: string; + IdentifierId: string; + values: Array<{ value: string; text: string }>; + containerClass: string; +} + +interface IsChecked { + isChecked: boolean; + id: string; +} + +const OtherInput = forwardRef<HTMLInputElement, IsChecked>( + ({ isChecked, id }, ref) => ( + <input + ref={ref} + type="text" + name={`${id}-other`} + className={ + isChecked + ? "border-b-2 p-1 h-6 border-black w-6/12" + : "border-b-2 p-1 h-6 border-black w-6/12 bg-transparent" + } + required={isChecked} + disabled={!isChecked} + /> + ), +); +OtherInput.displayName = "OtherInput"; + +export default function RadioSelect({ + name, + labelText, + IdentifierId, + values, + containerClass, +}: RadioInputs) { + const [isOtherChecked, setIsOtherChecked] = useState(false); + const otherRef = useRef<HTMLInputElement>(null); + + useEffect(() => { + if (isOtherChecked) { + otherRef.current?.focus(); + } + }, [isOtherChecked]); + + return ( + <div className={containerClass}> + <p className="m-0 text-lg mb-4"> + {labelText} <span className="text-[#FF2222]">*</span> + </p> + <div className="w-10/12 flex flex-col gap-2"> + {values.map((item, i) => { + const keyandId = `${IdentifierId}-${i}`; + if (item.value === "other") { + return ( + <div key={keyandId} className="flex gap-2"> + <input + id={keyandId} + type="radio" + key={`option-${i}`} + name={name} + value={item.value} + onChange={(e) => + setIsOtherChecked(e.target.checked) + } + required + /> + <label className="text-lg" htmlFor={keyandId}> + {item.text} + </label> + <OtherInput + isChecked={isOtherChecked} + id={IdentifierId} + ref={otherRef} + /> + </div> + ); + } + return ( + <div key={keyandId} className="flex gap-2"> + <input + id={keyandId} + type="radio" + key={`option-${i}`} + name={name} + value={item.value} + onChange={() => setIsOtherChecked(false)} + required + /> + <label className="text-lg" htmlFor={keyandId}> + {item.text} + </label> + </div> + ); + })} + </div> + </div> + ); +} diff --git a/apps/site/src/app/apply/sections/Components/RequiredAsterisk.tsx b/apps/site/src/app/apply/sections/Components/RequiredAsterisk.tsx new file mode 100644 index 00000000..24a5390b --- /dev/null +++ b/apps/site/src/app/apply/sections/Components/RequiredAsterisk.tsx @@ -0,0 +1,3 @@ +export default function RequiredAsterisk() { + return <span className="text-[#FF2222]">*</span>; +} diff --git a/apps/site/src/app/apply/sections/Components/SimpleRadio.tsx b/apps/site/src/app/apply/sections/Components/SimpleRadio.tsx new file mode 100644 index 00000000..59da1d90 --- /dev/null +++ b/apps/site/src/app/apply/sections/Components/SimpleRadio.tsx @@ -0,0 +1,59 @@ +import RequiredAsterisk from "./RequiredAsterisk"; + +interface TextfieldProps { + name: string; + values: Array<{ + id: string; + labelText: string; + inputValue: string; + }>; + title: string; + titleClass: string; + containerClassTotal: string; + containerClassInputLabels: string; + containerClassValues: string; + labelClass: string; + isRequired: boolean; +} + +export default function SimpleRadio({ + name, + values, + title, + titleClass, + containerClassTotal, + containerClassInputLabels, + containerClassValues, + labelClass, + isRequired, +}: TextfieldProps) { + return ( + <div className={containerClassTotal}> + <p className={titleClass}> + {`${title} `} + {isRequired && <RequiredAsterisk />} + </p> + <div className={containerClassValues}> + {values.map((value, i) => { + return ( + <div + key={`${name}-${i}`} + className={containerClassInputLabels} + > + <input + type="radio" + id={value.id} + name={name} + value={value.inputValue} + required={isRequired} + /> + <label htmlFor={value.id} className={labelClass}> + {value.labelText} + </label> + </div> + ); + })} + </div> + </div> + ); +} diff --git a/apps/site/src/app/apply/sections/Components/TextInput.tsx b/apps/site/src/app/apply/sections/Components/TextInput.tsx new file mode 100644 index 00000000..5c703829 --- /dev/null +++ b/apps/site/src/app/apply/sections/Components/TextInput.tsx @@ -0,0 +1,39 @@ +import RequiredAsterisk from "./RequiredAsterisk"; + +interface TextProps { + name: string; + labelClass: string; + labelText: string; + inputClass: string; + containerClass: string; + type: string; + placeholder: string; + isRequired: boolean; +} + +export default function TextInput({ + name, + labelClass, + labelText, + inputClass, + containerClass, + placeholder, + type, + isRequired, +}: TextProps) { + return ( + <div className={containerClass}> + <label className={labelClass} htmlFor={name}> + {`${labelText} `} {isRequired && <RequiredAsterisk />} + </label> + <input + className={inputClass} + type={type} + name={name} + id={name} + required={isRequired} + placeholder={placeholder} + /> + </div> + ); +} diff --git a/apps/site/src/app/apply/sections/Components/Textfield.tsx b/apps/site/src/app/apply/sections/Components/Textfield.tsx new file mode 100644 index 00000000..b9a3f34b --- /dev/null +++ b/apps/site/src/app/apply/sections/Components/Textfield.tsx @@ -0,0 +1,36 @@ +import RequiredAsterisk from "./RequiredAsterisk"; + +interface TextfieldProps { + name: string; + labelClass: string; + labelText: string; + inputClass: string; + containerClass: string; + isRequired: boolean; +} + +export default function Textfield({ + name, + labelClass, + labelText, + inputClass, + containerClass, + isRequired, +}: TextfieldProps) { + return ( + <div className={containerClass}> + <div className="flex flex-col w-full"> + <label className={labelClass} htmlFor={name}> + {`${labelText} `} + {isRequired && <RequiredAsterisk />} + </label> + <textarea + className={inputClass} + id={name} + name={name} + required={isRequired} + /> + </div> + </div> + ); +} diff --git a/apps/site/src/app/apply/sections/Form/AgeInformation.tsx b/apps/site/src/app/apply/sections/Form/AgeInformation.tsx new file mode 100644 index 00000000..deeaffa3 --- /dev/null +++ b/apps/site/src/app/apply/sections/Form/AgeInformation.tsx @@ -0,0 +1,45 @@ +import SimpleRadio from "@/app/apply/sections/Components/SimpleRadio"; + +const yesNoOptions = [ + { + id: "minor-yes", + labelText: "Yes", + inputValue: "Yes", + }, + { + id: "minor-no", + labelText: "No", + inputValue: "No", + }, +]; + +export default function AgeInformation() { + return ( + <div className="flex flex-col gap-5 w-11/12"> + <div className="flex flex-col gap-5"> + <p className="m-0 text-lg"> + Because of limitations imposed by UCI, we are legally not + allowed to host minors (those under 18) for IrvineHacks + 2024. By answering yes, you affirm that you are and will be + 18 years or older by January 26, 2024. + </p> + <p className="text-[#FF2222] m-0 text-lg"> + We will be checking ID. If you are a minor, you will be + turned away at the door. + </p> + </div> + + <SimpleRadio + name="minor-check" + values={yesNoOptions} + title="Will you be 18 years or older by January 26th, 2024?" + titleClass="text-xl font-bold m-0 text-center" + containerClassTotal="flex flex-col gap-1 w-full items-center" + isRequired={true} + labelClass="font-bold text-xl" + containerClassInputLabels="flex gap-2 items-center" + containerClassValues="flex gap-5" + /> + </div> + ); +} diff --git a/apps/site/src/app/apply/sections/Form/BasicInformation.tsx b/apps/site/src/app/apply/sections/Form/BasicInformation.tsx new file mode 100644 index 00000000..e5967a8c --- /dev/null +++ b/apps/site/src/app/apply/sections/Form/BasicInformation.tsx @@ -0,0 +1,72 @@ +import RadioSelect from "@/app/apply/sections/Components/RadioSelect"; +import TextInput from "@/app/apply/sections/Components/TextInput"; +import styles from "./Form.module.scss"; + +const pronouns = [ + { value: "he", text: "He/him/his" }, + { value: "she", text: "She/her/hers" }, + { value: "they", text: "They/them/theirs" }, + { value: "ze", text: "Ze/zir/zirs" }, + { value: "other", text: "Other:" }, +]; + +const ethnicity = [ + { value: "American", text: "American Indian or Alaskan" }, + { value: "Asian", text: "Asian or Pacific Islander" }, + { value: "Black", text: "Black or African American" }, + { value: "Hispanic", text: "Hispanic" }, + { value: "White", text: "White or Caucasian" }, + { value: "Two-or-more", text: "Two or more races" }, + { value: "Prefer not to answer", text: "Prefer not to answer" }, + { value: "other", text: "Other:" }, +]; + +export default function BasicInformation() { + return ( + <div className="flex flex-col gap-5 w-11/12"> + <p className="text-4xl m-0 font-bold text-center max-[700px]:text-3xl"> + Basic Information + </p> + + <div className="flex gap-5 w-full max-[1000px]:flex-col max-[1000px]:items-center"> + <TextInput + name="first-name" + labelClass={styles.label} + labelText="First Name" + inputClass={styles.input} + containerClass="flex flex-col w-6/12 max-[1000px]:w-full" + isRequired={true} + type="text" + placeholder="" + /> + <TextInput + name="last-name" + labelClass={styles.label} + labelText="Last Name" + inputClass={styles.input} + containerClass="flex flex-col w-6/12 max-[1000px]:w-full" + isRequired={true} + type="text" + placeholder="" + /> + </div> + + <div className="flex gap-5 w-full max-[1000px]:flex-col max-[1000px]:items-center"> + <RadioSelect + IdentifierId="pronouns-identifier" + name="pronouns" + labelText="Pronouns" + values={pronouns} + containerClass="flex flex-col w-6/12 max-[1000px]:w-full" + /> + <RadioSelect + IdentifierId="ethnicity-identifier" + name="ethnicity" + labelText="Race / Ethnicity" + values={ethnicity} + containerClass="flex flex-col w-6/12 max-[1000px]:w-full" + /> + </div> + </div> + ); +} diff --git a/apps/site/src/app/apply/sections/Form/Form.module.scss b/apps/site/src/app/apply/sections/Form/Form.module.scss new file mode 100644 index 00000000..4aba9cb4 --- /dev/null +++ b/apps/site/src/app/apply/sections/Form/Form.module.scss @@ -0,0 +1,21 @@ +.form { + background-color: #ffffff; + border-radius: 10px; +} + +.input { + background-color: #e1e1e1; + height: 38px; + padding: 5px; + border-radius: 4px; + font-size: 18px; +} + +.label { + font-size: 18px; + margin-bottom: 8px; +} + +.image { + z-index: -1; +} diff --git a/apps/site/src/app/apply/sections/Form/Form.tsx b/apps/site/src/app/apply/sections/Form/Form.tsx new file mode 100644 index 00000000..bf1946d4 --- /dev/null +++ b/apps/site/src/app/apply/sections/Form/Form.tsx @@ -0,0 +1,49 @@ +import Image from "next/image"; +import BasicInformation from "./BasicInformation"; +import AgeInformation from "./AgeInformation"; +import SchoolInformation from "./SchoolInformation"; +import ProfileInformation from "./ProfileInformation"; +import Button from "@/lib/components/Button/Button"; +import koiLeft from "@/assets/images/koi-swim-left.png"; +import koiRight from "@/assets/images/koi-swim-right.png"; +import styles from "./Form.module.scss"; + +export default function Form() { + return ( + <div className="relative w-full flex flex-col items-center"> + <Image + src={koiLeft} + height="250" + alt="Koi fish" + className={`${styles.image} absolute top-0 right-0`} + /> + <Image + src={koiRight} + height="250" + alt="Koi fish" + className={`${styles.image} absolute top-1/4 left-0`} + /> + <Image + src={koiLeft} + height="250" + alt="Koi fish" + className={`${styles.image} absolute top-1/2 right-0`} + /> + <Image + src={koiRight} + height="250" + alt="Koi fish" + className={`${styles.image} absolute top-3/4 left-0`} + /> + <form + className={`${styles.form} text-[#000000] w-8/12 flex flex-col items-center py-12 gap-10 z-1 max-[800px]:w-9/12 max-[400px]:w-11/12`} + > + <BasicInformation /> + <SchoolInformation /> + <ProfileInformation /> + <AgeInformation /> + <Button text="Submit Application" /> + </form> + </div> + ); +} diff --git a/apps/site/src/app/apply/sections/Form/ProfileInformation.tsx b/apps/site/src/app/apply/sections/Form/ProfileInformation.tsx new file mode 100644 index 00000000..1ff37023 --- /dev/null +++ b/apps/site/src/app/apply/sections/Form/ProfileInformation.tsx @@ -0,0 +1,54 @@ +import TextInput from "@/app/apply/sections/Components/TextInput"; +import Textfield from "@/app/apply/sections/Components/Textfield"; +import styles from "./Form.module.scss"; + +export default function ProfileInformation() { + return ( + <div className="flex flex-col gap-5 w-11/12"> + <p className="text-4xl m-0 font-bold text-center max-[700px]:text-3xl"> + Profile Information + </p> + + <div className="flex gap-5 w-full max-[1000px]:flex-col max-[1000px]:items-center"> + <TextInput + name="linkedin" + labelClass={styles.label} + labelText="LinkedIn" + inputClass={styles.input} + containerClass="flex flex-col w-6/12 max-[1000px]:w-full" + isRequired={false} + type="url" + placeholder="https://" + /> + <TextInput + name="portfolio" + labelClass={styles.label} + labelText="Portfolio (Github, website, etc.)" + inputClass={styles.input} + containerClass="flex flex-col w-6/12 max-[1000px]:w-full" + isRequired={false} + type="url" + placeholder="https://" + /> + </div> + + <Textfield + name="collaboration-answer" + labelClass={`${styles.label} mt-7`} + labelText="Why is collaboration important to being a programmer or technologist, and what does it mean to you? (150 words)" + inputClass={`bg-[#E1E1E1] p-3 h-48 resize-none rounded-xl`} + containerClass="flex flex-col w-full" + isRequired={true} + /> + + <Textfield + name="job-answer" + labelClass={`${styles.label} mt-7`} + labelText="If you could have any job in the world, what would it be? (ex. YouTuber, Body Builder, etc.) (100 words)" + inputClass={`bg-[#E1E1E1] p-3 h-48 resize-none rounded-xl`} + containerClass="flex flex-col w-full" + isRequired={true} + /> + </div> + ); +} diff --git a/apps/site/src/app/apply/sections/Form/SchoolInformation.tsx b/apps/site/src/app/apply/sections/Form/SchoolInformation.tsx new file mode 100644 index 00000000..6a031540 --- /dev/null +++ b/apps/site/src/app/apply/sections/Form/SchoolInformation.tsx @@ -0,0 +1,113 @@ +import styles from "./Form.module.scss"; +import DropdownSelect from "@/app/apply/sections/Components/DropdownSelect"; +import SimpleRadio from "@/app/apply/sections/Components/SimpleRadio"; + +//these values can be edited if backend needs it later on + +const educationLevels = [ + { value: "high school", text: "High School (18+)" }, + { value: "first-year-undergrad", text: "First Year Undergraduate" }, + { value: "second-year-undergrad", text: "Second Year Undergraduate" }, + { value: "third-year-undergrad", text: "Third Year Undergraduate" }, + { value: "fourth-year-undergrad", text: "Fourth Year Undergraduate" }, + { value: "fifth-year-undergrad", text: "Fifth+ Year Undergraduate" }, + { value: "graduate", text: "Graduate" }, +]; +const universityOptions = [ + { value: "UC Irvine", text: "UC Irvine" }, + { value: "Cal Poly Pomona", text: "Cal Poly Pomona" }, + { value: "Cal State Fullerton", text: "Cal State Fullerton" }, + { value: "Cal State Long Beach", text: "Cal State Long Beach" }, + { value: "UC Berkeley", text: "UC Berkeley" }, + { value: "UCLA", text: "UCLA" }, + { value: "UC Riverside", text: "UC Riverside" }, + { value: "UC San Diego", text: "UC San Diego" }, + { value: "UC Santa Barbara", text: "UC Santa Barbara" }, + { value: "other", text: "Other" }, +]; + +const majorOptions = [ + { + value: "Business Information Management", + text: "Business Information Management", + }, + { value: "Computer Game Science", text: "Computer Game Science" }, + { value: "Computer Science", text: "Computer Science" }, + { + value: "Computer Science and Engineering", + text: "Computer Science and Engineering", + }, + { value: "Data Science", text: "Data Science" }, + { value: "Informatics", text: "Informatics" }, + { value: "Electrical Engineering", text: "Electrical Engineering" }, + { value: "Software Engineering", text: "Software Engineering" }, + { value: "N/A (High School)", text: "N/A (High School)" }, + { value: "Undeclared", text: "Undeclared" }, + { value: "other", text: "Other" }, +]; + +const yesNoOptions = [ + { + id: "hack-yes", + labelText: "Yes", + inputValue: "Yes", + }, + { + id: "hack-no", + labelText: "No", + inputValue: "No", + }, +]; + +export default function SchoolInformation() { + return ( + <div className="flex flex-col gap-5 w-11/12"> + <p className="text-4xl m-0 font-bold text-center max-[700px]:text-3xl"> + Education + </p> + + <div className="flex gap-5 w-full max-[1000px]:flex-col max-[1000px]:items-center"> + <DropdownSelect + labelStyle={styles.label} + inputStyle={styles.input} + name="education-level" + labelText="Current Education Level" + values={educationLevels} + containerClass="flex flex-col w-6/12 max-[1000px]:w-full" + /> + + <DropdownSelect + labelStyle={styles.label} + inputStyle={styles.input} + name="school-name" + labelText="School Name" + values={universityOptions} + containerClass="flex flex-col w-6/12 max-[1000px]:w-full" + /> + </div> + + <div className="flex gap-5 items-end w-full max-[1000px]:flex-col max-[1000px]:items-center"> + <DropdownSelect + labelStyle={styles.label} + inputStyle={styles.input} + name="major" + labelText="What is your major?" + values={majorOptions} + containerClass="flex flex-col w-6/12 max-[1000px]:w-full" + /> + + <SimpleRadio + name="hack-check" + values={yesNoOptions} + title="Is this your first Hackathon?" + titleClass="text-lg mb-0 p-0" + containerClassTotal="flex gap-5 w-6/12 max-[1000px]:w-full mb-2 max-[1300px]:mb-0 max-[1300px]:flex-col max-[1300px]:gap-1 items-center text-center" + isRequired={true} + labelClass="font-bold" + containerClassInputLabels="flex gap-2 items-center" + containerClassValues="flex gap-3" + /> + </div> + </div> + ); +} diff --git a/apps/site/src/app/apply/sections/Title/Title.module.scss b/apps/site/src/app/apply/sections/Title/Title.module.scss new file mode 100644 index 00000000..3f271463 --- /dev/null +++ b/apps/site/src/app/apply/sections/Title/Title.module.scss @@ -0,0 +1,3 @@ +.title { + text-shadow: 0px 0px 20px rgba(255, 255, 255, 0.75); +} diff --git a/apps/site/src/app/apply/sections/Title/Title.tsx b/apps/site/src/app/apply/sections/Title/Title.tsx new file mode 100644 index 00000000..3a684fcf --- /dev/null +++ b/apps/site/src/app/apply/sections/Title/Title.tsx @@ -0,0 +1,16 @@ +import styles from "./Title.module.scss"; + +export default function Title() { + return ( + <> + <h1 + className={`${styles.title} font-display text-[#fffce2] text-8xl text-center max-[500px]:text-7xl`} + > + Apply + </h1> + <h2 className="text-[#fffce2] text-4xl font-bold m-5 text-center max-[900px]:text-3xl max-[500px]:text-2xl"> + Applications close on January 14th, 2024 at 11:59PM PST + </h2> + </> + ); +} diff --git a/apps/site/src/assets/images/koi-swim-left.png b/apps/site/src/assets/images/koi-swim-left.png new file mode 100644 index 00000000..ce41b491 Binary files /dev/null and b/apps/site/src/assets/images/koi-swim-left.png differ diff --git a/apps/site/src/assets/images/koi-swim-right.png b/apps/site/src/assets/images/koi-swim-right.png new file mode 100644 index 00000000..882d110f Binary files /dev/null and b/apps/site/src/assets/images/koi-swim-right.png differ