From 54ec152e29f14ec342ea994e688929d4acadd7b0 Mon Sep 17 00:00:00 2001 From: Celeste Roselli <72556836+celesteroselli@users.noreply.github.com> Date: Thu, 6 Feb 2025 10:22:50 -0500 Subject: [PATCH 01/24] Added links to 2022 and 2023 annual reports to philanthropy pag e, under 990s. --- pages/philanthropy/index.js | 45 +++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/pages/philanthropy/index.js b/pages/philanthropy/index.js index 3a2d12ba..80e95d48 100644 --- a/pages/philanthropy/index.js +++ b/pages/philanthropy/index.js @@ -685,6 +685,51 @@ const Philanthropy = ({ posts = [] }) => { firm and has audited financials through the current fiscal year. + + + + + View Hack Club's Annual Reports + 2024 Report will be shared when ready. + + + + + + + + Explore Hack Club's annual reports from 2022 onward, showcasing each year's impact and key milestones. + + {/* Date: Thu, 6 Mar 2025 03:08:32 -0800 Subject: [PATCH 02/24] [HCB] Non-functional refactor of apply form components --- .../apply/application-form.js | 79 +++++++++++++++++++ .../apply/form-container.js | 3 +- .../apply/org-adult-form.js | 48 +---------- .../apply/teenager-or-adult-form.js | 57 +++++++++++++ pages/fiscal-sponsorship/apply/index.js | 77 +----------------- 5 files changed, 143 insertions(+), 121 deletions(-) create mode 100644 components/fiscal-sponsorship/apply/application-form.js create mode 100644 components/fiscal-sponsorship/apply/teenager-or-adult-form.js diff --git a/components/fiscal-sponsorship/apply/application-form.js b/components/fiscal-sponsorship/apply/application-form.js new file mode 100644 index 00000000..2322ca47 --- /dev/null +++ b/components/fiscal-sponsorship/apply/application-form.js @@ -0,0 +1,79 @@ +import { useRouter } from 'next/router' +import { useRef, useState } from 'react' +import { Alert, Button, Heading } from 'theme-ui' +import FormContainer from './form-container' +import OrganizationInfoForm from './org-form' +import PersonalInfoForm from './personal-form' +import { onSubmit } from './submit' +import Callout from './callout' +import TeenagerOrAdultForm from './teenager-or-adult-form' + +export default function ApplicationForm() { + const router = useRouter() + const formContainer = useRef() + const [formError, setFormError] = useState(null) + const [isSubmitting, setIsSubmitting] = useState(false) + + const requiredFields = [ + 'eventName', + 'eventLocation', + 'eventPostalCode', + 'eventDescription', + 'eventTeenagerLed', + 'eventPoliticalActivity', + 'eventAnnualBudget', + 'firstName', + 'lastName', + 'userEmail', + 'userPhone', + 'userBirthday', + 'slackUsername' + ] + + return ( + + onSubmit({ + event, + router, + form: formContainer, + setFormError, + setIsSubmitting, + requiredFields + }) + } + > + + + + + + Your organization + + + + Personal details + + + {formError && {formError}} + + + ) +} diff --git a/components/fiscal-sponsorship/apply/form-container.js b/components/fiscal-sponsorship/apply/form-container.js index aa8707f4..7a653cc5 100644 --- a/components/fiscal-sponsorship/apply/form-container.js +++ b/components/fiscal-sponsorship/apply/form-container.js @@ -1,5 +1,6 @@ import { forwardRef } from 'react' import { Box, Container } from 'theme-ui' +import { TeenagerLedProvider } from '../../../components/fiscal-sponsorship/apply/teenager-led-context' const formContainer = forwardRef(({ children, ...props }, ref) => { return ( @@ -31,7 +32,7 @@ const formContainer = forwardRef(({ children, ...props }, ref) => { px: 0 }} > - {children} + {children} ) diff --git a/components/fiscal-sponsorship/apply/org-adult-form.js b/components/fiscal-sponsorship/apply/org-adult-form.js index 2d813d86..737382d1 100644 --- a/components/fiscal-sponsorship/apply/org-adult-form.js +++ b/components/fiscal-sponsorship/apply/org-adult-form.js @@ -1,58 +1,14 @@ -import { useEffect, useState } from 'react' -import { Input, Select, Textarea } from 'theme-ui' +import { Input, Textarea } from 'theme-ui' import Field from './field' import useOrganizationI18n from '../organizationI18n' import { useTeenagerLedContext } from './teenager-led-context' export default function OrganizationAdultForm({ requiredFields }) { const org = useOrganizationI18n() - const { teenagerLed, setTeenagerLed } = useTeenagerLedContext() - - const onTeenagerLedChange = e => { - const newValue = e.target.value - setTeenagerLed(newValue) - - if (newValue === 'true') { - // Clear cache of removed fields - sessionStorage.removeItem('bank-signup-eventPoliticalActivity') - sessionStorage.removeItem('bank-signup-eventAnnualBudget') - } - } - - useEffect(() => { - // [@garyhtou] welp... this exists because the Field component will cache - // input values and set them on page load. It does it by directly setting - // `input.value` with JavaScript; bypassing React. Because of that, the - // `teenagerLed` state may not be synced with the DOM input value. This code - // syncs `teenagerLed` with the DOM input value. - // NOTE: This depends on Field's useEffect hook to run first. - const eventTeenagerLedElm = document.getElementById('eventTeenagerLed') - setTeenagerLed(eventTeenagerLedElm.value) - }, []) + const { teenagerLed } = useTeenagerLedContext() return ( <> - - - - {teenagerLed !== 'true' && ( <> { + const newValue = e.target.value + setTeenagerLed(newValue) + + if (newValue === 'true') { + // Clear cache of removed fields + sessionStorage.removeItem('bank-signup-eventPoliticalActivity') + sessionStorage.removeItem('bank-signup-eventAnnualBudget') + } + } + + useEffect(() => { + // [@garyhtou] welp... this exists because the Field component will cache + // input values and set them on page load. It does it by directly setting + // `input.value` with JavaScript; bypassing React. Because of that, the + // `teenagerLed` state may not be synced with the DOM input value. This code + // syncs `teenagerLed` with the DOM input value. + // NOTE: This depends on Field's useEffect hook to run first. + const eventTeenagerLedElm = document.getElementById('eventTeenagerLed') + setTeenagerLed(eventTeenagerLedElm.value) + }, []) + + return ( + <> + + + + + ) +} diff --git a/pages/fiscal-sponsorship/apply/index.js b/pages/fiscal-sponsorship/apply/index.js index 77040d35..d19b4daa 100644 --- a/pages/fiscal-sponsorship/apply/index.js +++ b/pages/fiscal-sponsorship/apply/index.js @@ -1,43 +1,15 @@ -import { useRouter } from 'next/router' -import { useRef, useState } from 'react' -import { Alert, Box, Button, Flex, Grid, Heading, Text } from 'theme-ui' +import { Box, Flex, Grid, Heading, Text } from 'theme-ui' import Head from 'next/head' import Link from 'next/link' import Icon from '@hackclub/icons' import Meta from '@hackclub/meta' import ForceTheme from '../../../components/force-theme' -import FormContainer from '../../../components/fiscal-sponsorship/apply/form-container' import HCBInfo from '../../../components/fiscal-sponsorship/apply/hcb-info' -import OrganizationInfoForm from '../../../components/fiscal-sponsorship/apply/org-form' -import PersonalInfoForm from '../../../components/fiscal-sponsorship/apply/personal-form' -import { onSubmit } from '../../../components/fiscal-sponsorship/apply/submit' import Watermark from '../../../components/fiscal-sponsorship/apply/watermark' import ContactBanner from '../../../components/fiscal-sponsorship/contact' -import Callout from '../../../components/fiscal-sponsorship/apply/callout' -import { TeenagerLedProvider } from '../../../components/fiscal-sponsorship/apply/teenager-led-context' +import ApplicationForm from '../../../components/fiscal-sponsorship/apply/application-form' export default function Apply() { - const router = useRouter() - const formContainer = useRef() - const [formError, setFormError] = useState(null) - const [isSubmitting, setIsSubmitting] = useState(false) - - const requiredFields = [ - 'eventName', - 'eventLocation', - 'eventPostalCode', - 'eventDescription', - 'eventTeenagerLed', - 'eventPoliticalActivity', - 'eventAnnualBudget', - 'firstName', - 'lastName', - 'userEmail', - 'userPhone', - 'userBirthday', - 'slackUsername' - ] - return ( <> @@ -112,50 +84,7 @@ export default function Apply() { sx={{ borderRadius: 'default', bg: 'snow', width: 'fit-content' }} /> - - onSubmit({ - event, - router, - form: formContainer, - setFormError, - setIsSubmitting, - requiredFields - }) - } - > - - - - - Your organization - - - - Personal details - - - {formError && {formError}} - - - + From 353a893d31a8efcf5323081db758de517af88810 Mon Sep 17 00:00:00 2001 From: Gary Tou Date: Thu, 6 Mar 2025 03:31:03 -0800 Subject: [PATCH 03/24] Add jank multi step form --- .../apply/application-form.js | 57 ++++++++--------- .../apply/multi-step-context.js | 19 ++++++ .../apply/multi-step-form.js | 63 +++++++++++++++++++ pages/fiscal-sponsorship/apply/index.js | 5 +- 4 files changed, 113 insertions(+), 31 deletions(-) create mode 100644 components/fiscal-sponsorship/apply/multi-step-context.js create mode 100644 components/fiscal-sponsorship/apply/multi-step-form.js diff --git a/components/fiscal-sponsorship/apply/application-form.js b/components/fiscal-sponsorship/apply/application-form.js index 2322ca47..70f43558 100644 --- a/components/fiscal-sponsorship/apply/application-form.js +++ b/components/fiscal-sponsorship/apply/application-form.js @@ -1,18 +1,21 @@ import { useRouter } from 'next/router' import { useRef, useState } from 'react' -import { Alert, Button, Heading } from 'theme-ui' +import { Alert, Heading } from 'theme-ui' import FormContainer from './form-container' import OrganizationInfoForm from './org-form' import PersonalInfoForm from './personal-form' import { onSubmit } from './submit' import Callout from './callout' import TeenagerOrAdultForm from './teenager-or-adult-form' +import { useMultiStepContext } from './multi-step-context' +import MultiStepForm from './multi-step-form' export default function ApplicationForm() { const router = useRouter() const formContainer = useRef() const [formError, setFormError] = useState(null) const [isSubmitting, setIsSubmitting] = useState(false) + const { step } = useMultiStepContext() const requiredFields = [ 'eventName', @@ -45,35 +48,29 @@ export default function ApplicationForm() { }) } > - - - - - - Your organization - - - - Personal details - - - {formError && {formError}} - + + {step === 1 ? ( + <> + + + + ) : step === 2 ? ( + <> + + Your organization + + + + ) : ( + <> + + Personal details + + + {formError && {formError}} + + )} + ) } diff --git a/components/fiscal-sponsorship/apply/multi-step-context.js b/components/fiscal-sponsorship/apply/multi-step-context.js new file mode 100644 index 00000000..61ea1a0a --- /dev/null +++ b/components/fiscal-sponsorship/apply/multi-step-context.js @@ -0,0 +1,19 @@ +import { createContext, useContext, useState } from 'react' + +const MultiStepContext = createContext() +const useMultiStepContext = () => useContext(MultiStepContext) + +const MultiStepProvider = ({ children }) => { + const [step, setStep] = useState(1) + const modifyStep = direction => { + setStep(step + direction) // either 1 or -1 + } + + return ( + + {children} + + ) +} + +export { MultiStepProvider, useMultiStepContext } diff --git a/components/fiscal-sponsorship/apply/multi-step-form.js b/components/fiscal-sponsorship/apply/multi-step-form.js new file mode 100644 index 00000000..f0735f4a --- /dev/null +++ b/components/fiscal-sponsorship/apply/multi-step-form.js @@ -0,0 +1,63 @@ +import { Box, Button } from 'theme-ui' +import { useMultiStepContext } from './multi-step-context' + +export default function MultiStepForm({ isSubmitting, maxSteps, children }) { + const { step, modifyStep } = useMultiStepContext() + // TODO: it shows form validation errors whens switching to a new page + + return ( + <> + {children} + + + {step > 1 && ( + + )} + + {step < maxSteps && ( + + )} + + {step === maxSteps && ( + + )} + + + ) +} diff --git a/pages/fiscal-sponsorship/apply/index.js b/pages/fiscal-sponsorship/apply/index.js index d19b4daa..a8428df2 100644 --- a/pages/fiscal-sponsorship/apply/index.js +++ b/pages/fiscal-sponsorship/apply/index.js @@ -8,6 +8,7 @@ import HCBInfo from '../../../components/fiscal-sponsorship/apply/hcb-info' import Watermark from '../../../components/fiscal-sponsorship/apply/watermark' import ContactBanner from '../../../components/fiscal-sponsorship/contact' import ApplicationForm from '../../../components/fiscal-sponsorship/apply/application-form' +import { MultiStepProvider } from '../../../components/fiscal-sponsorship/apply/multi-step-context' export default function Apply() { return ( @@ -84,7 +85,9 @@ export default function Apply() { sx={{ borderRadius: 'default', bg: 'snow', width: 'fit-content' }} /> - + + + From 20c042a4ba31b9335f6a703a5710c6ea0f6502cc Mon Sep 17 00:00:00 2001 From: Gus Ruben <95830851+gusruben@users.noreply.github.com> Date: Thu, 6 Mar 2025 16:01:30 -0500 Subject: [PATCH 04/24] Merge pull request #1475 from gusruben/scrapyard-card Add Scrapyard Card + Remove High Seas and Counterspell Cards --- components/index/cards/scrapyard.js | 182 ++++++++++++++++++++++++++++ pages/index.js | 6 +- 2 files changed, 184 insertions(+), 4 deletions(-) create mode 100644 components/index/cards/scrapyard.js diff --git a/components/index/cards/scrapyard.js b/components/index/cards/scrapyard.js new file mode 100644 index 00000000..f6c9db96 --- /dev/null +++ b/components/index/cards/scrapyard.js @@ -0,0 +1,182 @@ +import CardModel from './card-model' +import { Box, Flex, Grid, Image, Text, Heading } from 'theme-ui' +import Buttons from './button' +import { Global } from '@emotion/react' + +/** @jsxImportSource theme-ui */ + +export default function Scrapyard() { + return ( + + + + + + Scrapyard + + + + + + + Build stupid stuff, get stupid prizes. + + + + + 100+ Cities worldwide – March 15-16 + + + + + + + + + + + Learn More + + + + + + + ) +} diff --git a/pages/index.js b/pages/index.js index 22c22985..0af6e9c0 100644 --- a/pages/index.js +++ b/pages/index.js @@ -40,8 +40,7 @@ import Comma from '../components/comma' import Haxidraw from '../components/index/cards/haxidraw' import Onboard from '../components/index/cards/onboard' import Trail from '../components/index/cards/trail' -import HighSeas from '../components/index/cards/highseas' -import Counterspell from '../components/index/cards/counterspell' +import Scrapyard from '../components/index/cards/scrapyard' /** @jsxImportSource theme-ui */ function Page({ @@ -669,8 +668,7 @@ function Page({ and make things together! - - + From 0018faf5272e6fdc0362907845d9d21a43489bfc Mon Sep 17 00:00:00 2001 From: Gary Tou Date: Thu, 6 Mar 2025 13:39:26 -0800 Subject: [PATCH 05/24] [MultiStepForm] Clean up and abstract out stepping logic --- .../apply/application-form.js | 66 ++++++++++++------- .../apply/form-container.js | 5 +- .../apply/multi-step-context.js | 26 ++++++-- .../apply/multi-step-form.js | 48 ++++---------- pages/fiscal-sponsorship/apply/index.js | 5 +- 5 files changed, 80 insertions(+), 70 deletions(-) diff --git a/components/fiscal-sponsorship/apply/application-form.js b/components/fiscal-sponsorship/apply/application-form.js index 70f43558..f1bbe447 100644 --- a/components/fiscal-sponsorship/apply/application-form.js +++ b/components/fiscal-sponsorship/apply/application-form.js @@ -1,13 +1,12 @@ import { useRouter } from 'next/router' import { useRef, useState } from 'react' -import { Alert, Heading } from 'theme-ui' +import { Alert, Heading, Button } from 'theme-ui' import FormContainer from './form-container' import OrganizationInfoForm from './org-form' import PersonalInfoForm from './personal-form' import { onSubmit } from './submit' import Callout from './callout' import TeenagerOrAdultForm from './teenager-or-adult-form' -import { useMultiStepContext } from './multi-step-context' import MultiStepForm from './multi-step-form' export default function ApplicationForm() { @@ -15,7 +14,6 @@ export default function ApplicationForm() { const formContainer = useRef() const [formError, setFormError] = useState(null) const [isSubmitting, setIsSubmitting] = useState(false) - const { step } = useMultiStepContext() const requiredFields = [ 'eventName', @@ -33,6 +31,25 @@ export default function ApplicationForm() { 'slackUsername' ] + const submitButton = ( + + ) + return ( - - {step === 1 ? ( - <> - - - - ) : step === 2 ? ( - <> - - Your organization - - - - ) : ( - <> - - Personal details - - - {formError && {formError}} - - )} + + {/* Step 1 */} + <> + + + + {/* Step 2 */} + <> + + Your organization + + + + {/* Step 3 */} + <> + + Personal details + + + {formError && {formError}} + ) diff --git a/components/fiscal-sponsorship/apply/form-container.js b/components/fiscal-sponsorship/apply/form-container.js index 7a653cc5..a4b468db 100644 --- a/components/fiscal-sponsorship/apply/form-container.js +++ b/components/fiscal-sponsorship/apply/form-container.js @@ -1,6 +1,7 @@ import { forwardRef } from 'react' import { Box, Container } from 'theme-ui' import { TeenagerLedProvider } from '../../../components/fiscal-sponsorship/apply/teenager-led-context' +import { MultiStepProvider } from './multi-step-context' const formContainer = forwardRef(({ children, ...props }, ref) => { return ( @@ -32,7 +33,9 @@ const formContainer = forwardRef(({ children, ...props }, ref) => { px: 0 }} > - {children} + + {children} + ) diff --git a/components/fiscal-sponsorship/apply/multi-step-context.js b/components/fiscal-sponsorship/apply/multi-step-context.js index 61ea1a0a..7e82182e 100644 --- a/components/fiscal-sponsorship/apply/multi-step-context.js +++ b/components/fiscal-sponsorship/apply/multi-step-context.js @@ -4,13 +4,31 @@ const MultiStepContext = createContext() const useMultiStepContext = () => useContext(MultiStepContext) const MultiStepProvider = ({ children }) => { - const [step, setStep] = useState(1) - const modifyStep = direction => { - setStep(step + direction) // either 1 or -1 + const [step, setStep] = useState(0) + + const useStepper = steps => { + const modifyStep = number => { + const newStep = step + number + + // Guard against invalid step numbers + if (newStep < 0 || newStep > steps.length - 1) { + console.error( + `[MultiStepProvider] Invalid new step number: ${newStep}. Current step number: ${step}` + ) + return + } + + setStep(newStep) + } + + return { + nextStep: () => modifyStep(1), + previousStep: () => modifyStep(-1) + } } return ( - + {children} ) diff --git a/components/fiscal-sponsorship/apply/multi-step-form.js b/components/fiscal-sponsorship/apply/multi-step-form.js index f0735f4a..ebe27c39 100644 --- a/components/fiscal-sponsorship/apply/multi-step-form.js +++ b/components/fiscal-sponsorship/apply/multi-step-form.js @@ -1,13 +1,16 @@ import { Box, Button } from 'theme-ui' import { useMultiStepContext } from './multi-step-context' +import { Children } from 'react' -export default function MultiStepForm({ isSubmitting, maxSteps, children }) { - const { step, modifyStep } = useMultiStepContext() - // TODO: it shows form validation errors whens switching to a new page +export default function MultiStepForm({ children, submitButton }) { + const { step, useStepper } = useMultiStepContext() + const steps = Children.toArray(children) + const { nextStep, previousStep } = useStepper(steps) return ( <> - {children} + {/* Render current step */} + {steps[step]} - {step > 1 && ( - )} - {step < maxSteps && ( - )} - {step === maxSteps && ( - - )} + {step === steps.length - 1 && submitButton} ) diff --git a/pages/fiscal-sponsorship/apply/index.js b/pages/fiscal-sponsorship/apply/index.js index a8428df2..d19b4daa 100644 --- a/pages/fiscal-sponsorship/apply/index.js +++ b/pages/fiscal-sponsorship/apply/index.js @@ -8,7 +8,6 @@ import HCBInfo from '../../../components/fiscal-sponsorship/apply/hcb-info' import Watermark from '../../../components/fiscal-sponsorship/apply/watermark' import ContactBanner from '../../../components/fiscal-sponsorship/contact' import ApplicationForm from '../../../components/fiscal-sponsorship/apply/application-form' -import { MultiStepProvider } from '../../../components/fiscal-sponsorship/apply/multi-step-context' export default function Apply() { return ( @@ -85,9 +84,7 @@ export default function Apply() { sx={{ borderRadius: 'default', bg: 'snow', width: 'fit-content' }} /> - - - + From 89b8af9bede78b158aa15a2c67275154cbe1d0ed Mon Sep 17 00:00:00 2001 From: Gary Tou Date: Thu, 6 Mar 2025 13:39:47 -0800 Subject: [PATCH 06/24] [Checkbox] Remove invalid HTML attribute --- components/fiscal-sponsorship/apply/checkbox.js | 1 - 1 file changed, 1 deletion(-) diff --git a/components/fiscal-sponsorship/apply/checkbox.js b/components/fiscal-sponsorship/apply/checkbox.js index 5ec96ed8..34dbc979 100644 --- a/components/fiscal-sponsorship/apply/checkbox.js +++ b/components/fiscal-sponsorship/apply/checkbox.js @@ -28,7 +28,6 @@ export default function Checkbox({ name, defaultChecked = false, size = 38 }) { name={name} aria-checked={checked} role="checkbox" - tabindex="0" onClick={() => toggle()} onKeyDown={e => e.key === 'Enter' && toggle()} /> From 39926188c58d7f30ba3803d56fbab1edefef14bd Mon Sep 17 00:00:00 2001 From: Gary Tou Date: Thu, 6 Mar 2025 14:50:46 -0800 Subject: [PATCH 07/24] Fix form required validation and create with title --- .../apply/application-form.js | 20 ++++++----------- .../apply/multi-step-form.js | 22 +++++++++++++++++-- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/components/fiscal-sponsorship/apply/application-form.js b/components/fiscal-sponsorship/apply/application-form.js index f1bbe447..535784e3 100644 --- a/components/fiscal-sponsorship/apply/application-form.js +++ b/components/fiscal-sponsorship/apply/application-form.js @@ -67,26 +67,20 @@ export default function ApplicationForm() { > {/* Step 1 */} - <> + - + {/* Step 2 */} - <> - - Your organization - + - + {/* Step 3 */} - <> - - Personal details - + - {formError && {formError}} - + + {formError && {formError}} ) } diff --git a/components/fiscal-sponsorship/apply/multi-step-form.js b/components/fiscal-sponsorship/apply/multi-step-form.js index ebe27c39..83d9892e 100644 --- a/components/fiscal-sponsorship/apply/multi-step-form.js +++ b/components/fiscal-sponsorship/apply/multi-step-form.js @@ -1,4 +1,4 @@ -import { Box, Button } from 'theme-ui' +import { Box, Button, Heading } from 'theme-ui' import { useMultiStepContext } from './multi-step-context' import { Children } from 'react' @@ -10,7 +10,11 @@ export default function MultiStepForm({ children, submitButton }) { return ( <> {/* Render current step */} - {steps[step]} + {steps.map((stepComponent, index) => ( + + {stepComponent} + + ))} ) } + +function Step({ children, title }) { + return ( + <> + {title && ( + + {title} + + )} + {children} + + ) +} +MultiStepForm.Step = Step From 3bc355f34794007be3903c86ab9ed1418fac83f5 Mon Sep 17 00:00:00 2001 From: Gary Tou Date: Thu, 6 Mar 2025 14:53:02 -0800 Subject: [PATCH 08/24] Add comment about rendering all form fields to DOM --- components/fiscal-sponsorship/apply/multi-step-form.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/components/fiscal-sponsorship/apply/multi-step-form.js b/components/fiscal-sponsorship/apply/multi-step-form.js index 83d9892e..46392b36 100644 --- a/components/fiscal-sponsorship/apply/multi-step-form.js +++ b/components/fiscal-sponsorship/apply/multi-step-form.js @@ -9,7 +9,10 @@ export default function MultiStepForm({ children, submitButton }) { return ( <> - {/* Render current step */} + {/* + We must render all form fields to DOM so that they can be submitted + with the form. So, we simple hide all non-current steps. + */} {steps.map((stepComponent, index) => ( {stepComponent} From 7441f8d150f8a43fae45413fcbb4b362020596a5 Mon Sep 17 00:00:00 2001 From: Gary Tou Date: Thu, 6 Mar 2025 16:51:42 -0800 Subject: [PATCH 09/24] Add friendlier validation error and fix `slackUsername` validation --- .../apply/application-form.js | 31 +++++++-------- components/fiscal-sponsorship/apply/field.js | 2 +- components/fiscal-sponsorship/apply/submit.js | 38 +++++++++++++------ 3 files changed, 43 insertions(+), 28 deletions(-) diff --git a/components/fiscal-sponsorship/apply/application-form.js b/components/fiscal-sponsorship/apply/application-form.js index 535784e3..1ad09ed8 100644 --- a/components/fiscal-sponsorship/apply/application-form.js +++ b/components/fiscal-sponsorship/apply/application-form.js @@ -15,21 +15,22 @@ export default function ApplicationForm() { const [formError, setFormError] = useState(null) const [isSubmitting, setIsSubmitting] = useState(false) - const requiredFields = [ - 'eventName', - 'eventLocation', - 'eventPostalCode', - 'eventDescription', - 'eventTeenagerLed', - 'eventPoliticalActivity', - 'eventAnnualBudget', - 'firstName', - 'lastName', - 'userEmail', - 'userPhone', - 'userBirthday', - 'slackUsername' - ] + const requiredFields = { + // Key: form field name + // Value: humanize field name used in error message + eventName: 'organization name', + eventLocation: 'organization country', + eventPostalCode: 'organization zip/postal code', + eventDescription: 'organization description', + eventTeenagerLed: 'are you a teenager?', + eventPoliticalActivity: "organization's political activity", + eventAnnualBudget: 'organization annual budget', + firstName: 'first name', + lastName: 'last name', + userEmail: 'email', + userPhone: 'phone number', + userBirthday: 'birthday', + } const submitButton = ( )} From 7855281b3f371722e1728382cc9ed27a79254081 Mon Sep 17 00:00:00 2001 From: Gary Tou Date: Fri, 7 Mar 2025 01:46:22 -0800 Subject: [PATCH 11/24] Minor copy edit --- components/fiscal-sponsorship/apply/application-form.js | 2 +- components/fiscal-sponsorship/apply/callout.js | 3 ++- .../fiscal-sponsorship/apply/teenager-or-adult-form.js | 7 +++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/components/fiscal-sponsorship/apply/application-form.js b/components/fiscal-sponsorship/apply/application-form.js index 917f21fb..56b93634 100644 --- a/components/fiscal-sponsorship/apply/application-form.js +++ b/components/fiscal-sponsorship/apply/application-form.js @@ -68,7 +68,7 @@ export default function ApplicationForm() { > {/* Step 1 */} - + diff --git a/components/fiscal-sponsorship/apply/callout.js b/components/fiscal-sponsorship/apply/callout.js index 46e27d2a..39f691c1 100644 --- a/components/fiscal-sponsorship/apply/callout.js +++ b/components/fiscal-sponsorship/apply/callout.js @@ -10,7 +10,8 @@ export default function Callout() { color: 'blue', fontSize: 1, textWrap: 'pretty', - lineHeight: 1.375 + lineHeight: 1.375, + marginBottom: '2rem' }} > diff --git a/components/fiscal-sponsorship/apply/teenager-or-adult-form.js b/components/fiscal-sponsorship/apply/teenager-or-adult-form.js index 275cb751..0552c517 100644 --- a/components/fiscal-sponsorship/apply/teenager-or-adult-form.js +++ b/components/fiscal-sponsorship/apply/teenager-or-adult-form.js @@ -1,11 +1,9 @@ import { useEffect } from 'react' import { Select } from 'theme-ui' import Field from './field' -import useOrganizationI18n from '../organizationI18n' import { useTeenagerLedContext } from './teenager-led-context' export default function TeenagerOrAdultForm({ requiredFields }) { - const org = useOrganizationI18n() const { teenagerLed, setTeenagerLed } = useTeenagerLedContext() const onTeenagerLedChange = e => { @@ -52,6 +50,11 @@ export default function TeenagerOrAdultForm({ requiredFields }) { ))} +

+ NOTE: this kinda ugly rn, i plan on making this teenager question look a + bit better by having two boxes side by side. one box/button for teen, + another for adult +

) } From 27aa406f52e73cf25507fcb0e19d323a90ddb867 Mon Sep 17 00:00:00 2001 From: Gary Tou Date: Fri, 7 Mar 2025 01:53:04 -0800 Subject: [PATCH 12/24] Copy edits --- components/fiscal-sponsorship/apply/hcb-info.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/components/fiscal-sponsorship/apply/hcb-info.js b/components/fiscal-sponsorship/apply/hcb-info.js index 4e19f4be..15674e31 100644 --- a/components/fiscal-sponsorship/apply/hcb-info.js +++ b/components/fiscal-sponsorship/apply/hcb-info.js @@ -8,7 +8,7 @@ export default function HCBInfo() { gridArea: 'info', alignItems: 'start', mark: { color: '#ec555c', bg: 'inherit' }, - ul: { pl: [3, 0], color: 'muted', mb: 4 }, + ul: { pl: 3, color: 'muted', mb: 4 }, p: { color: 'muted', mb: 0 } }} > @@ -28,14 +28,15 @@ export default function HCBInfo() {
    -
  • Nonprofit status.
  • -
  • Tax-deductible donations.
  • +
  • Gives your project nonprofit status.
  • +
  • Enables tax-deductible donations.
HCB provides a financial platform.
    -
  • A donations page and invoicing system.
  • +
  • Accessed via a beautiful, modern interface.
  • +
  • Provides a donation page and invoicing system.
  • Transfer money electronically.
  • Order cards for you and your team to make purchases.
From 4c79cf99e06108e9ae797a92c230d71407606ce7 Mon Sep 17 00:00:00 2001 From: Gary Tou Date: Fri, 7 Mar 2025 02:14:11 -0800 Subject: [PATCH 13/24] Copy edit + Hide HCB info on mobile after first form step --- .../apply/form-container.js | 6 +- .../fiscal-sponsorship/apply/hcb-info.js | 35 ++--- .../apply/teenager-or-adult-form.js | 2 +- pages/fiscal-sponsorship/apply/index.js | 137 +++++++++--------- 4 files changed, 86 insertions(+), 94 deletions(-) diff --git a/components/fiscal-sponsorship/apply/form-container.js b/components/fiscal-sponsorship/apply/form-container.js index a4b468db..b63268b9 100644 --- a/components/fiscal-sponsorship/apply/form-container.js +++ b/components/fiscal-sponsorship/apply/form-container.js @@ -11,7 +11,7 @@ const formContainer = forwardRef(({ children, ...props }, ref) => { sx={{ bg: 'snow', px: [3, 5], - py: 5, + py: [1, 5], minHeight: '100dvb', '&.has-errors div[aria-required="true"] input:placeholder-shown': { borderColor: 'primary' @@ -33,9 +33,7 @@ const formContainer = forwardRef(({ children, ...props }, ref) => { px: 0 }} > - - {children} - + {children}
) diff --git a/components/fiscal-sponsorship/apply/hcb-info.js b/components/fiscal-sponsorship/apply/hcb-info.js index 15674e31..21c312f4 100644 --- a/components/fiscal-sponsorship/apply/hcb-info.js +++ b/components/fiscal-sponsorship/apply/hcb-info.js @@ -1,10 +1,16 @@ import { Box, Link, Heading } from 'theme-ui' import Icon from '../../icon' +import { useMultiStepContext } from './multi-step-context' +import { useEffect } from 'react' export default function HCBInfo() { + const { step } = useMultiStepContext() + const firstStep = step === 0 + return (
  • Gives your project nonprofit status.
  • Enables tax-deductible donations.
  • +
  • + HCB is not a bank. We partner partner with{' '} + + Column N.A. + {' '} + to offer restricted funds to fiscally-sponsored projects. +
  • HCB provides a financial platform. @@ -40,28 +53,6 @@ export default function HCBInfo() {
  • Transfer money electronically.
  • Order cards for you and your team to make purchases.
  • - HCB is not a bank. -
      -
    • - We partner with{' '} - - Column N.A. - {' '} - to offer restricted funds to fiscally-sponsored projects. -
    • -
    • - You can't deposit or withdraw cash. But you can receive any kind of - electronic payment! -
    • -
    - HCB is not for for-profits. -

    - If you’re looking to set up a for-profit entity, consider{' '} - - Stripe Atlas - - . -

    ) } diff --git a/components/fiscal-sponsorship/apply/teenager-or-adult-form.js b/components/fiscal-sponsorship/apply/teenager-or-adult-form.js index 0552c517..8a0c3e65 100644 --- a/components/fiscal-sponsorship/apply/teenager-or-adult-form.js +++ b/components/fiscal-sponsorship/apply/teenager-or-adult-form.js @@ -26,7 +26,7 @@ export default function TeenagerOrAdultForm({ requiredFields }) { // NOTE: This depends on Field's useEffect hook to run first. const eventTeenagerLedElm = document.getElementById('eventTeenagerLed') setTeenagerLed(eventTeenagerLedElm.value) - }, []) + }) return ( <> diff --git a/pages/fiscal-sponsorship/apply/index.js b/pages/fiscal-sponsorship/apply/index.js index d19b4daa..30f69d1b 100644 --- a/pages/fiscal-sponsorship/apply/index.js +++ b/pages/fiscal-sponsorship/apply/index.js @@ -8,6 +8,7 @@ import HCBInfo from '../../../components/fiscal-sponsorship/apply/hcb-info' import Watermark from '../../../components/fiscal-sponsorship/apply/watermark' import ContactBanner from '../../../components/fiscal-sponsorship/contact' import ApplicationForm from '../../../components/fiscal-sponsorship/apply/application-form' +import { MultiStepProvider } from '../../../components/fiscal-sponsorship/apply/multi-step-context' export default function Apply() { return ( @@ -15,77 +16,79 @@ export default function Apply() { - - + - {/* vertically align h1 to top of form */} - - - - - Back - - - - Apply to join -
    - - HCB logo + {/* vertically align h1 to top of form */} + + + {' '} - HCB - -
    -
    - - -
    - -
    + > + + Back + + + + Apply to join +
    + + HCB logo{' '} + HCB + +
    +
    + + + + + + ) From afa8fad66e68a7853907c789897b9bba1783cdd1 Mon Sep 17 00:00:00 2001 From: Gary Tou Date: Fri, 7 Mar 2025 16:09:33 -0800 Subject: [PATCH 14/24] From question and copy edits with Mel --- .../apply/application-form.js | 46 +++-- .../fiscal-sponsorship/apply/callout.js | 30 --- components/fiscal-sponsorship/apply/field.js | 13 +- .../apply/form-container.js | 10 +- .../fiscal-sponsorship/apply/hcb-info.js | 37 ++-- .../apply/multi-step-form.js | 20 +- .../apply/org-adult-form.js | 17 -- .../fiscal-sponsorship/apply/org-form.js | 175 +++++++++++++----- .../fiscal-sponsorship/apply/personal-form.js | 107 +++++++---- components/fiscal-sponsorship/apply/submit.js | 2 +- .../apply/teenager-led-context.js | 2 +- .../apply/teenager-or-adult-form.js | 93 +++++++--- .../fiscal-sponsorship/organizationI18n.js | 4 +- pages/fiscal-sponsorship/apply/index.js | 16 +- pages/fiscal-sponsorship/index.js | 2 +- 15 files changed, 357 insertions(+), 217 deletions(-) delete mode 100644 components/fiscal-sponsorship/apply/callout.js diff --git a/components/fiscal-sponsorship/apply/application-form.js b/components/fiscal-sponsorship/apply/application-form.js index 56b93634..8f269557 100644 --- a/components/fiscal-sponsorship/apply/application-form.js +++ b/components/fiscal-sponsorship/apply/application-form.js @@ -1,11 +1,10 @@ import { useRouter } from 'next/router' import { useRef, useState } from 'react' -import { Alert, Heading, Button } from 'theme-ui' +import { Alert, Button, Text } from 'theme-ui' import FormContainer from './form-container' import OrganizationInfoForm from './org-form' import PersonalInfoForm from './personal-form' import { onSubmit } from './submit' -import Callout from './callout' import TeenagerOrAdultForm from './teenager-or-adult-form' import MultiStepForm from './multi-step-form' @@ -18,18 +17,27 @@ export default function ApplicationForm() { const requiredFields = { // Key: form field name // Value: humanize field name used in error message - eventName: 'organization name', - eventLocation: 'organization country', - eventPostalCode: 'organization zip/postal code', - eventDescription: 'organization description', eventTeenagerLed: 'are you a teenager?', - eventPoliticalActivity: "organization's political activity", - eventAnnualBudget: 'organization annual budget', + eventName: 'project name', + eventPostalCode: 'project zip/postal code', + eventDescription: 'project description', + eventIsPolitical: "project's political activity", + eventPoliticalActivity: "project's political activity", + eventHasWebsite: 'project website', + eventWebsite: 'project website', + eventAnnualBudget: 'project annual budget', firstName: 'first name', lastName: 'last name', userEmail: 'email', userPhone: 'phone number', - userBirthday: 'birthday' + userBirthday: 'birthday', + userAddressLine1: 'address line 1', + userAddressCity: 'city', + userAddressProvince: 'state/province', + userAddressPostalCode: 'ZIP/postal code', + userAddressCountry: 'country', + + referredBy: 'how did you hear about HCB?' } const submitButton = ( @@ -66,14 +74,27 @@ export default function ApplicationForm() { }) } > - + + {formError} + + ) + } + > {/* Step 1 */} - + + Fill out this quick application to run your project on HCB. If you + are a teenager, there is a very high likelihood we will accept your + project. We just need to collect a few pieces of information first. + {/* Step 2 */} - + {/* Step 3 */} @@ -81,7 +102,6 @@ export default function ApplicationForm() { - {formError && {formError}} ) } diff --git a/components/fiscal-sponsorship/apply/callout.js b/components/fiscal-sponsorship/apply/callout.js deleted file mode 100644 index 39f691c1..00000000 --- a/components/fiscal-sponsorship/apply/callout.js +++ /dev/null @@ -1,30 +0,0 @@ -import { Box, Text } from 'theme-ui' - -export default function Callout() { - return ( - - - HCB is a fiscal sponsor primarily for teenage-led organizations - - - Although we would love to be able to support organizations across all - ages and missions, we are currently prioritizing applications from - teenagers. - - - We are accepting adult-led organizations on a case-by-case basis. - - - ) -} diff --git a/components/fiscal-sponsorship/apply/field.js b/components/fiscal-sponsorship/apply/field.js index d71a2f04..646b38a5 100644 --- a/components/fiscal-sponsorship/apply/field.js +++ b/components/fiscal-sponsorship/apply/field.js @@ -18,10 +18,17 @@ export default function Field({ useEffect(() => { const value = router.query[name] || sessionStorage.getItem('bank-signup-' + name) - if (value) { - const input = document.getElementById(name) - if (input) input.value = value + if (!value) return + + let input = document.getElementById(name) + if (input) { + input.value = value + return } + + // Maybe it's radio buttons + input = document.querySelector(`input[name='${name}']`) + if (input) input.checked = true }, [router.query, name]) return ( diff --git a/components/fiscal-sponsorship/apply/form-container.js b/components/fiscal-sponsorship/apply/form-container.js index b63268b9..d0f7636d 100644 --- a/components/fiscal-sponsorship/apply/form-container.js +++ b/components/fiscal-sponsorship/apply/form-container.js @@ -12,10 +12,14 @@ const formContainer = forwardRef(({ children, ...props }, ref) => { bg: 'snow', px: [3, 5], py: [1, 5], - minHeight: '100dvb', + pb: 5, + minHeight: [null, null, '100dvb'], '&.has-errors div[aria-required="true"] input:placeholder-shown': { borderColor: 'primary' }, + '&.has-errors div[aria-required="true"] input[type="date"]': { + borderColor: 'primary' + }, '&.has-errors div[aria-required="true"] textarea:placeholder-shown': { borderColor: 'primary' } @@ -26,10 +30,6 @@ const formContainer = forwardRef(({ children, ...props }, ref) => { variant="copy" sx={{ ml: 0, - display: 'flex', - flexDirection: 'column', - columnGap: 4, - rowGap: 3, px: 0 }} > diff --git a/components/fiscal-sponsorship/apply/hcb-info.js b/components/fiscal-sponsorship/apply/hcb-info.js index 21c312f4..9a923698 100644 --- a/components/fiscal-sponsorship/apply/hcb-info.js +++ b/components/fiscal-sponsorship/apply/hcb-info.js @@ -1,4 +1,4 @@ -import { Box, Link, Heading } from 'theme-ui' +import { Box, Link, Heading, Grid } from 'theme-ui' import Icon from '../../icon' import { useMultiStepContext } from './multi-step-context' import { useEffect } from 'react' @@ -19,7 +19,7 @@ export default function HCBInfo() { }} > - HCB is a{' '} + HCB is not a bank, we're a{' '} + {/* + Gives your project nonprofit status. + */} +
      -
    • Gives your project nonprofit status.
    • -
    • Enables tax-deductible donations.
    • -
    • - HCB is not a bank. We partner partner with{' '} - - Column N.A. - {' '} - to offer restricted funds to fiscally-sponsored projects. -
    • -
    - - HCB provides a financial platform. - -
      -
    • Accessed via a beautiful, modern interface.
    • -
    • Provides a donation page and invoicing system.
    • -
    • Transfer money electronically.
    • -
    • Order cards for you and your team to make purchases.
    • +
    • HCB gives your project nonprofit status.
    • +
    • Allows you to raise tax-deductible donations.
    • +
    • Provides a financial platform.
    • +
    • Allows you to order cards to make purchases.
    +

    + HCB partners with{' '} + + Column N.A. + {' '} + to provide restricted funds to fiscally-sponsored projects. +

    ) } diff --git a/components/fiscal-sponsorship/apply/multi-step-form.js b/components/fiscal-sponsorship/apply/multi-step-form.js index 5b084a82..a7e29a2f 100644 --- a/components/fiscal-sponsorship/apply/multi-step-form.js +++ b/components/fiscal-sponsorship/apply/multi-step-form.js @@ -2,7 +2,11 @@ import { Box, Button, Heading } from 'theme-ui' import { useMultiStepContext } from './multi-step-context' import { Children } from 'react' -export default function MultiStepForm({ children, submitButton }) { +export default function MultiStepForm({ + children, + submitButton, + validationErrors +}) { const { step, useStepper } = useMultiStepContext() const steps = Children.toArray(children) const { nextStep, previousStep } = useStepper(steps) @@ -14,17 +18,27 @@ export default function MultiStepForm({ children, submitButton }) { with the form. So, we simple hide all non-current steps. */} {steps.map((stepComponent, index) => ( - + {stepComponent} ))} + {validationErrors} + {step > 0 && ( diff --git a/components/fiscal-sponsorship/apply/org-adult-form.js b/components/fiscal-sponsorship/apply/org-adult-form.js index 737382d1..90c15023 100644 --- a/components/fiscal-sponsorship/apply/org-adult-form.js +++ b/components/fiscal-sponsorship/apply/org-adult-form.js @@ -11,23 +11,6 @@ export default function OrganizationAdultForm({ requiredFields }) { <> {teenagerLed !== 'true' && ( <> - -