diff --git a/components/background-image.js b/components/background-image.js index 14128d4e..5687ce52 100644 --- a/components/background-image.js +++ b/components/background-image.js @@ -36,11 +36,7 @@ const BGImg = ({ '~ *': { position: 'relative' } }} > - {alt} + {alt} ) diff --git a/components/bank/apply-button.js b/components/bank/apply-button.js index de8fde2d..8da4fdc4 100644 --- a/components/bank/apply-button.js +++ b/components/bank/apply-button.js @@ -2,28 +2,29 @@ import { Button, Text, Image, Flex } from 'theme-ui' import Icon from '../icon' export default function ApplyButton() { - return ( - - ) -} \ No newline at end of file + return ( + + ) +} diff --git a/components/bank/apply/address-input.js b/components/bank/apply/address-input.js index 19eae241..5834e7c0 100644 --- a/components/bank/apply/address-input.js +++ b/components/bank/apply/address-input.js @@ -5,158 +5,166 @@ import AutofillColourFix from './autofill-colour-fix' import { geocode } from '../../../lib/bank/apply/address-validation' import Icon from '../../icon' -const approvedCountries = ['US', 'CA', 'MX']; +const approvedCountries = ['US', 'CA', 'MX'] export default function AutoComplete({ name, isPersonalAddressInput }) { - const input = useRef() - const base = useRef() - const [predictions, setPredictions] = useState(null) - const [countryCode, setCountryCode] = useState(null) + const input = useRef() + const base = useRef() + const [predictions, setPredictions] = useState(null) + const [countryCode, setCountryCode] = useState(null) - const performGeocode = async (address) => { - if (isPersonalAddressInput) return - geocode(address) - .then((res) => { - const country = res?.results[0]?.country - const countryCode = res?.results[0]?.countryCode + const performGeocode = async address => { + if (isPersonalAddressInput) return + geocode(address) + .then(res => { + const country = res?.results[0]?.country + const countryCode = res?.results[0]?.countryCode - setCountryCode(countryCode) + setCountryCode(countryCode) - sessionStorage.setItem('bank-signup-eventCountry', country) - sessionStorage.setItem('bank-signup-eventCountryCode', countryCode) - }) - .catch((err) => console.error(err)) + sessionStorage.setItem('bank-signup-eventCountry', country) + sessionStorage.setItem('bank-signup-eventCountryCode', countryCode) + }) + .catch(err => console.error(err)) + } + + const optionClicked = async prediction => { + input.current.value = prediction.description + performGeocode(prediction.description) + setPredictions(null) + } + const clickOutside = e => { + if (input.current && !input.current.contains(e.target)) { + setPredictions(null) } + } - const optionClicked = async (prediction) => { - input.current.value = prediction.description - performGeocode(prediction.description) + //TODO: Close suggestions view when focus is lost via tabbing. + //TODO: Navigate suggestions with arrow keys. + + useEffect(() => { + const inputEl = input.current + + if (!window.google || !inputEl) return + + const service = new window.google.maps.places.AutocompleteService() + + const onInput = async e => { + if (!e.target.value) { setPredictions(null) - } - const clickOutside = (e) => { - if (input.current && !input.current.contains(e.target)) { - setPredictions(null) - } + } else { + service.getPlacePredictions( + { input: e.target.value }, + (predictions, status) => { + setPredictions(predictions) + if (status !== window.google.maps.places.PlacesServiceStatus.OK) { + //DEBUG + setPredictions([]) + } + } + ) + } } - //TODO: Close suggestions view when focus is lost via tabbing. - //TODO: Navigate suggestions with arrow keys. + document.addEventListener('click', clickOutside) + inputEl.addEventListener('input', onInput) + inputEl.addEventListener('focus', onInput) - useEffect(() => { - const inputEl = input.current - - if (!window.google || !inputEl) return + return () => { + document.removeEventListener('click', clickOutside) + inputEl.removeEventListener('input', onInput) + inputEl.removeEventListener('focus', onInput) + } + }, []) - const service = new window.google.maps.places.AutocompleteService() - - const onInput = async (e) => { - if (!e.target.value) { - setPredictions(null) - } else { - service.getPlacePredictions( - { input: e.target.value }, - (predictions, status) => { - setPredictions(predictions) - if (status !== window.google.maps.places.PlacesServiceStatus.OK) { //DEBUG - setPredictions([]) - } - } - ) - } - } - - document.addEventListener('click', clickOutside) - inputEl.addEventListener('input', onInput) - inputEl.addEventListener('focus', onInput) - - return () => { - document.removeEventListener('click', clickOutside) - inputEl.removeEventListener('input', onInput) - inputEl.removeEventListener('focus', onInput) - } - }, []) - - return ( - - - performGeocode(e.target.value)} - /> - - {/* {String(countryCode)} */} - {countryCode && !approvedCountries.includes(countryCode) && - - - - Currently, we only have first-class support for organizations in the United States, Canada, and Mexico.
- If you're somewhere else, you can still use bank!
- Please contact us at bank@hackclub.com -
-
- } -
-
- { predictions && - - - { predictions.map((prediction, idx) => ( - <> - optionClicked(prediction)} - sx={{ - cursor: 'pointer', - border: 'none', - background: 'none', - color: '#d1cbe7', - '&:hover': { - color: 'white', - }, - fontFamily: 'inherit', - fontSize: 'inherit', - textAlign: 'inherit', - }} - key={prediction.id} - > - {prediction.description} - - - { - idx < predictions.length - 1 && -
- } - - ))} -
-
- } + return ( + + + performGeocode(e.target.value)} + /> + + {/* {String(countryCode)} */} + {countryCode && !approvedCountries.includes(countryCode) && ( + + + + Currently, we only have first-class support for organizations in + the United States, Canada, and Mexico. +
+ If you're somewhere else, you can still use bank! +
+ Please contact us at bank@hackclub.com +
+
+ )}
- ) -} \ No newline at end of file +
+ {predictions && ( + + + {predictions.map((prediction, idx) => ( + <> + optionClicked(prediction)} + sx={{ + cursor: 'pointer', + border: 'none', + background: 'none', + color: '#d1cbe7', + '&:hover': { + color: 'white' + }, + fontFamily: 'inherit', + fontSize: 'inherit', + textAlign: 'inherit' + }} + key={prediction.id} + > + {prediction.description} + + + {idx < predictions.length - 1 && ( +
+ )} + + ))} +
+
+ )} +
+ ) +} diff --git a/components/bank/apply/alert-modal.js b/components/bank/apply/alert-modal.js index d4ad225f..09b06f79 100644 --- a/components/bank/apply/alert-modal.js +++ b/components/bank/apply/alert-modal.js @@ -2,36 +2,41 @@ import { Box, Button, Flex, Text } from 'theme-ui' import Icon from '../../icon' export default function AlertModal({ formError, setFormError }) { - if (!formError) return null + if (!formError) return null - const close = () => setFormError(null) + const close = () => setFormError(null) - return ( - - - - Oops! - {formError} - - - - ) -} \ No newline at end of file + return ( + + + + Oops! + {formError} + + + + ) +} diff --git a/components/bank/apply/autofill-colour-fix.js b/components/bank/apply/autofill-colour-fix.js index b18d4251..b9aa59d1 100644 --- a/components/bank/apply/autofill-colour-fix.js +++ b/components/bank/apply/autofill-colour-fix.js @@ -1,10 +1,10 @@ //TODO: Move to main theme const autofillColourFix = { - '&:-webkit-autofill': { - boxShadow: '0 0 0 100px #252429 inset !important', - WebkitTextFillColor: 'white', - }, + '&:-webkit-autofill': { + boxShadow: '0 0 0 100px #252429 inset !important', + WebkitTextFillColor: 'white' + } } -export default autofillColourFix \ No newline at end of file +export default autofillColourFix diff --git a/components/bank/apply/bank-info.js b/components/bank/apply/bank-info.js index d07d108d..c142fffc 100644 --- a/components/bank/apply/bank-info.js +++ b/components/bank/apply/bank-info.js @@ -12,15 +12,15 @@ export default function BankInfo() { - + @@ -28,31 +28,20 @@ export default function BankInfo() { - +
    -
  • - Nonprofit status. -
  • -
  • - Tax-deductable donations. -
  • +
  • Nonprofit status.
  • +
  • Tax-deductable donations.
