Merge pull request #1401 from hackclub/garyhtou/hcb-apply-teenager-questions

[HCB] Add additional application questions for adult orgs
This commit is contained in:
Gary Tou 2024-10-26 04:37:04 +00:00 committed by GitHub
commit 251a9b2bfb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 188 additions and 67 deletions

View file

@ -13,6 +13,9 @@ const formContainer = forwardRef(({ children, ...props }, ref) => {
minHeight: '100dvb',
'&.has-errors div[aria-required="true"] input:placeholder-shown': {
borderColor: 'primary'
},
'&.has-errors div[aria-required="true"] textarea:placeholder-shown': {
borderColor: 'primary'
}
}}
{...props}

View file

@ -0,0 +1,92 @@
import { useEffect, useState } from 'react'
import { Input, Select, Textarea } from 'theme-ui'
import Field from './field'
import useOrganizationI18n from '../organizationI18n'
export default function OrganizationAdultForm({ requiredFields }) {
const org = useOrganizationI18n()
const [teenagerLed, setTeenagerLed] = useState('false')
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)
}, [])
return (
<>
<Field
name="eventTeenagerLed"
label={`Is your ${org.toLowerCase()} led by teenagers?`}
col={true}
description={`This means your ${org.toLowerCase()} was founded and is being led exclusively by teenagers.`}
requiredFields={requiredFields}
>
<Select
name="eventTeenagerLed"
id="eventTeenagerLed"
onChange={onTeenagerLedChange}
value={teenagerLed}
>
{Object.entries({ Yes: 'true', No: 'false' }).map(([name, value]) => (
<option key={name} value={value}>
{name}
</option>
))}
</Select>
</Field>
{teenagerLed !== 'true' && (
<>
<Field
name="eventPoliticalActivity"
label={`Please describe any political activity your ${org.toLowerCase()} is involved in`}
description="This includes but is not limited to protests, public demonstrations, political education, and lobbying."
requiredFields={requiredFields}
>
<Textarea
name="eventPoliticalActivity"
id="eventPoliticalActivity"
placeholder="We are involved in..."
rows={3}
sx={{
resize: 'vertical'
}}
/>
</Field>
<Field
name="eventAnnualBudget"
label="What is your estimated annual budget (USD) for this year?"
requiredFields={requiredFields}
>
<Input
name="eventAnnualBudget"
id="eventAnnualBudget"
type="number"
min={0}
step={1}
placeholder="10,000"
/>
</Field>
</>
)}
</>
)
}

View file

@ -4,13 +4,11 @@ import Checkbox from './checkbox'
import Field from './field'
// This is using country-list instead of country-list-js as it has a smaller bundle size
import { getNames } from 'country-list'
import useOrganizationI18n from '../organizationI18n'
import OrganizationAdultForm from './org-adult-form'
export default function OrganizationInfoForm({ requiredFields }) {
const [org, setOrg] = useState('Organization')
useEffect(() => {
if (navigator.language === 'en-GB') setOrg('Organisation')
}, [])
const org = useOrganizationI18n()
return (
<>
@ -95,6 +93,8 @@ export default function OrganizationInfoForm({ requiredFields }) {
}}
/>
</Field>
<OrganizationAdultForm requiredFields={requiredFields} />
</>
)
}

View file