A financial platform - +
    -
  • - A donations page and invoicing system. -
  • -
  • - Transfer money electronically. -
  • -
  • - Order cards for you and your team to - make purchases. -
  • +
  • A donations page and invoicing system.
  • +
  • Transfer money electronically.
  • +
  • Order cards for you and your team to make purchases.
@@ -64,22 +53,23 @@ export default function BankInfo() { - A bank! (we're better) - + + A bank!{' '} + (we're better) + +
  • - Rather than setting up a standard bank account, - you'll get a restricted fund within Hack Club accounts. -
  • -
  • - You can't deposit or withdraw money. + Rather than setting up a standard bank account, you'll get a + restricted fund within Hack Club accounts.
  • +
  • You can't deposit or withdraw money.
For-profit - +
  • If you’re a for-profit entity, then Bank is not for you. @@ -92,5 +82,5 @@ export default function BankInfo() { - ); + ) } diff --git a/components/bank/apply/checkbox.js b/components/bank/apply/checkbox.js index 0b4fac64..0442d343 100644 --- a/components/bank/apply/checkbox.js +++ b/components/bank/apply/checkbox.js @@ -1,39 +1,35 @@ import { useEffect, useState } from 'react' import Icon from '../../icon' -export default function Checkbox({ name, defaultChecked=false, size=38 }) { - const [checked, setChecked] = useState(defaultChecked) - const toggle = () => setChecked(!checked) +export default function Checkbox({ name, defaultChecked = false, size = 38 }) { + const [checked, setChecked] = useState(defaultChecked) + const toggle = () => setChecked(!checked) - /* Fill in the field with the value from sessionStorage. + /* Fill in the field with the value from sessionStorage. For other input elements, the value is set in the Field component, but these checkboxes hold their state in useState rather than in the DOM. */ - useEffect(() => { - const value = sessionStorage.getItem('bank-signup-' + name) - if (value) { - const input = document.getElementById(name) - input && setChecked(value === 'true') - } - }, [name]) + useEffect(() => { + const value = sessionStorage.getItem('bank-signup-' + name) + if (value) { + const input = document.getElementById(name) + input && setChecked(value === 'true') + } + }, [name]) - return (<> - - toggle()} - onKeyDown={(e) => e.key === 'Enter' && toggle()} - /> + return ( + <> + + toggle()} + onKeyDown={e => e.key === 'Enter' && toggle()} + /> - ) -} \ No newline at end of file + ) +} diff --git a/components/bank/apply/field.js b/components/bank/apply/field.js index 50a41ab4..4b942e1f 100644 --- a/components/bank/apply/field.js +++ b/components/bank/apply/field.js @@ -3,50 +3,67 @@ import { useEffect } from 'react' import { Box, Flex, Label, Text } from 'theme-ui' import FlexCol from '../../flex-col' -export default function Field({ name, label, description, col = true, requiredFields, children }) { - const router = useRouter() - const isRequired = requiredFields[parseInt(router.query.step) - 1].includes(name) +export default function Field({ + name, + label, + description, + col = true, + requiredFields, + children +}) { + const router = useRouter() + const isRequired = + requiredFields[parseInt(router.query.step) - 1].includes(name) - /* Fill in the field input element with the value from sessionStorage. + /* Fill in the field input element with the value from sessionStorage. Note: the custom checkbox component does this in its own useEffect hook. */ - useEffect(() => { - const value = sessionStorage.getItem('bank-signup-' + name) - if (value) { - const input = document.getElementById(name) - if (input) input.value = value - } - }, [name]) - - return ( - - - - - { isRequired && - Required - } - - { children } - - { description && - { description } - } - - ) + useEffect(() => { + const value = sessionStorage.getItem('bank-signup-' + name) + if (value) { + const input = document.getElementById(name) + if (input) input.value = value + } + }, [name]) + + return ( + + + + + {isRequired && ( + + Required + + )} + + {children} + + {description && ( + {description} + )} + + ) } diff --git a/components/bank/apply/form-container.js b/components/bank/apply/form-container.js index 78f67471..2eadc9e5 100644 --- a/components/bank/apply/form-container.js +++ b/components/bank/apply/form-container.js @@ -1,29 +1,29 @@ import { forwardRef } from 'react' import { Box } from 'theme-ui' -const formContainer = forwardRef(({ children }, ref) => { - return ( - - { children } - - ) +const formContainer = forwardRef(({ children }, ref) => { + return ( + + {children} + + ) }) -https://stackoverflow.com/a/67993106/10652680 -formContainer.displayName = 'formContainer' -export default formContainer \ No newline at end of file +//stackoverflow.com/a/67993106/10652680 +https: formContainer.displayName = 'formContainer' +export default formContainer diff --git a/components/bank/apply/nav-button.js b/components/bank/apply/nav-button.js index d529bfd5..6df3c92b 100644 --- a/components/bank/apply/nav-button.js +++ b/components/bank/apply/nav-button.js @@ -3,156 +3,171 @@ import { useEffect, useState } from 'react' import { Button, Flex, Text, Spinner } from 'theme-ui' async function sendApplication() { - // Get the form data from sessionStorage - const data = {} - for (let i = 0; i < sessionStorage.length; i++) { - const key = sessionStorage.key(i) - if (key.startsWith('bank-signup-')) { - data[key.replace('bank-signup-', '')] = sessionStorage.getItem(key) - } + // Get the form data from sessionStorage + const data = {} + for (let i = 0; i < sessionStorage.length; i++) { + const key = sessionStorage.key(i) + if (key.startsWith('bank-signup-')) { + data[key.replace('bank-signup-', '')] = sessionStorage.getItem(key) } - console.dir('Sending data:', data) + } + console.dir('Sending data:', data) - // Send the data - try { - const res = await fetch('/api/bank/apply', { - method: 'POST', - cors: 'no-cors', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(data), - }) - } catch (error) { - console.error(error); - } -} - -function NavIcon({ isBack }) { - const style = { - height: '1em', - fill: 'white', - margin: 0, - flexShrink: 0, - } - - return isBack ? - - - - - - : - - - - - + // Send the data + try { + const res = await fetch('/api/bank/apply', { + method: 'POST', + cors: 'no-cors', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data) + }) + } catch (error) { + console.error(error) + } } -export default function NavButton({ isBack, form, clickHandler, requiredFields, setFormError }) { - const router = useRouter() - const [spinner, setSpinner] = useState(false) +function NavIcon({ isBack }) { + const style = { + height: '1em', + fill: 'white', + margin: 0, + flexShrink: 0 + } - useEffect(() => { - setSpinner(false) - }, [router.query.step]) + return isBack ? ( + + + + + + ) : ( + + + + + + ) +} - const minStep = 1 - const maxStep = 3 +export default function NavButton({ + isBack, + form, + clickHandler, + requiredFields, + setFormError +}) { + const router = useRouter() + const [spinner, setSpinner] = useState(false) - const click = async () => { - setSpinner(true) + useEffect(() => { + setSpinner(false) + }, [router.query.step]) - let step = parseInt(router.query.step) + const minStep = 1 + const maxStep = 3 - async function setStep(s) { - await router.push({ - pathname: router.pathname, - query: { ...router.query, step: s } }, - undefined, - {} - ) - } + const click = async () => { + setSpinner(true) - if (!step) { - // Set the step query param to minStep if it's not there. - await setStep(minStep) - } else if (step === minStep && isBack) { - await router.push('/bank') - return - } else if (step < minStep) { - // Set the step query param to minStep if it's lower than that. - await setStep(minStep) - } + let step = parseInt(router.query.step) - /* Don't return from inside the loop since - we want all input values to be saved every time */ - let wasError = false; - - // Save form data - new FormData(form.current).forEach((value, key) => { - sessionStorage.setItem('bank-signup-' + key, value) - - // Check if there are empty required fields. - if ( - !isBack && - (!value || value.trim() === "") - && requiredFields[step - 1].includes(key) - ) { - setFormError("Please fill all required fields") - wasError = true - setSpinner(false) - } - }) - if (wasError) return - - // Run the parent's click handler for this button. - if (clickHandler) await clickHandler() - - if (step >= maxStep && !isBack) { - await sendApplication() - await router.push('/bank/apply/success') - return - } else { - step += isBack ? -1 : 1 - } - await setStep(step) + async function setStep(s) { + await router.push( + { + pathname: router.pathname, + query: { ...router.query, step: s } + }, + undefined, + {} + ) } - return ( - - ) -} \ No newline at end of file + {isBack ? 'Back' : 'Next'} + + + {!isBack && spinner && ( + + )} + + ) +} diff --git a/components/bank/apply/org-form.js b/components/bank/apply/org-form.js index a174b8d8..3bb3c484 100644 --- a/components/bank/apply/org-form.js +++ b/components/bank/apply/org-form.js @@ -6,70 +6,78 @@ import Field from './field' import AutofillColourFix from './autofill-colour-fix' export default function OrganizationInfoForm({ requiredFields }) { - const [org, setOrg] = useState('organization') + const [org, setOrg] = useState('organization') - useEffect(() => { - if (navigator.language === 'en-GB') setOrg('organisation') - }, []) + useEffect(() => { + if (navigator.language === 'en-GB') setOrg('organisation') + }, []) - return ( - <> - - - - - - - - - - + + + + + + + + + + - - - -