@ -58,7 +58,12 @@ export function onSubmit({
if (!formError) {
setIsSubmitting(true)
sendApplication().then(() => {
router.push('/fiscal-sponsorship/apply/success')
const isAdult = formData.get('eventTeenagerLed') !== 'true'
const acceptanceEta = isAdult
? 'within two weeks'
: 'within two business days'
router.push(`/fiscal-sponsorship/apply/success?eta=${acceptanceEta}`)
})
}
return

View file

@ -0,0 +1,11 @@
import { useState, useEffect } from 'react'
export default function useOrganizationI18n() {
const [org, setOrg] = useState('Organization')
useEffect(() => {
if (navigator.language === 'en-GB') setOrg('Organisation')
}, [])
return org
}

View file

@ -8,65 +8,69 @@ const applicationsTable = new AirtablePlus({
})
export default async function handler(req, res) {
if (req.method === 'POST') {
const data = req.body
await fetch('https://hcb.hackclub.com/api/v1/events/create_demo', {
body: JSON.stringify({
email: data.userEmail,
name: data.eventName,
country: getCode(data.eventLocation) || '',
postal_code: data.eventPostalCode || '',
transparent: data.transparent,
}),
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.HCB_API_TOKEN || ''}`
}
})
.then(r => r.json())
.then(async r => {
await applicationsTable.create({
'First Name': data.firstName,
'Last Name': data.lastName,
'Email Address': data.userEmail,
'Phone Number': data.userPhone,
'Date of Birth': data.userBirthday,
'Event Name': data.eventName,
'Event Website': data.eventWebsite,
'Zip Code': data.eventPostalCode,
'Tell us about your event': data.eventDescription,
'Mailing Address': data.userAddress,
'Address Line 1': data.addressLine1,
City: data.addressCity,
State: data.addressState,
'Address Country': data.addressCountry,
'Address Country Code': data.addressCountryCode,
'Event Location': data.eventLocation,
'Event Country Code': data.eventCountryCode,
'Have you used HCB for any previous events?':
data.returningUser === 'true'
? 'Yes, I have used HCB before'
: 'No, first time!',
'How did you hear about HCB?': data.referredBy,
Transparent:
data.transparent === 'true' ? 'Yes, please!' : 'No, thanks.',
'HCB account URL': `https://hcb.hackclub.com/${r.slug}`,
'Contact Option': data.contactOption,
'Slack Username': data.slackUsername,
Accommodations: data.accommodations,
'HCB ID': r.id
})
res.writeHead(302, { Location: '/hcb/apply/success' }).end()
})
.catch(error => {
console.error(error)
res.writeHead(500, {
Location: `/hcb/apply?step=3&airtable-error=${error}`
})
})
} else {
if (req.method !== 'POST') {
res.status(405).json({ status: 'error', error: 'Must send POST request' })
return
}
const data = req.body
await fetch('https://hcb.hackclub.com/api/v1/events/create_demo', {
body: JSON.stringify({
email: data.userEmail,
name: data.eventName,
country: getCode(data.eventLocation) || '',
postal_code: data.eventPostalCode || '',
transparent: data.transparent
}),
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.HCB_API_TOKEN || ''}`
}
})
.then(r => r.json())
.then(async r => {
await applicationsTable.create({
'First Name': data.firstName,
'Last Name': data.lastName,
'Email Address': data.userEmail,
'Phone Number': data.userPhone,
'Date of Birth': data.userBirthday,
'Event Name': data.eventName,
'Event Website': data.eventWebsite,
'Zip Code': data.eventPostalCode,
'Tell us about your event': data.eventDescription,
'Mailing Address': data.userAddress,
'Address Line 1': data.addressLine1,
City: data.addressCity,
State: data.addressState,
'Address Country': data.addressCountry,
'Address Country Code': data.addressCountryCode,
'Event Location': data.eventLocation,
'Event Country Code': data.eventCountryCode,
'Have you used HCB for any previous events?':
data.returningUser === 'true'
? 'Yes, I have used HCB before'
: 'No, first time!',
'How did you hear about HCB?': data.referredBy,
Transparent:
data.transparent === 'true' ? 'Yes, please!' : 'No, thanks.',
'HCB account URL': `https://hcb.hackclub.com/${r.slug}`,
'Contact Option': data.contactOption,
'Slack Username': data.slackUsername,
Accommodations: data.accommodations,
'Teenager Led?': data.eventTeenagerLed === 'true',
'(Adults) Political Activity': data.eventPoliticalActivity,
'(Adults) Annual Budget': parseInt(data.eventAnnualBudget),
'HCB ID': r.id
})
res.status(200).end();
})
.catch(error => {
console.error(error)
res.writeHead(500, {
Location: `/hcb/apply?step=3&airtable-error=${error}`
})
})
}

View file

@ -25,6 +25,9 @@ export default function Apply() {
'eventLocation',
'eventPostalCode',
'eventDescription',
'eventTeenagerLed',
'eventPoliticalActivity',
'eventAnnualBudget',
'firstName',
'lastName',
'userEmail',

View file

@ -2,6 +2,7 @@ import { useEffect } from 'react'
import { Container, Text, Link, Image } from 'theme-ui'
import JSConfetti from 'js-confetti'
import { Balancer } from 'react-wrap-balancer'
import { useRouter } from 'next/router'
function fireConfetti() {
const jsConfetti = new JSConfetti()
@ -24,6 +25,9 @@ export default function ApplicationSuccess() {
fireConfetti()
}, [])
const router = useRouter()
const eta = router.query["eta"] || "soon" // default value
return (
<Container
variant="narrow"
@ -51,8 +55,7 @@ export default function ApplicationSuccess() {
</Text>
<Text as="p" variant="lead">
<Balancer>
Well review your application and get back to you within two
business days.
Well review your application and get back to you {eta}.
</Balancer>
</Text>
</header>