🎨 Run prettier

This commit is contained in:
Malted 2023-04-07 13:02:19 +01:00
parent 928a4092f2
commit fb7e4df565
No known key found for this signature in database
GPG key ID: C1AF1C0E5180BE02
41 changed files with 1551 additions and 1408 deletions

View file

@ -36,11 +36,7 @@ const BGImg = ({
'~ *': { position: 'relative' }
}}
>
<Image
layout="responsive"
alt={alt}
{...props}
/>
<Image layout="responsive" alt={alt} {...props} />
</Box>
)

View file

@ -2,28 +2,29 @@ import { Button, Text, Image, Flex } from 'theme-ui'
import Icon from '../icon'
export default function ApplyButton() {
return (
<Button
variant='ctaLg'
as='a'
href='apply'
sx={{
width: '100%',
height: '4.2rem',
// borderRadius: '1.5rem',
}}
>
<Flex sx={{
alignItems: 'center',
gap: 3,
mr: '-32px' // Man...
}}>
<Text
color='white'
sx={{ fontWeight: 'bold', fontSize: 4 }}
>Apply now</Text>
<Icon glyph='view-forward' size={46} color='white' />
</Flex>
</Button>
)
}
return (
<Button
variant="ctaLg"
as="a"
href="apply"
sx={{
width: '100%',
height: '4.2rem'
// borderRadius: '1.5rem',
}}
>
<Flex
sx={{
alignItems: 'center',
gap: 3,
mr: '-32px' // Man...
}}
>
<Text color="white" sx={{ fontWeight: 'bold', fontSize: 4 }}>
Apply now
</Text>
<Icon glyph="view-forward" size={46} color="white" />
</Flex>
</Button>
)
}

View file

@ -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 (
<Box sx={{ position: 'relative', width: '100%' }}>
<FlexCol flexDirection='column' position='relative' width='100%' gap='2'>
<Input
ref={input}
name={name}
id={name}
placeholder='Shelburne, VT'
autoComplete="off"
sx={{ ...AutofillColourFix }}
onInput={async (e) => performGeocode(e.target.value)}
/>
<Box>
{/* {String(countryCode)} */}
{countryCode && !approvedCountries.includes(countryCode) &&
<Flex sx={{ alignItems: 'center' }}>
<Icon glyph='sad' size='2.5rem' sx={{color: 'red', mr: 1, flexShrink: 0 }} />
<Text
as='label'
htmlFor={name}
sx={{
color: 'red',
// fontWeight: 'medium',
}}
>
Currently, we only have first-class support for organizations in the United States, Canada, and Mexico.<br />
If you're somewhere else, you can still use bank!<br />
Please contact us at bank@hackclub.com
</Text>
</Flex>
}
</Box>
</FlexCol>
{ predictions &&
<Box sx={{
background: '#47454f',
border: '1px solid #696675',
width: '100%',
p: 3,
borderRadius: '4px',
position: 'absolute',
bottom: 'calc(100% + 0.5em)',
}}>
<FlexCol gap={1}>
{ predictions.map((prediction, idx) => (
<>
<Text
as='button'
onClick={() => 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}
</Text>
{
idx < predictions.length - 1 &&
<hr
style={{
width: '100%',
color: '#8492a6',
}}
/>
}
</>
))}
</FlexCol>
</Box>
}
return (
<Box sx={{ position: 'relative', width: '100%' }}>
<FlexCol flexDirection="column" position="relative" width="100%" gap="2">
<Input
ref={input}
name={name}
id={name}
placeholder="Shelburne, VT"
autoComplete="off"
sx={{ ...AutofillColourFix }}
onInput={async e => performGeocode(e.target.value)}
/>
<Box>
{/* {String(countryCode)} */}
{countryCode && !approvedCountries.includes(countryCode) && (
<Flex sx={{ alignItems: 'center' }}>
<Icon
glyph="sad"
size="2.5rem"
sx={{ color: 'red', mr: 1, flexShrink: 0 }}
/>
<Text
as="label"
htmlFor={name}
sx={{
color: 'red'
// fontWeight: 'medium',
}}
>
Currently, we only have first-class support for organizations in
the United States, Canada, and Mexico.
<br />
If you're somewhere else, you can still use bank!
<br />
Please contact us at bank@hackclub.com
</Text>
</Flex>
)}
</Box>
)
}
</FlexCol>
{predictions && (
<Box
sx={{
background: '#47454f',
border: '1px solid #696675',
width: '100%',
p: 3,
borderRadius: '4px',
position: 'absolute',
bottom: 'calc(100% + 0.5em)'
}}
>
<FlexCol gap={1}>
{predictions.map((prediction, idx) => (
<>
<Text
as="button"
onClick={() => 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}
</Text>
{idx < predictions.length - 1 && (
<hr
style={{
width: '100%',
color: '#8492a6'
}}
/>
)}
</>
))}
</FlexCol>
</Box>
)}
</Box>
)
}

View file

@ -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 (
<Box>
<Box onClick={close} sx={{
position: 'fixed',
inset: 0,
background: '#000000',
opacity: 0.5,
zIndex: 1000,
}} />
<Flex sx={{
flexDirection: 'column',
alignItems: 'center',
gap: 3,
background: '#252429',
position: 'fixed',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
zIndex: 1001,
padding: 4,
borderRadius: 'default'
}}>
<Text variant='title'>Oops!</Text>
<Text variant='lead'>{formError}</Text>
<Button onClick={close}>Dismiss</Button>
</Flex>
</Box>
)
}
return (
<Box>
<Box
onClick={close}
sx={{
position: 'fixed',
inset: 0,
background: '#000000',
opacity: 0.5,
zIndex: 1000
}}
/>
<Flex
sx={{
flexDirection: 'column',
alignItems: 'center',
gap: 3,
background: '#252429',
position: 'fixed',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
zIndex: 1001,
padding: 4,
borderRadius: 'default'
}}
>
<Text variant="title">Oops!</Text>
<Text variant="lead">{formError}</Text>
<Button onClick={close}>Dismiss</Button>
</Flex>
</Box>
)
}

View file

@ -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
export default autofillColourFix

View file

@ -12,15 +12,15 @@ export default function BankInfo() {
</Text>
<FlexCol gap={3} ml={3}>
<FlexCol gap={2}>
<Flex sx={{ alignItems: "center", gap: 2 }}>
<Flex sx={{ alignItems: 'center', gap: 2 }}>
<Link
color='white'
color="white"
href="/bank/fiscal-sponsorship"
target='_blank'
target="_blank"
sx={{
fontSize: 3,
display: "inline-flex",
alignItems: "flex-end",
display: 'inline-flex',
alignItems: 'flex-end',
gap: 1
}}
>
@ -28,31 +28,20 @@ export default function BankInfo() {
<Icon glyph="external" />
</Link>
</Flex>
<Text sx={{ color: "muted" }}>
<Text sx={{ color: 'muted' }}>
<ul>
<li>
Nonprofit status.
</li>
<li>
Tax-deductable donations.
</li>
<li>Nonprofit status.</li>
<li>Tax-deductable donations.</li>
</ul>
</Text>
</FlexCol>
<FlexCol gap={2}>
<Text sx={{ fontSize: 3 }}>A financial platform</Text>
<Text sx={{ color: "muted" }}>
<Text sx={{ color: 'muted' }}>
<ul>
<li>
A donations page and invoicing system.
</li>
<li>
Transfer money electronically.
</li>
<li>
Order cards for you and your team to
make purchases.
</li>
<li>A donations page and invoicing system.</li>
<li>Transfer money electronically.</li>
<li>Order cards for you and your team to make purchases.</li>
</ul>
</Text>
</FlexCol>
@ -64,22 +53,23 @@ export default function BankInfo() {
</Text>
<FlexCol gap={3} ml={3}>
<FlexCol gap={2}>
<Text sx={{ fontSize: 3 }}>A bank! <Text sx={{ color: 'muted', fontSize: 2 }}>(we're better)</Text></Text>
<Text sx={{ color: "muted" }}>
<Text sx={{ fontSize: 3 }}>
A bank!{' '}
<Text sx={{ color: 'muted', fontSize: 2 }}>(we're better)</Text>
</Text>
<Text sx={{ color: 'muted' }}>
<ul>
<li>
Rather than setting up a standard bank account,
you'll get a restricted fund within Hack Club accounts.
</li>
<li>
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.
</li>
<li>You can't deposit or withdraw money.</li>
</ul>
</Text>
</FlexCol>
<FlexCol gap={2}>
<Text sx={{ fontSize: 3 }}>For-profit</Text>
<Text sx={{ color: "muted" }}>
<Text sx={{ color: 'muted' }}>
<ul>
<li>
If youre a for-profit entity, then Bank is not for you.
@ -92,5 +82,5 @@ export default function BankInfo() {
</FlexCol>
</FlexCol>
</Box>
);
)
}

View file

@ -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 (<>
<input
aria-hidden='true'
type='hidden'
value={checked}
name={name}
/>
<Icon
glyph={checked ? 'checkmark' : 'checkbox'}
size={size}
id={name}
name={name}
aria-checked={checked}
role='checkbox'
tabindex='0'
onClick={() => toggle()}
onKeyDown={(e) => e.key === 'Enter' && toggle()}
/>
return (
<>
<input aria-hidden="true" type="hidden" value={checked} name={name} />
<Icon
glyph={checked ? 'checkmark' : 'checkbox'}
size={size}
id={name}
name={name}
aria-checked={checked}
role="checkbox"
tabindex="0"
onClick={() => toggle()}
onKeyDown={e => e.key === 'Enter' && toggle()}
/>
</>
)
}
)
}

View file

@ -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 (
<FlexCol gap={2} width={'100%'}>
<Flex sx={{
flexDirection: col ? 'column' : 'row',
alignItems: col ? 'flex-start' : 'center',
gap: 2,
}}>
<Flex sx={{ alignItems: 'center', gap: 2 }}>
<Label htmlFor={name} sx={{
textTransform: 'capitalize',
fontSize: 3,
width: 'fit-content',
}}>
{ label }
</Label>
{ isRequired && <Box sx={{
backgroundColor: 'muted',
padding: '4px 6px',
borderRadius: '999px',
lineHeight: '1',
fontSize: 14,
}}>
Required
</Box>}
</Flex>
{ children }
</Flex>
{ description && <Text sx={{ color: 'muted', fontSize: 1 }}>
{ description }
</Text> }
</FlexCol>
)
useEffect(() => {
const value = sessionStorage.getItem('bank-signup-' + name)
if (value) {
const input = document.getElementById(name)
if (input) input.value = value
}
}, [name])
return (
<FlexCol gap={2} width={'100%'}>
<Flex
sx={{
flexDirection: col ? 'column' : 'row',
alignItems: col ? 'flex-start' : 'center',
gap: 2
}}
>
<Flex sx={{ alignItems: 'center', gap: 2 }}>
<Label
htmlFor={name}
sx={{
textTransform: 'capitalize',
fontSize: 3,
width: 'fit-content'
}}
>
{label}
</Label>
{isRequired && (
<Box
sx={{
backgroundColor: 'muted',
padding: '4px 6px',
borderRadius: '999px',
lineHeight: '1',
fontSize: 14
}}
>
Required
</Box>
)}
</Flex>
{children}
</Flex>
{description && (
<Text sx={{ color: 'muted', fontSize: 1 }}>{description}</Text>
)}
</FlexCol>
)
}

View file

@ -1,29 +1,29 @@
import { forwardRef } from 'react'
import { Box } from 'theme-ui'
const formContainer = forwardRef(({ children }, ref) => {
return (
<Box
ref={ref}
as='form'
sx={{
height: '100%',
width: ['100%', null, null, '50ch'],
flex: '1',
overflowY: ['none', null, null, 'auto'],
pr: [0, null, '2ch'],
pl: [0, null, 1],
pb: [0, null, 3],
display: 'flex',
flexDirection: 'column',
gap: 4,
}}
>
{ children }
</Box>
)
const formContainer = forwardRef(({ children }, ref) => {
return (
<Box
ref={ref}
as="form"
sx={{
height: '100%',
width: ['100%', null, null, '50ch'],
flex: '1',
overflowY: ['none', null, null, 'auto'],
pr: [0, null, '2ch'],
pl: [0, null, 1],
pb: [0, null, 3],
display: 'flex',
flexDirection: 'column',
gap: 4
}}
>
{children}
</Box>
)
})
https://stackoverflow.com/a/67993106/10652680
formContainer.displayName = 'formContainer'
export default formContainer
//stackoverflow.com/a/67993106/10652680
https: formContainer.displayName = 'formContainer'
export default formContainer

View file

@ -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 ?
<svg style={style} viewBox="10.73 7.72 9.27 16.53">
<g>
<path d="M19.768,23.89c0.354,-0.424 0.296,-1.055 -0.128,-1.408c-1.645,-1.377 -5.465,-4.762 -6.774,-6.482c1.331,-1.749 5.1,-5.085 6.774,-6.482c0.424,-0.353 0.482,-0.984 0.128,-1.408c-0.353,-0.425 -0.984,-0.482 -1.409,-0.128c-1.839,1.532 -5.799,4.993 -7.2,6.964c-0.219,0.312 -0.409,0.664 -0.409,1.054c0,0.39 0.19,0.742 0.409,1.053c1.373,1.932 5.399,5.462 7.2,6.964l0.001,0.001c0.424,0.354 1.055,0.296 1.408,-0.128Z" ></path>
</g>
</svg>
:
<svg style={style} viewBox="12.75 7.72 9.25 16.53">
<g>
<path d="M12.982,23.89c-0.354,-0.424 -0.296,-1.055 0.128,-1.408c1.645,-1.377 5.465,-4.762 6.774,-6.482c-1.331,-1.749 -5.1,-5.085 -6.774,-6.482c-0.424,-0.353 -0.482,-0.984 -0.128,-1.408c0.353,-0.425 0.984,-0.482 1.409,-0.128c1.839,1.532 5.799,4.993 7.2,6.964c0.219,0.312 0.409,0.664 0.409,1.054c0,0.39 -0.19,0.742 -0.409,1.053c-1.373,1.932 -5.399,5.462 -7.2,6.964l-0.001,0.001c-0.424,0.354 -1.055,0.296 -1.408,-0.128Z"></path>
</g>
</svg>
// 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 ? (
<svg style={style} viewBox="10.73 7.72 9.27 16.53">
<g>
<path d="M19.768,23.89c0.354,-0.424 0.296,-1.055 -0.128,-1.408c-1.645,-1.377 -5.465,-4.762 -6.774,-6.482c1.331,-1.749 5.1,-5.085 6.774,-6.482c0.424,-0.353 0.482,-0.984 0.128,-1.408c-0.353,-0.425 -0.984,-0.482 -1.409,-0.128c-1.839,1.532 -5.799,4.993 -7.2,6.964c-0.219,0.312 -0.409,0.664 -0.409,1.054c0,0.39 0.19,0.742 0.409,1.053c1.373,1.932 5.399,5.462 7.2,6.964l0.001,0.001c0.424,0.354 1.055,0.296 1.408,-0.128Z"></path>
</g>
</svg>
) : (
<svg style={style} viewBox="12.75 7.72 9.25 16.53">
<g>
<path d="M12.982,23.89c-0.354,-0.424 -0.296,-1.055 0.128,-1.408c1.645,-1.377 5.465,-4.762 6.774,-6.482c-1.331,-1.749 -5.1,-5.085 -6.774,-6.482c-0.424,-0.353 -0.482,-0.984 -0.128,-1.408c0.353,-0.425 0.984,-0.482 1.409,-0.128c1.839,1.532 5.799,4.993 7.2,6.964c0.219,0.312 0.409,0.664 0.409,1.054c0,0.39 -0.19,0.742 -0.409,1.053c-1.373,1.932 -5.399,5.462 -7.2,6.964l-0.001,0.001c-0.424,0.354 -1.055,0.296 -1.408,-0.128Z"></path>
</g>
</svg>
)
}
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 (
<Button
variant={ isBack ? 'outline' : 'ctaLg' }
sx={{
color: 'white',
width: '100%',
maxWidth: isBack ? '8rem' : '10rem',
position: 'relative',
}}
onClick={click}
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)
}
/* 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)
}
return (
<Button
variant={isBack ? 'outline' : 'ctaLg'}
sx={{
color: 'white',
width: '100%',
maxWidth: isBack ? '8rem' : '10rem',
position: 'relative'
}}
onClick={click}
>
<Flex
sx={{
flexDirection: isBack ? 'row' : 'row-reverse',
justifyContent: 'center',
placeItems: 'center',
fontSize: isBack ? 2 : 4,
gap: [2, null, null, 3]
}}
>
<NavIcon isBack={isBack} />
<Text
sx={{
textTransform: 'none',
fontWeight: 'bold'
}}
>
<Flex sx={{
flexDirection: isBack ? 'row' : 'row-reverse',
justifyContent: 'center',
placeItems: 'center',
fontSize: isBack ? 2 : 4,
gap: [2, null, null, 3],
}}>
<NavIcon isBack={isBack} />
<Text
sx={{
textTransform: 'none',
fontWeight: 'bold',
}}
>
{isBack ? 'Back' : 'Next'}
</Text>
</Flex>
{!isBack && spinner && <Spinner sx={{
height: '32px',
color: 'white',
position: 'absolute',
right: '-0.3rem',
margin: '0 !important',
}} />}
</Button>
)
}
{isBack ? 'Back' : 'Next'}
</Text>
</Flex>
{!isBack && spinner && (
<Spinner
sx={{
height: '32px',
color: 'white',
position: 'absolute',
right: '-0.3rem',
margin: '0 !important'
}}
/>
)}
</Button>
)
}

View file

@ -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 (
<>
<Field name='eventName' label={`${org} name`} requiredFields={requiredFields}>
<Input
name='eventName'
id='eventName'
placeholder='Shelburne School Hackathon'
sx={{...AutofillColourFix}}
/>
</Field>
<Field
name='eventWebsite'
label={`${org} website`}
description='If you dont have one yet, you can leave this blank.'
requiredFields={requiredFields}
>
<Input
name='eventWebsite'
id='eventWebsite'
type='url'
placeholder='hackclub.com'
sx={{...AutofillColourFix}}
/>
</Field>
<Field name='eventLocation' label={`${org} location`} requiredFields={requiredFields}>
<AddressInput isPersonalAddressInput={false} name='eventLocation' />
</Field>
<Field
name='transparent'
label='Transparency mode'
col={false}
description={`
return (
<>
<Field
name="eventName"
label={`${org} name`}
requiredFields={requiredFields}
>
<Input
name="eventName"
id="eventName"
placeholder="Shelburne School Hackathon"
sx={{ ...AutofillColourFix }}
/>
</Field>
<Field
name="eventWebsite"
label={`${org} website`}
description="If you dont have one yet, you can leave this blank."
requiredFields={requiredFields}
>
<Input
name="eventWebsite"
id="eventWebsite"
type="url"
placeholder="hackclub.com"
sx={{ ...AutofillColourFix }}
/>
</Field>
<Field
name="eventLocation"
label={`${org} location`}
requiredFields={requiredFields}
>
<AddressInput isPersonalAddressInput={false} name="eventLocation" />
</Field>
<Field
name="transparent"
label="Transparency mode"
col={false}
description={`
Transparent accounts balances and donations are public.
You choose who has access to personal details.
This can be changed later.
As an example, Hack Club's finances are transparent!
`}
requiredFields={requiredFields}
>
<Checkbox defaultChecked={true} name='transparent' />
</Field>
<Field
name='eventDescription'
label={`Tell us about your ${org}!`}
description='1 or 2 sentences will suffice'
requiredFields={requiredFields}
>
<Textarea
name='eventDescription'
id='eventDescription'
rows={3}
sx={{
resize: 'vertical',
width: '100%',
...AutofillColourFix
}}
/>
</Field>
</>
)
}
requiredFields={requiredFields}
>
<Checkbox defaultChecked={true} name="transparent" />
</Field>
<Field
name="eventDescription"
label={`Tell us about your ${org}!`}
description="1 or 2 sentences will suffice"
requiredFields={requiredFields}
>
<Textarea
name="eventDescription"
id="eventDescription"
rows={3}
sx={{
resize: 'vertical',
width: '100%',
...AutofillColourFix
}}
/>
</Field>
</>
)
}

View file

@ -4,87 +4,105 @@ import AddressInput from './address-input'
import Field from './field'
import AutofillColourFix from './autofill-colour-fix'
export default function PersonalInfoForm({ setValidationResult, requiredFields }) {
return (
<>
<Flex sx={{ justifyContent: 'space-between', gap: 4 }}>
<Field name='firstName' label='First name' requiredFields={requiredFields}>
<Input
name='firstName'
id='firstName'
placeholder='Fiona'
sx={{...AutofillColourFix}}
/>
</Field>
<Field name='lastName' label='Last name' requiredFields={requiredFields}>
<Input
name='lastName'
id='lastName'
placeholder='Hacksworth'
sx={{...AutofillColourFix}}
/>
</Field>
</Flex>
<Field name='userEmail' label='Email' requiredFields={requiredFields}>
<Input
name='userEmail'
id='userEmail'
type='email'
placeholder='fiona@hackclub.com'
sx={{...AutofillColourFix}}
/>
</Field>
<Field
name='userPhone'
label='Phone'
description='Well only use this if we need to get in touch with you urgently.'
requiredFields={requiredFields}
>
<Input
name='userPhone'
id='userPhone'
type='tel'
placeholder='(123) 456-7890'
sx={{...AutofillColourFix}}
/>
</Field>
<Field name='userBirthday' label='Birthday' requiredFields={requiredFields}>
<Input
name='userBirthday'
id='userBirthday'
type='date'
sx={{...AutofillColourFix}}
/>
</Field>
<Field name='referredBy' label='Who were you referred by?' requiredFields={requiredFields}>
<Input
name='referredBy'
id='referredBy'
placeholder='Max'
sx={{...AutofillColourFix}}
/>
</Field>
<Field
name='returningUser'
label='Have you used Bank before?'
col={false}
requiredFields={requiredFields}
>
<Checkbox name='returningUser' />
</Field>
<Field
name='userAddress'
label='Address'
description='This is so we can send you some swag and goodies if you ever request them!'
requiredFields={requiredFields}
>
<AddressInput
name='userAddress'
isPersonalAddressInput={true}
setValidationResult={setValidationResult}
/>
</Field>
</>
)
}
export default function PersonalInfoForm({
setValidationResult,
requiredFields
}) {
return (
<>
<Flex sx={{ justifyContent: 'space-between', gap: 4 }}>
<Field
name="firstName"
label="First name"
requiredFields={requiredFields}
>
<Input
name="firstName"
id="firstName"
placeholder="Fiona"
sx={{ ...AutofillColourFix }}
/>
</Field>
<Field
name="lastName"
label="Last name"
requiredFields={requiredFields}
>
<Input
name="lastName"
id="lastName"
placeholder="Hacksworth"
sx={{ ...AutofillColourFix }}
/>
</Field>
</Flex>
<Field name="userEmail" label="Email" requiredFields={requiredFields}>
<Input
name="userEmail"
id="userEmail"
type="email"
placeholder="fiona@hackclub.com"
sx={{ ...AutofillColourFix }}
/>
</Field>
<Field
name="userPhone"
label="Phone"
description="Well only use this if we need to get in touch with you urgently."
requiredFields={requiredFields}
>
<Input
name="userPhone"
id="userPhone"
type="tel"
placeholder="(123) 456-7890"
sx={{ ...AutofillColourFix }}
/>
</Field>
<Field
name="userBirthday"
label="Birthday"
requiredFields={requiredFields}
>
<Input
name="userBirthday"
id="userBirthday"
type="date"
sx={{ ...AutofillColourFix }}
/>
</Field>
<Field
name="referredBy"
label="Who were you referred by?"
requiredFields={requiredFields}
>
<Input
name="referredBy"
id="referredBy"
placeholder="Max"
sx={{ ...AutofillColourFix }}
/>
</Field>
<Field
name="returningUser"
label="Have you used Bank before?"
col={false}
requiredFields={requiredFields}
>
<Checkbox name="returningUser" />
</Field>
<Field
name="userAddress"
label="Address"
description="This is so we can send you some swag and goodies if you ever request them!"
requiredFields={requiredFields}
>
<AddressInput
name="userAddress"
isPersonalAddressInput={true}
setValidationResult={setValidationResult}
/>
</Field>
</>
)
}

View file

@ -31,16 +31,16 @@ function StepIcon({ completed, number }) {
alignItems: 'center',
justifyContent: 'center',
position: 'absolute',
inset: '0',
inset: '0'
}}
>
<Text
sx={{
color: 'white',
fontSize: 2,
fontSize: 2
}}
>
{ number }
{number}
</Text>
</Flex>
</Box>
@ -51,10 +51,14 @@ function Step({ number, label, completed }) {
return (
<Flex sx={{ lineHeight: '1', alignItems: 'center', gap: '4' }}>
<StepIcon completed={completed} number={number + 1} />
<Text sx={{
fontSize: '3',
display: ['none', null, null, 'block']
}}>{label}</Text>
<Text
sx={{
fontSize: '3',
display: ['none', null, null, 'block']
}}
>
{label}
</Text>
</Flex>
)
}
@ -63,11 +67,7 @@ export default function Progress() {
const router = useRouter()
const step = parseInt(router.query.step)
const labels = [
'Intro',
'Organization info',
'Personal info'
]
const labels = ['Intro', 'Organization info', 'Personal info']
return (
<Flex
@ -82,4 +82,4 @@ export default function Progress() {
))}
</Flex>
)
}
}

View file

@ -2,81 +2,81 @@ import React, { useRef, useEffect } from 'react'
import { Box } from 'theme-ui'
export default function Watermark() {
/* This is going to come back to haunt me one day.
/* This is going to come back to haunt me one day.
It's an abomination but it works ... for now */
const shineRef = useRef()
const svgRef = useRef()
const shineRef = useRef()
const svgRef = useRef()
// Mouse move event
const handleMouseMove = ({ clientX, clientY }) => {
if (!shineRef.current || !svgRef.current) return
const svgWidth = svgRef.current.clientWidth / 100
const svgFromTop = svgRef.current.getBoundingClientRect().top
const svgFromLeft = svgRef.current.getBoundingClientRect().left
// Mouse move event
const handleMouseMove = ({ clientX, clientY }) => {
if (!shineRef.current || !svgRef.current) return
shineRef.current.style.top = `${clientY / svgWidth + 6.2}px`
shineRef.current.style.left = `${clientX / svgWidth + 9.2}px`
}
const svgWidth = svgRef.current.clientWidth / 100
const svgFromTop = svgRef.current.getBoundingClientRect().top
const svgFromLeft = svgRef.current.getBoundingClientRect().left
// Bind event
useEffect(() => {
window.addEventListener('mousemove', handleMouseMove)
return () => window.removeEventListener('mousemove', handleMouseMove)
}, [])
shineRef.current.style.top = `${clientY / svgWidth + 6.2}px`
shineRef.current.style.left = `${clientX / svgWidth + 9.2}px`
}
return (
<Box
sx={{
// Bind event
useEffect(() => {
window.addEventListener('mousemove', handleMouseMove)
return () => window.removeEventListener('mousemove', handleMouseMove)
}, [])
return (
<Box
sx={{
position: 'absolute',
zIndex: -1,
top: -330,
left: -480
}}
>
<svg
ref={svgRef}
viewBox="0 0 100 100"
preserveAspectRatio="none"
style={{
position: 'fixed',
width: '4600px',
overflow: 'hidden'
}}
>
<defs>
<clipPath id="my-clip-path">
<path d="M16.194 8.096A2.397 2.397 0 0 0 16 8.018V6c.358 0 .735.149.997.264.297.13.676.326 1.077.555a37.817 37.817 0 0 1 2.878 1.864c2.15 1.518 2.548 1.817 4.755 3.61a.999.999 0 1 1-1.414 1.414C22 12 21.9 11.799 19.798 10.317a35.088 35.088 0 0 0-2.716-1.761 9.091 9.091 0 0 0-.888-.46zM15.806 8.096c.09-.04.153-.064.194-.078V6c-.358 0-.735.149-.997.264-.297.13-.676.326-1.077.555a37.817 37.817 0 0 0-2.878 1.864C8.9 10.201 8.5 10.5 6.293 12.293a.999.999 0 1 0 1.414 1.414c2.294-1.707 2.394-1.908 4.495-3.39a35.088 35.088 0 0 1 2.716-1.761c.365-.209.65-.357.888-.46zM7 24a1 1 0 0 1 1-1h16a1 1 0 0 1 0 2H8a1 1 0 0 1-1-1z"></path>
<path d="M16 22a1 1 0 0 1-1-1v-7a1 1 0 0 1 2 0v7a1 1 0 0 1-1 1zM21 22a1 1 0 0 1-1-1v-7a1 1 0 0 1 2 0v7a1 1 0 0 1-1 1zM11 22a1 1 0 0 1-1-1v-7a1 1 0 0 1 2 0v7a1 1 0 0 1-1 1z"></path>
</clipPath>
</defs>
<g id="clip-container">
<foreignObject id="clip-content" width="100%" height="100%">
<Box
sx={{
position: 'absolute',
zIndex: -1,
top: -330,
left: -480,
}}
>
<svg
ref={svgRef}
viewBox="0 0 100 100"
preserveAspectRatio="none"
style={{
position: 'fixed',
width: '4600px',
overflow: 'hidden',
}}
width: '100%',
height: '100%',
backgroundColor: '#1d181f',
clipPath: 'url(#my-clip-path)'
}}
>
<defs>
<clipPath id="my-clip-path">
<path d="M16.194 8.096A2.397 2.397 0 0 0 16 8.018V6c.358 0 .735.149.997.264.297.13.676.326 1.077.555a37.817 37.817 0 0 1 2.878 1.864c2.15 1.518 2.548 1.817 4.755 3.61a.999.999 0 1 1-1.414 1.414C22 12 21.9 11.799 19.798 10.317a35.088 35.088 0 0 0-2.716-1.761 9.091 9.091 0 0 0-.888-.46zM15.806 8.096c.09-.04.153-.064.194-.078V6c-.358 0-.735.149-.997.264-.297.13-.676.326-1.077.555a37.817 37.817 0 0 0-2.878 1.864C8.9 10.201 8.5 10.5 6.293 12.293a.999.999 0 1 0 1.414 1.414c2.294-1.707 2.394-1.908 4.495-3.39a35.088 35.088 0 0 1 2.716-1.761c.365-.209.65-.357.888-.46zM7 24a1 1 0 0 1 1-1h16a1 1 0 0 1 0 2H8a1 1 0 0 1-1-1z"></path>
<path d="M16 22a1 1 0 0 1-1-1v-7a1 1 0 0 1 2 0v7a1 1 0 0 1-1 1zM21 22a1 1 0 0 1-1-1v-7a1 1 0 0 1 2 0v7a1 1 0 0 1-1 1zM11 22a1 1 0 0 1-1-1v-7a1 1 0 0 1 2 0v7a1 1 0 0 1-1 1z"></path>
</clipPath>
</defs>
<g id="clip-container">
<foreignObject id="clip-content" width="100%" height="100%">
<Box
sx={{
position: 'absolute',
width: '100%',
height: '100%',
backgroundColor: '#1d181f',
clipPath: 'url(#my-clip-path)',
}}
>
<Box
ref={shineRef}
sx={{
position: 'absolute',
width: '2px',
height: '2px',
borderRadius: '50%',
backgroundColor: 'red',
filter: 'blur(2px)',
}}
/>
</Box>
</foreignObject>
</g>
</svg>
</Box>
)
}
<Box
ref={shineRef}
sx={{
position: 'absolute',
width: '2px',
height: '2px',
borderRadius: '50%',
backgroundColor: 'red',
filter: 'blur(2px)'
}}
/>
</Box>
</foreignObject>
</g>
</svg>
</Box>
)
}

View file

@ -27,7 +27,8 @@ export default function Features({ partner = false }) {
name="Bank account"
body={
<>
Bank account under the hood with a custom, beautiful dashboard.
Bank account under the hood with a custom, beautiful
dashboard.
</>
}
/>

View file

@ -18,30 +18,33 @@ export default function Start() {
return (
<>
<Box as="section" id="apply" py={6}>
<Flex sx={{ flexDirection: 'column', alignItems: 'center', gap: 5, mx: 4 }}>
<Flex
sx={{ flexDirection: 'column', alignItems: 'center', gap: 5, mx: 4 }}
>
<Flex
sx={{
flexDirection: 'column',
textAlign: 'center',
gap: 3,
gap: 3
}}
>
<Heading variant="ultratitle" color="white">
Sign up for Hack&nbsp;Club&nbsp;Bank.
</Heading>
<Text color='muted' variant='lead' m='0 !important'>
Open to Hack Clubs, hackathons, and charitable organizations in the US and Canada.
<Text color="muted" variant="lead" m="0 !important">
Open to Hack Clubs, hackathons, and charitable organizations in
the US and Canada.
</Text>
</Flex>
<Stats />
<Timeline />
<Flex sx={{ flexDirection: 'column', textAlign: 'center', gap: 4, mx: 3 }}>
<Flex
sx={{ flexDirection: 'column', textAlign: 'center', gap: 4, mx: 3 }}
>
<ApplyButton />
<Text color='muted' sx={{ fontSize: 18 }}>We run Hack Club HQ on Bank!{' '}
<Link
href='https://bank.hackclub.com/hq'
color='primary'
>
<Text color="muted" sx={{ fontSize: 18 }}>
We run Hack Club HQ on Bank!{' '}
<Link href="https://bank.hackclub.com/hq" color="primary">
See&nbsp;our&nbsp;finances.
</Link>
</Text>

View file

@ -1,35 +1,40 @@
import { Text, Box, Flex } from 'theme-ui'
import { useEffect, useState } from 'react'
const easeInOutExpo = (x) =>
const easeInOutExpo = x =>
x === 0
? 0
: x === 1
? 1
: x < 0.5
? Math.pow(2, 20 * x - 10) / 2
: (2 - Math.pow(2, -20 * x + 10)) / 2;
function startMoneyAnimation(setBalance, amount, duration = 2_000, moneyFormatter) {
const startTime = performance.now();
: (2 - Math.pow(2, -20 * x + 10)) / 2
function startMoneyAnimation(
setBalance,
amount,
duration = 2_000,
moneyFormatter
) {
const startTime = performance.now()
function animate() {
const time = performance.now() - startTime;
const progress = time / duration;
const easedProgress = easeInOutExpo(progress);
const time = performance.now() - startTime
const progress = time / duration
const easedProgress = easeInOutExpo(progress)
setBalance(moneyFormatter(amount * easedProgress))
if (progress < 1) {
requestAnimationFrame(animate);
requestAnimationFrame(animate)
} else {
setBalance(moneyFormatter(amount))
}
}
requestAnimationFrame(animate);
requestAnimationFrame(animate)
}
function formatMoney(amount) {
const normalisedAmount = amount / 100
return normalisedAmount
@ -39,7 +44,7 @@ function formatMoney(amount) {
const Stats = () => {
const [transactedRaw, setTransactedRaw] = useState() // The raw amount transacted (in cents).
const [balance, setBalance] = useState(0) // A formatted balance string, split by decimal
const [balance, setBalance] = useState(0) // A formatted balance string, split by decimal
useEffect(() => {
if (!transactedRaw) {
@ -53,42 +58,49 @@ const Stats = () => {
}
let observer = new IntersectionObserver(
(e) => {
e => {
if (e[0].isIntersecting) {
console.info('intersecting')
startMoneyAnimation(setBalance, transactedRaw, 2_500, formatMoney)
}
},
{ threshold: 1.0 }
);
observer.observe(document.querySelector("#parent"));
)
observer.observe(document.querySelector('#parent'))
return () => observer.disconnect();
return () => observer.disconnect()
}, [transactedRaw])
return (
<Box id='parent'>
<Box id="parent">
<Flex sx={{ flexDirection: 'column', alignItems: 'center' }}>
<Text sx={{ fontSize: [3, 4] }}>
So far we have enabled
</Text>
{ transactedRaw ?
<Text sx={{ fontSize: [3, 4] }}>So far we have enabled</Text>
{transactedRaw ? (
<>
<Text variant='title' color='green' sx={{
<Text
variant="title"
color="green"
sx={{
color: 'green',
fontSize: [5, 6]
}}
>
{ balance[0] }
<Text sx={{ fontSize: [3, 4] }}>.{ balance[1] }</Text>
{balance[0]}
<Text sx={{ fontSize: [3, 4] }}>.{balance[1]}</Text>
</Text>
</>
:
<Text variant='title' color='green' sx={{
color: 'green',
fontSize: [5, 6]
}}>...</Text>
}
) : (
<Text
variant="title"
color="green"
sx={{
color: 'green',
fontSize: [5, 6]
}}
>
...
</Text>
)}
<Text sx={{ fontSize: [3, 4] }}>in transactions</Text>
</Flex>
</Box>

View file

@ -3,22 +3,29 @@ import Slide from 'react-reveal'
function Step({ stepIndex, label }) {
return (
<Flex sx={{
flexDirection: ['row', null, 'column'],
flex: '1 0 0',
alignItems: 'center',
maxWidth: ['24rem', null, '12rem'],
gap: 3
}}>
<Flex
sx={{
flexDirection: ['row', null, 'column'],
flex: '1 0 0',
alignItems: 'center',
maxWidth: ['24rem', null, '12rem'],
gap: 3
}}
>
<Image
src={`/bank/timeline-steps/step${stepIndex}.svg`}
sx={{ flexShrink: 0 }}
alt=''
alt=""
/>
<Text variant='lead' sx={{
textAlign: ['left', null, 'center'],
margin: '0px !important'
}}>{label}</Text>
<Text
variant="lead"
sx={{
textAlign: ['left', null, 'center'],
margin: '0px !important'
}}
>
{label}
</Text>
</Flex>
)
}
@ -30,29 +37,35 @@ export default function Timeline() {
'Hop on an intro call with the Bank team',
'Start fundraising!'
]
const stepSideLength = 64;
const stepSideLength = 64
return (
<Slide>
<Flex sx={{
flexDirection: ['column', null, 'row'],
justifyContent: 'space-between',
gap: 4,
maxWidth: ['300px', null, '1200px'],
mx: 'auto',
position: 'relative'
}}>
{labels.map((label, idx) => <Step stepIndex={idx + 1} label={label} key={idx} />)}
<Box sx={{
border: 'solid #8492a6',
borderWidth: '3px 3px 0 0',
position: 'absolute',
top: stepSideLength / 2,
left: '10%', // TODO: make this dynamic
right: ['auto', null, '10%'],
bottom: [stepSideLength / 2, null, 'auto'],
zIndex: -1,
}} />
<Flex
sx={{
flexDirection: ['column', null, 'row'],
justifyContent: 'space-between',
gap: 4,
maxWidth: ['300px', null, '1200px'],
mx: 'auto',
position: 'relative'
}}
>
{labels.map((label, idx) => (
<Step stepIndex={idx + 1} label={label} key={idx} />
))}
<Box
sx={{
border: 'solid #8492a6',
borderWidth: '3px 3px 0 0',
position: 'absolute',
top: stepSideLength / 2,
left: '10%', // TODO: make this dynamic
right: ['auto', null, '10%'],
bottom: [stepSideLength / 2, null, 'auto'],
zIndex: -1
}}
/>
</Flex>
</Slide>
)

View file

@ -7,7 +7,7 @@ const flashing = keyframes({
to: { opacity: 0 }
})
export default function Dot({hideOnMobile}) {
export default function Dot({ hideOnMobile }) {
return (
<Text
sx={{

View file

@ -1,9 +1,5 @@
import { Flex } from 'theme-ui'
export default function FlexCol({ children, ...props }) {
return (
<Flex sx={{ flexDirection: 'column', ...props }}>
{ children }
</Flex>
)
}
return <Flex sx={{ flexDirection: 'column', ...props }}>{children}</Flex>
}

View file

@ -21,10 +21,7 @@ const Content = () => (
<Container maxWidth={28} sx={{ mx: 0, pt: 4, pb: 2 }}>
<Text variant="eyebrow">Hack Club Bank</Text>
<br />
<Text
as="span"
variant="title"
>
<Text as="span" variant="title">
Grants, waived fees, and more!
</Text>
</Container>
@ -39,8 +36,8 @@ const Content = () => (
}
body={
<>
Running on Bank? Get a $500 grant once you have a venue,
provided by Hack Club with the help of{' '}
Running on Bank? Get a $500 grant once you have a venue, provided
by Hack Club with the help of{' '}
<Link
href="https://www.firstinspires.org/"
sx={{ fontStyle: 'italic', color: 'white' }}

View file

@ -23,11 +23,10 @@ export default function Overview() {
<Box sx={{ color: 'blue', fontSize: 28, mb: 2 }}>
The best way to learn is by <b>building</b>.
</Box>
A hackathon is a space that helps give makers everything they
need to start buildingmentors, collaborators, inspiration, and
a goal to work towards. Hackers will leave a hackathon with a
project of their own, ready and excited to keep hacking once
they get home.
A hackathon is a space that helps give makers everything they need
to start buildingmentors, collaborators, inspiration, and a goal
to work towards. Hackers will leave a hackathon with a project of
their own, ready and excited to keep hacking once they get home.
</Text>
<Text as="p" variant="subtitle">
<Box sx={{ color: 'green', fontSize: 28, mb: 2 }}>
@ -35,13 +34,29 @@ export default function Overview() {
</Box>
Hack Club is a global community of thousands of high school
makers. We're organizers, coders, hackers, painters, engineers,
musicians, writers, volunteers. We make things. We want others
to make things too.
musicians, writers, volunteers. We make things. We want others to
make things too.
</Text>
</Grid>
<Grid columns={[null, null, 2]} gap={[3, 4]} mt={4}>
<iframe width="100%" height="300px" src="https://www.youtube.com/embed/PnK4gzO6S3Q" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
<iframe width="100%" height="300px" src="https://www.youtube.com/embed/KLx4NZZPzMc" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
<iframe
width="100%"
height="300px"
src="https://www.youtube.com/embed/PnK4gzO6S3Q"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen
></iframe>
<iframe
width="100%"
height="300px"
src="https://www.youtube.com/embed/KLx4NZZPzMc"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen
></iframe>
</Grid>
</Container>
</Box>
@ -53,7 +68,13 @@ function Highlight({ children }) {
return (
<Text
as="span"
sx={{ bg: 'yellow', borderRadius: 'default', px: 1, color: '#5d114c', lineHeight: '1.3' }}
sx={{
bg: 'yellow',
borderRadius: 'default',
px: 1,
color: '#5d114c',
lineHeight: '1.3'
}}
>
{children}
</Text>

View file

@ -43,9 +43,23 @@ export default function ScrollingHackathons({
>
Join other high-schoolers at an upcoming hackathon.
</Heading>
<Box sx={{ maxWidth: ['95vw', '66vw'], margin: 'auto', display: 'flex', justifyContent: 'center', alignItems: 'center', mb: 2 }}>
<Text sx={{display: ['none', 'flex'], alignItems: 'center'}} ><Dot /></Text>
<Text variant="lead" sx={{ color: 'muted', mr: 2, textAlign: 'center' }}>
<Box
sx={{
maxWidth: ['95vw', '66vw'],
margin: 'auto',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
mb: 2
}}
>
<Text sx={{ display: ['none', 'flex'], alignItems: 'center' }}>
<Dot />
</Text>
<Text
variant="lead"
sx={{ color: 'muted', mr: 2, textAlign: 'center' }}
>
from{' '}
<NextLink href="https://hackathons.hackclub.com" passHref>
<Link sx={{ color: 'currentcolor' }}>

View file

@ -33,16 +33,15 @@ export default function Buttons({
alignItems: 'center',
color: 'inherit',
px: '3',
py: primary ? '12px' : 2,
py: primary ? '12px' : 2,
width: 'fit-content',
textTransform: 'none',
fontWeight: '400',
fontSize:
primary ? ['18px', '20px', '22px'] : [1, '16px', '18px'],
fontSize: primary ? ['18px', '20px', '22px'] : [1, '16px', '18px'],
backdropFilter: 'blur(2px)',
fontWeight: fontWeight
}}
as='a'
as="a"
href={link || '/'}
target="_blank"
rel="noreferrer"

View file

@ -148,7 +148,7 @@ const CardModel = ({
mt: ['-24px', '-32px', '-32px', '-32px'],
zIndex: 0
}}
alt=''
alt=""
/>
)}
{children}

View file

@ -129,7 +129,7 @@ export default function Epoch() {
ml: ['-24px', '-32px', '-32px', '-32px'],
mt: ['-24px', '-32px', '-32px', '-32px']
}}
alt='Hack Club Presents Epoch background'
alt="Hack Club Presents Epoch background"
/>
<Grid columns={[1, 1, '1fr 1.5fr']} sx={{ position: 'relative' }}>
<Box>
@ -140,7 +140,7 @@ export default function Epoch() {
position: 'relative',
zIndex: 2
}}
alt='Hack Club Presents Epoch header'
alt="Hack Club Presents Epoch header"
/>
<Box>
{timer.length ? (

View file

@ -64,7 +64,7 @@ export default function Slack({ data, slackKey, events }) {
ml: ['-24px', '-32px', '-32px', '-32px'],
mt: ['-24px', '-32px', '-32px', '-32px']
}}
alt='Slack AMA'
alt="Slack AMA"
/>
<Cover />
<Grid sx={{ zIndex: 2 }}>

View file

@ -5,7 +5,6 @@ import Tilt from '../../tilt'
/** @jsxImportSource theme-ui */
export default function SprigConsole({ stars, consoleCount }) {
return (
<Box sx={{ position: 'relative' }}>
@ -34,7 +33,7 @@ export default function SprigConsole({ stars, consoleCount }) {
mt: '-24px',
zIndex: 0
}}
alt='Printed circuit board'
alt="Printed circuit board"
/>
<Image
src="https://cloud-8u6hh0ho9-hack-club-bot.vercel.app/0sprig_console.svg"

View file

@ -210,7 +210,6 @@ export default function Sprig({ stars, game, gameImage, gameImage1 }) {
Build a Sprig game
</Buttons>
<Buttons
content="learn more on our github"
id="8"
link="https://github.com/hackclub/sprig"

View file

@ -42,7 +42,7 @@ export default function CarouselCards({
>
<Image
src={img}
alt='carousel card'
alt="carousel card"
sx={{
position: 'absolute',
top: ['-26px', '-30px', '-35px'],

View file

@ -15,10 +15,10 @@ export default function GitHub({
<Badge
variant="pill"
bg="snow"
as='a'
href='https://github.com/hackclub'
target='_blank'
rel='noopener'
as="a"
href="https://github.com/hackclub"
target="_blank"
rel="noopener"
sx={{
color: 'black',
textDecoration: 'none',
@ -32,14 +32,14 @@ export default function GitHub({
gap: 2,
height: '2rem',
width: ['fit-content', null, null, '100%'],
maxWidth: '30rem',
maxWidth: '30rem'
}}
{...props}
>
<Image
alt='GitHub user avatar'
alt="GitHub user avatar"
src={img}
sx={{ borderRadius: '100%', height: '90%', mr: 2, flexShrink: 0, }}
sx={{ borderRadius: '100%', height: '90%', mr: 2, flexShrink: 0 }}
/>
<Text
sx={{
@ -47,14 +47,9 @@ export default function GitHub({
textOverflow: 'ellipsis',
display: 'inline-block',
overflow: 'hidden',
whiteSpace: 'nowrap',
whiteSpace: 'nowrap',
flexShrink: 0,
display: [
'none',
'inline-block',
'inline-block',
'inline-block'
]
display: ['none', 'inline-block', 'inline-block', 'inline-block']
}}
>
{user}
@ -65,8 +60,8 @@ export default function GitHub({
maxWidth: '100%',
display: 'inline-block',
overflow: 'hidden',
whiteSpace: 'nowrap',
flexShrink: 1,
whiteSpace: 'nowrap',
flexShrink: 1
}}
>
{message}
@ -78,7 +73,7 @@ export default function GitHub({
color: 'inherit',
mx: 2,
flexShrink: 0,
ml: 'auto',
ml: 'auto'
}}
>
<RelativeTime value={time} titleformat="iso8601" />

View file

@ -123,7 +123,7 @@ export default function Secret({ reveal, ...props }) {
src={img}
width="30%"
sx={{ margin: 'auto' }}
alt='a secret dino!'
alt="a secret dino!"
/>
<Text>print kc</Text>
</Box>

View file

@ -4,7 +4,7 @@ import VanillaTilt from 'vanilla-tilt'
// NOTE(@lachlanjc): only pass one child!
const Tilt = ({ options = {}, children, ...props }) => {
const root = useRef(null)
useEffect(() => {
VanillaTilt.init(root.current, {
max: 7.5,

View file

@ -1,81 +1,83 @@
async function getOrRefreshToken() {
// Get the token from localStorage
const token = JSON.parse(localStorage.getItem('mapkit-token'))
if (token) {
// If the token is still valid, return it
if (Date.now() < token.refreshBefore) return token.accessToken
} else {
// The token is invalid or doesn't exist, so get a new one
// Get the token from localStorage
const token = JSON.parse(localStorage.getItem('mapkit-token'))
if (token) {
// If the token is still valid, return it
if (Date.now() < token.refreshBefore) return token.accessToken
} else {
// The token is invalid or doesn't exist, so get a new one
// This is a MapKit master token, restricted to https://hackclub.com
const master = 'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IkNSQkg2S1VLTEIifQ.eyJpc3MiOiJQNlBWMlI5NDQzIiwiaWF0IjoxNjc5NjY3NjIyLCJleHAiOjE3MTEyMzg0MDB9.E6g9QPdbEWLgF6tdcL0YfX8NescYnwKhQpXdiyRitNm7-Oot-3VH0ze9xUd8xkOzuS_-7KeWy4bfYTD-2yX7Sg';
// Get a MapKit server token
const res = await fetch('https://maps-api.apple.com/v1/token', {
headers: { Authorization: `Bearer ${master}` },
})
const resJson = await res.json()
// Set the token's expiration time to 10 seconds before the actual expiration time
resJson.refreshBefore = Date.now() + (resJson.expiresInSeconds * 1_000) - 10_000
// This is a MapKit master token, restricted to https://hackclub.com
const master =
'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IkNSQkg2S1VLTEIifQ.eyJpc3MiOiJQNlBWMlI5NDQzIiwiaWF0IjoxNjc5NjY3NjIyLCJleHAiOjE3MTEyMzg0MDB9.E6g9QPdbEWLgF6tdcL0YfX8NescYnwKhQpXdiyRitNm7-Oot-3VH0ze9xUd8xkOzuS_-7KeWy4bfYTD-2yX7Sg'
// Save the token to localStorage
localStorage.setItem('mapkit-token', JSON.stringify(resJson))
// Get a MapKit server token
const res = await fetch('https://maps-api.apple.com/v1/token', {
headers: { Authorization: `Bearer ${master}` }
})
const resJson = await res.json()
return resJson.accessToken
}
// Set the token's expiration time to 10 seconds before the actual expiration time
resJson.refreshBefore =
Date.now() + resJson.expiresInSeconds * 1_000 - 10_000
// Save the token to localStorage
localStorage.setItem('mapkit-token', JSON.stringify(resJson))
return resJson.accessToken
}
}
//TODO: Limit the number of retries
export async function search(query) {
if (!query.trim()) return
const token = await getOrRefreshToken()
if (!query.trim()) return
const res = await fetch(`https://maps-api.apple.com/v1/search?q=${query}`, {
headers: { Authorization: `Bearer ${token}` },
})
const resJson = await res.json()
if (resJson.error) {
if (resJson.error.message === 'Not Authorized') {
// The token is invalid, so remove it from localStorage
localStorage.removeItem('mapkit-token')
const token = await getOrRefreshToken()
// Try again
console.warn('MapKit token expired, refreshing')
return search(query)
} else {
throw new Error(resJson.error.message)
}
const res = await fetch(`https://maps-api.apple.com/v1/search?q=${query}`, {
headers: { Authorization: `Bearer ${token}` }
})
const resJson = await res.json()
if (resJson.error) {
if (resJson.error.message === 'Not Authorized') {
// The token is invalid, so remove it from localStorage
localStorage.removeItem('mapkit-token')
// Try again
console.warn('MapKit token expired, refreshing')
return search(query)
} else {
throw new Error(resJson.error.message)
}
}
return resJson
return resJson
}
export async function geocode(query) {
if (!query.trim()) return
if (!query.trim()) return
const token = await getOrRefreshToken()
const token = await getOrRefreshToken()
const res = await fetch(`https://maps-api.apple.com/v1/geocode?q=${query}`, {
headers: { Authorization: `Bearer ${token}` },
})
const resJson = await res.json()
if (resJson.error) {
if (resJson.error.message === 'Not Authorized') {
// The token is invalid, so remove it from localStorage
localStorage.removeItem('mapkit-token')
const res = await fetch(`https://maps-api.apple.com/v1/geocode?q=${query}`, {
headers: { Authorization: `Bearer ${token}` }
})
const resJson = await res.json()
// Try again
console.warn('MapKit token expired, refreshing')
return geocode(query)
} else {
throw new Error(resJson.error.message)
}
if (resJson.error) {
if (resJson.error.message === 'Not Authorized') {
// The token is invalid, so remove it from localStorage
localStorage.removeItem('mapkit-token')
// Try again
console.warn('MapKit token expired, refreshing')
return geocode(query)
} else {
throw new Error(resJson.error.message)
}
}
return resJson
}
return resJson
}

View file

@ -18,7 +18,7 @@ export default async function handler(req, res) {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.HCB_API_TOKEN || ""}`
Authorization: `Bearer ${process.env.HCB_API_TOKEN || ''}`
}
})
.then(r => r.json())
@ -35,15 +35,16 @@ export default async function handler(req, res) {
'Mailing Address': data.mailingAddress,
'Address Line 1': data.addressLine1,
'Address Line 2': data.addressLine2,
'City': data.addressCity,
'State': data.addressState,
City: data.addressCity,
State: data.addressState,
'Zip Code': data.addressZip,
'Address Country': data.addressCountry,
'Country': data.eventCountry,
Country: data.eventCountry,
'Event Location': data.eventLocation,
'Have you used Hack Club Bank for any previous events?': data.returningUser,
'Have you used Hack Club Bank for any previous events?':
data.returningUser,
'How did you hear about HCB?': data.referredBy,
'Transparent': data.transparent,
Transparent: data.transparent,
'HCB account URL': `https://bank.hackclub.com/${r.slug}`
})
res.writeHead(302, { Location: '/bank/apply/success' }).end()

View file

@ -34,10 +34,12 @@ export default function Apply() {
// Set the query url parameter to 1 if it's not present
if (!step || step < 1) {
router.push({
pathname: router.pathname,
query: { ...router.query, step: 1 } },
undefined,
router.push(
{
pathname: router.pathname,
query: { ...router.query, step: 1 }
},
undefined,
{}
)
}
@ -47,9 +49,9 @@ export default function Apply() {
<>
<script
async
src='https://maps.googleapis.com/maps/api/js?key=AIzaSyApxZZ8-Eh_6RgHUu8-BAOpx3xhfF2yK9U&libraries=places&mapInit=foo'
src="https://maps.googleapis.com/maps/api/js?key=AIzaSyApxZZ8-Eh_6RgHUu8-BAOpx3xhfF2yK9U&libraries=places&mapInit=foo"
></script>
<Meta as={Head} title="Apply for Hack Club Bank" />
<ForceTheme theme="dark" />
@ -61,7 +63,7 @@ export default function Apply() {
'"title" "form" "form" "nav"',
null,
null,
'"title form" "title form" "nav form"',
'"title form" "title form" "nav form"'
],
height: ['auto', null, null, '100vh'],
p: [4, 5]
@ -69,15 +71,21 @@ export default function Apply() {
>
<Box sx={{ gridArea: 'title' }}>
<FlexCol gap={[4, null, null, '20vh']}>
<Text variant='title'>Let's get you<br />set up on bank.</Text>
<Text variant="title">
Let's get you
<br />
set up on bank.
</Text>
<Progress />
</FlexCol>
</Box>
<Box sx={{ gridArea: 'form', overflowY: 'auto' }}>
<FormContainer ref={formContainer}>
{ step === 1 && <BankInfo /> }
{ step === 2 && <OrganizationInfoForm requiredFields={requiredFields} /> }
{ step === 3 && <PersonalInfoForm requiredFields={requiredFields} /> }
{step === 1 && <BankInfo />}
{step === 2 && (
<OrganizationInfoForm requiredFields={requiredFields} />
)}
{step === 3 && <PersonalInfoForm requiredFields={requiredFields} />}
</FormContainer>
</Box>
<Flex
@ -85,7 +93,7 @@ export default function Apply() {
gridArea: 'nav',
alignSelf: 'end',
alignItems: 'flex-end',
justifyContent: 'space-between',
justifyContent: 'space-between'
}}
>
<NavButton isBack={true} form={formContainer} />
@ -100,14 +108,16 @@ export default function Apply() {
// Validate the address
if (step === 3) {
// Get the raw personal address input
const userAddress = sessionStorage.getItem('bank-signup-userAddressRaw')
const userAddress = sessionStorage.getItem(
'bank-signup-userAddressRaw'
)
if (!userAddress) return
const result = await geocode(userAddress)
const addrComp = (type) =>
const addrComp = type =>
result.results[0].structuredAddress[type]
const thoroughfare = addrComp('fullThoroughfare')
const city = addrComp('locality')
const state = addrComp('administrativeArea')
@ -118,9 +128,18 @@ export default function Apply() {
sessionStorage.setItem('bank-signup-addressLine1', thoroughfare)
sessionStorage.setItem('bank-signup-addressCity', city ?? '')
sessionStorage.setItem('bank-signup-addressState', state ?? '')
sessionStorage.setItem('bank-signup-addressZip', postalCode ?? '')
sessionStorage.setItem('bank-signup-addressCountry', country ?? '')
sessionStorage.setItem('bank-signup-addressCountryCode', countryCode ?? '')
sessionStorage.setItem(
'bank-signup-addressZip',
postalCode ?? ''
)
sessionStorage.setItem(
'bank-signup-addressCountry',
country ?? ''
)
sessionStorage.setItem(
'bank-signup-addressCountryCode',
countryCode ?? ''
)
}
}}
/>

View file

@ -10,467 +10,476 @@ import Footer from '../../components/footer'
import Icon from '../../components/icon'
import Tilt from '../../components/tilt'
function Bullet({ glow=true, icon, href, children }) {
let effectColours = [
'#ec3750',
'#ff8c37',
'#f1c40f',
'#33d6a6',
'#5bc0de',
'#338eda',
'#a633d6',
]
function Bullet({ glow = true, icon, href, children }) {
let effectColours = [
'#ec3750',
'#ff8c37',
'#f1c40f',
'#33d6a6',
'#5bc0de',
'#338eda',
'#a633d6'
]
// Raw doggen this trig, no AI this is natty
function keyframeGenerator(spread, blur, colours, opacity = 0.5) {
let hexOpacity =
Math.max(Math.min(Math.round(opacity * 255), 255), 0).toString(16).padStart(2, '0');
// Raw doggen this trig, no AI this is natty
function keyframeGenerator(spread, blur, colours, opacity = 0.5) {
let hexOpacity = Math.max(Math.min(Math.round(opacity * 255), 255), 0)
.toString(16)
.padStart(2, '0')
let final = {}
for (let i = 0; i <= 100; i++) {
let baseX = Math.sin(i * Math.PI / 50) // 50 keyframes for each pi radians
let baseY = -Math.cos(i * Math.PI / 50)
let final = {}
for (let i = 0; i <= 100; i++) {
let baseX = Math.sin((i * Math.PI) / 50) // 50 keyframes for each pi radians
let baseY = -Math.cos((i * Math.PI) / 50)
// Ensure no scientific notation
const roundFactor = 1_000_000
baseX = Math.round(baseX * roundFactor) / roundFactor
baseY = Math.round(baseY * roundFactor) / roundFactor
// Ensure no scientific notation
const roundFactor = 1_000_000
baseX = Math.round(baseX * roundFactor) / roundFactor
baseY = Math.round(baseY * roundFactor) / roundFactor
let boxShadow = ''
for (let c = 0; c < colours.length; c++) {
// Rotate by 2pi / colours.length * c radians
let x = baseX * Math.cos(2 * Math.PI * c / colours.length) - baseY * Math.sin(2 * Math.PI * c / colours.length)
let y = baseX * Math.sin(2 * Math.PI * c / colours.length) + baseY * Math.cos(2 * Math.PI * c / colours.length)
let boxShadow = ''
for (let c = 0; c < colours.length; c++) {
// Rotate by 2pi / colours.length * c radians
let x =
baseX * Math.cos((2 * Math.PI * c) / colours.length) -
baseY * Math.sin((2 * Math.PI * c) / colours.length)
let y =
baseX * Math.sin((2 * Math.PI * c) / colours.length) +
baseY * Math.cos((2 * Math.PI * c) / colours.length)
boxShadow += `${x * spread}px ${y * spread}px ${blur}px ${colours[c]}${hexOpacity},`
}
boxShadow += `${x * spread}px ${y * spread}px ${blur}px ${
colours[c]
}${hexOpacity},`
}
// Remove trailing comma
boxShadow = boxShadow.slice(0, -1)
// Remove trailing comma
boxShadow = boxShadow.slice(0, -1)
final[i + '%'] = { boxShadow }
}
return final
final[i + '%'] = { boxShadow }
}
const shadowSpread = glow ? 5 : 0
const shadowBlur = glow ? 10 : 0
const animatedBoxShadow = keyframes(keyframeGenerator(shadowSpread, shadowBlur, effectColours))
return final
}
const borderWidth = '2px'
const shadowSpread = glow ? 5 : 0
const shadowBlur = glow ? 10 : 0
const animatedBoxShadow = keyframes(
keyframeGenerator(shadowSpread, shadowBlur, effectColours)
)
return (
<Tilt>
<Flex
as='a'
{...href && { href }}
target='_blank'
sx={{
display: 'flex',
alignItems: 'center',
gap: 2,
const borderWidth = '2px'
width: '20rem',
return (
<Tilt>
<Flex
as="a"
{...(href && { href })}
target="_blank"
sx={{
display: 'flex',
alignItems: 'center',
gap: 2,
borderWidth,
borderRadius: '8px !important',
p: '8px !important',
width: '20rem',
textDecoration: 'none',
color: 'inherit',
borderWidth,
borderRadius: '8px !important',
p: '8px !important',
backgroundColor: 'var(--theme-ui-colors-darkless)',
'&:hover::after': {
content: '""',
position: 'absolute',
inset: 0,
boxShadow: `linear-gradient(60deg, ${effectColours[0]}, ${effectColours[1]}, ${effectColours[2]}, ${effectColours[3]}, ${effectColours[4]})`,
borderRadius: 'inherit',
zIndex: -1,
animation: `${animatedBoxShadow} 5s ease infinite`,
}
}}
>
<Icon glyph={icon} size={42} sx={{ color: 'red', flexShrink: 0 }} />
<Box sx={{ textAlign: 'left' }}>{children}</Box>
{ href &&
<Icon glyph='external-fill' size={32} sx={{ position: 'absolute', top: 0, right: 0, translate: '50% -50%', color: 'muted' }} />
}
</Flex>
</Tilt>
)
textDecoration: 'none',
color: 'inherit',
backgroundColor: 'var(--theme-ui-colors-darkless)',
'&:hover::after': {
content: '""',
position: 'absolute',
inset: 0,
boxShadow: `linear-gradient(60deg, ${effectColours[0]}, ${effectColours[1]}, ${effectColours[2]}, ${effectColours[3]}, ${effectColours[4]})`,
borderRadius: 'inherit',
zIndex: -1,
animation: `${animatedBoxShadow} 5s ease infinite`
}
}}
>
<Icon glyph={icon} size={42} sx={{ color: 'red', flexShrink: 0 }} />
<Box sx={{ textAlign: 'left' }}>{children}</Box>
{href && (
<Icon
glyph="external-fill"
size={32}
sx={{
position: 'absolute',
top: 0,
right: 0,
translate: '50% -50%',
color: 'muted'
}}
/>
)}
</Flex>
</Tilt>
)
}
function BulletBox({ padding='2rem', children }) {
return (
<Box
as='ul'
sx={{
width: '100%',
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'center',
gridGap: '2rem',
padding,
}}
>
{ children }
</Box>
)
function BulletBox({ padding = '2rem', children }) {
return (
<Box
as="ul"
sx={{
width: '100%',
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'center',
gridGap: '2rem',
padding
}}
>
{children}
</Box>
)
}
function Section({ id, children }) {
return (
<Flex as='section' id={id} sx={{ flexDirection: 'column', pt: 5 }}>
{ children }
</Flex>
)
return (
<Flex as="section" id={id} sx={{ flexDirection: 'column', pt: 5 }}>
{children}
</Flex>
)
}
export default function FiscalSponsorship() {
const gridRef = useRef()
const glowRef = useRef()
const gridRef = useRef()
const glowRef = useRef()
const scrollPos = useRef(0)
const mousePos = useRef([0, 0])
const scrollPos = useRef(0)
const mousePos = useRef([0, 0])
const setGlowMaskPosition = () => {
const finalPos = [-mousePos.current[0], -mousePos.current[1] + scrollPos.current]
glowRef.current.style.maskPosition = `${finalPos[0]}px ${finalPos[1]}px`;
glowRef.current.style.WebkitMaskPosition = `${finalPos[0]}px ${finalPos[1]}px`;
const setGlowMaskPosition = () => {
const finalPos = [
-mousePos.current[0],
-mousePos.current[1] + scrollPos.current
]
glowRef.current.style.maskPosition = `${finalPos[0]}px ${finalPos[1]}px`
glowRef.current.style.WebkitMaskPosition = `${finalPos[0]}px ${finalPos[1]}px`
}
useEffect(() => {
const handleScroll = e => {
scrollPos.current = -window.scrollY / 10
gridRef.current.style.transform = `translateY(${scrollPos.current}px)`
setGlowMaskPosition()
}
useEffect(() => {
const handleScroll = (e) => {
scrollPos.current = -window.scrollY / 10
const handleMouseMove = e => {
const x = e.clientX
const y = e.clientY
glowRef.current.style.left = x + 'px'
glowRef.current.style.top = y + 'px'
gridRef.current.style.transform = `translateY(${scrollPos.current}px)`
mousePos.current = [x, y]
setGlowMaskPosition()
}
setGlowMaskPosition()
}
window.addEventListener('scroll', handleScroll)
window.addEventListener('mousemove', handleMouseMove)
return () => {
window.removeEventListener('scroll', handleScroll)
window.removeEventListener('mousemove', handleMouseMove)
}
}, [])
const handleMouseMove = (e) => {
const x = e.clientX
const y = e.clientY
glowRef.current.style.left = x + 'px'
glowRef.current.style.top = y + 'px'
mousePos.current = [x, y]
setGlowMaskPosition()
}
window.addEventListener('scroll', handleScroll)
window.addEventListener('mousemove', handleMouseMove)
return () => {
window.removeEventListener('scroll', handleScroll)
window.removeEventListener('mousemove', handleMouseMove)
}
}, [])
return (
<Box as="main" key="main" sx={{ position: 'relative' }}>
<style>
{`*{
return (
<Box as="main" key="main" sx={{ position: 'relative' }}>
<style>
{`*{
scroll-behavior: smooth;
}`}
</style>
<Box
ref={gridRef}
sx={{
position: 'fixed',
inset: 0,
height: '1000%',
zIndex: -2,
backgroundSize: '20px 20px',
backgroundImage: `linear-gradient(to right, #23262D 1px, transparent 1px),
</style>
<Box
ref={gridRef}
sx={{
position: 'fixed',
inset: 0,
height: '1000%',
zIndex: -2,
backgroundSize: '20px 20px',
backgroundImage: `linear-gradient(to right, #23262D 1px, transparent 1px),
linear-gradient(to bottom, #23262D 1px, transparent 1px) `,
backgroundPosition: 'top left',
maskImage: `linear-gradient(180deg, transparent 0%, white 3%)`,
}}
/>
<Box
ref={glowRef}
sx={{
pointerEvents: 'none',
zIndex: -2,
position: 'fixed',
top: '0',
left: '0',
width: '20rem',
height: '20rem',
background: 'red',
opacity: 0.2,
borderRadius: '50%',
filter: 'blur(2rem)',
translate: '-50% -50%',
// Mask it to the grid background
maskImage: `linear-gradient(to right, #23262D 1px, transparent 1px),
backgroundPosition: 'top left',
maskImage: `linear-gradient(180deg, transparent 0%, white 3%)`
}}
/>
<Box
ref={glowRef}
sx={{
pointerEvents: 'none',
zIndex: -2,
position: 'fixed',
top: '0',
left: '0',
width: '20rem',
height: '20rem',
background: 'red',
opacity: 0.2,
borderRadius: '50%',
filter: 'blur(2rem)',
translate: '-50% -50%',
// Mask it to the grid background
maskImage: `linear-gradient(to right, #23262D 1px, transparent 1px),
linear-gradient(to bottom, #23262D 1px, transparent 1px) `,
maskSize: '20px 20px, 20px 20px, cover',
maskPosition: '0px 0px',
maskSize: '20px 20px, 20px 20px, cover',
maskPosition: '0px 0px'
}}
/>
<Nav dark />
<ForceTheme theme="dark" />
<Meta
as={Head}
title="Fiscal Sponsorship"
description="What is fiscal sponsorship?"
image="/bank/og-image.png"
>
<title>Fiscal Sponsorship Hack&nbsp;Club&nbsp;Bank</title>
</Meta>
<Container sx={{ textAlign: 'center', mt: 6 }}>
<FlexCol gap={4} alignItems="center">
<FlexCol gap={4} alignItems="center">
<Text variant="ultratitle">Fiscal sponsorship</Text>
<Text variant="title">
The fast track to{' '}
<Text
sx={{
color: 'red',
textShadow: '0 0 50px var(--theme-ui-colors-red)'
}}
/>
<Nav dark />
<ForceTheme theme="dark" />
<Meta
as={Head}
title="Fiscal Sponsorship"
description="What is fiscal sponsorship?"
image="/bank/og-image.png"
>
<title>Fiscal Sponsorship Hack&nbsp;Club&nbsp;Bank</title>
</Meta>
<Container sx={{ textAlign: 'center', mt: 6 }}>
<FlexCol gap={4} alignItems='center'>
<FlexCol gap={4} alignItems='center'>
<Text variant='ultratitle'>Fiscal sponsorship</Text>
<Text variant='title'>
The fast track to <Text sx={{ color: 'red', textShadow: '0 0 50px var(--theme-ui-colors-red)' }}>501(c)(3)&nbsp;nonprofit </Text>
status for your startup
</Text>
</FlexCol>
<Text variant='headline'>
Building a project, event, or organization on a
mission to serve the public good or your community?
Obtaining 501(c)(3) public charity status in the U.S.
just got easier through fiscal sponsorship.
</Text>
>
501(c)(3)&nbsp;nonprofit{' '}
</Text>
status for your startup
</Text>
</FlexCol>
<Text variant="headline">
Building a project, event, or organization on a mission to serve the
public good or your community? Obtaining 501(c)(3) public charity
status in the U.S. just got easier through fiscal sponsorship.
</Text>
<FlexCol gap={1} alignItems='center'>
<Text variant='headline'>Jump to:</Text>
<BulletBox padding='0'>
<Link sx={{fontSize: 2 }} href='#what-is'>Whats Fiscal Sponsorship?</Link>
<Link sx={{fontSize: 2 }} href='#requirements'>Requirements for Fiscal Sponsorship</Link>
<Link sx={{fontSize: 2 }} href='#partner'>Partner with Hack&nbsp;Club&nbsp;Bank</Link>
</BulletBox>
</FlexCol>
<>
<Text variant='lead'>
Every year, thousands of organizations from around
the world apply through the IRS to become a U.S. 501(c)(3)
for charitable recognition and tax exemptions.
The process for obtaining legal status can take anywhere
from 2-12 months, and as a nonprofit organizer,
its important to know that this can mean:
</Text>
<BulletBox>
<Bullet glow={false} icon='sad'>
$3,000 in up-front costs, from filing different
applications and forms to support from legal counsel.
</Bullet>
<Bullet glow={false} icon='sad'>
The potential for the IRS to reject an application
(and not return your money).
</Bullet>
<Bullet glow={false} icon='sad'>
An annual filing fee of $2,500.
</Bullet>
<Bullet glow={false} icon='sad'>
Hiring bookkeepers and accountants to prepare
taxes and provide upkeep to stay in good standing.
</Bullet>
<Bullet glow={false} icon='sad'>
Up to $5,000 to close down shop if you lose or terminate status.
</Bullet>
</BulletBox>
</>
<Text variant='lead'>
Unfortunately, between the price and time needed to
organize a nonprofit, these barriers prevent many
charitable initiatives from exiting an idea phase
to go on to making a valuable impact throughout the world.
</Text>
<Text variant='lead'>
So 501(c)(3) status is cool and all,
but why go through the hassle of applying
when its so expensive and time consuming?
</Text>
<FlexCol gap={1} alignItems="center">
<Text variant="headline">Jump to:</Text>
<BulletBox padding="0">
<Link sx={{ fontSize: 2 }} href="#what-is">
Whats Fiscal Sponsorship?
</Link>
<Link sx={{ fontSize: 2 }} href="#requirements">
Requirements for Fiscal Sponsorship
</Link>
<Link sx={{ fontSize: 2 }} href="#partner">
Partner with Hack&nbsp;Club&nbsp;Bank
</Link>
</BulletBox>
</FlexCol>
<>
<Text variant="lead">
Every year, thousands of organizations from around the world apply
through the IRS to become a U.S. 501(c)(3) for charitable
recognition and tax exemptions. The process for obtaining legal
status can take anywhere from 2-12 months, and as a nonprofit
organizer, its important to know that this can mean:
</Text>
<BulletBox>
<Bullet glow={false} icon="sad">
$3,000 in up-front costs, from filing different applications and
forms to support from legal counsel.
</Bullet>
<Bullet glow={false} icon="sad">
The potential for the IRS to reject an application (and not
return your money).
</Bullet>
<Bullet glow={false} icon="sad">
An annual filing fee of $2,500.
</Bullet>
<Bullet glow={false} icon="sad">
Hiring bookkeepers and accountants to prepare taxes and provide
upkeep to stay in good standing.
</Bullet>
<Bullet glow={false} icon="sad">
Up to $5,000 to close down shop if you lose or terminate status.
</Bullet>
</BulletBox>
</>
<Text variant="lead">
Unfortunately, between the price and time needed to organize a
nonprofit, these barriers prevent many charitable initiatives from
exiting an idea phase to go on to making a valuable impact
throughout the world.
</Text>
<Text variant="lead">
So 501(c)(3) status is cool and all, but why go through the hassle
of applying when its so expensive and time consuming?
</Text>
<Text variant='headline'>
As a legally recognized 501(c)(3) nonprofit
in the U.S., there are loads of legal tax
benefits that make the status worth it, like:
</Text>
<BulletBox>
<Bullet icon='payment'>
The ability to receive <b>tax deductible
donations</b> from sponsors.
</Bullet>
<Bullet icon='member-add'>
Reduced taxable income for your U.S.
supporters, which <b>incentivizes giving</b>.
</Bullet>
<Bullet icon='leader'>
<b>Exemption</b> from U.S. federal
income tax and unemployment tax.
</Bullet>
<Bullet icon='bolt'>
Potential exemption from state
income, sales, and employment taxes.
</Bullet>
<Bullet icon='email'>
Potential for reduced rates on postage,
marketing, advertising, legal counsel, and more.
</Bullet>
</BulletBox>
<Text variant='lead'>
As it turns out, theres an alternative route
for startups, student-led initiatives,
or anyone looking to avoid a headache
with the IRS to obtain all the benefits of
501(c)(3) status. To avoid the traditional
filing route, go for fiscal sponsorship.
</Text>
<Text variant="headline">
As a legally recognized 501(c)(3) nonprofit in the U.S., there are
loads of legal tax benefits that make the status worth it, like:
</Text>
<BulletBox>
<Bullet icon="payment">
The ability to receive <b>tax deductible donations</b> from
sponsors.
</Bullet>
<Bullet icon="member-add">
Reduced taxable income for your U.S. supporters, which{' '}
<b>incentivizes giving</b>.
</Bullet>
<Bullet icon="leader">
<b>Exemption</b> from U.S. federal income tax and unemployment
tax.
</Bullet>
<Bullet icon="bolt">
Potential exemption from state income, sales, and employment
taxes.
</Bullet>
<Bullet icon="email">
Potential for reduced rates on postage, marketing, advertising,
legal counsel, and more.
</Bullet>
</BulletBox>
<Text variant="lead">
As it turns out, theres an alternative route for startups,
student-led initiatives, or anyone looking to avoid a headache with
the IRS to obtain all the benefits of 501(c)(3) status. To avoid the
traditional filing route, go for fiscal sponsorship.
</Text>
<Section id='what-is'>
<Text variant='title'>
Whats Fiscal Sponsorship?
</Text>
<Text variant='lead'>
By legally partnering with an existing
nonprofit offering fiscal sponsorship,
projects and events can claim all the
legal benefits of individual 501(c)(3)
status. Piggy-backing off this existing
status, organizations also gain access
to resources from their fiscal sponsor like:
</Text>
<BulletBox>
<Bullet icon='docs'>
Bookkeeping and administration ensuring
that all paperwork and taxes are filed.
</Bullet>
<Bullet icon='bag'>
Fully established HR and benefits,
which can vary by the fiscal sponsor.
</Bullet>
<Bullet icon='admin'>
A board of directors - you
dont have to organize your own!
</Bullet>
<Bullet icon='payment'>
Fully transparent operational fees,
typically ranging from 7-12% that
prevent you from paying operating costs.
</Bullet>
<Bullet icon='door-leave'>
The ability to terminate your fiscal
sponsorship agreement and file for
separate tax-exempt status at any point.
</Bullet>
</BulletBox>
<Text variant='lead'>
If youre brand new to nonprofit organizing
or unsure where your project will take you,
fiscal sponsorship is a great tool to help
manage your finances and gauge whether becoming
an independent nonprofit down the line is
practical or financially feasible.
</Text>
<Text variant='lead'>
Fiscal sponsorship makes it so that hacks
arent just for folding that stubborn fitted
sheet or sending an email to hundreds of
people in seconds; theyre also for obtaining
nonprofit status.
</Text>
</Section>
<Section id='requirements'>
<Text variant='title'>
Requirements for Fiscal Sponsorship
</Text>
<Text variant='lead'>
Depending on the fiscal sponsor you choose,
requirements for working together can vary.
Fiscal sponsors generally ask that your
nonprofits goals be similar to theirs.
They usually also ask that your organization
or event commits to remaining charitable in
nature and refrains from activites that may
result in loss of 501(c)(3) status.
</Text>
</Section>
<Section id="what-is">
<Text variant="title">Whats Fiscal Sponsorship?</Text>
<Text variant="lead">
By legally partnering with an existing nonprofit offering fiscal
sponsorship, projects and events can claim all the legal benefits
of individual 501(c)(3) status. Piggy-backing off this existing
status, organizations also gain access to resources from their
fiscal sponsor like:
</Text>
<BulletBox>
<Bullet icon="docs">
Bookkeeping and administration ensuring that all paperwork and
taxes are filed.
</Bullet>
<Bullet icon="bag">
Fully established HR and benefits, which can vary by the fiscal
sponsor.
</Bullet>
<Bullet icon="admin">
A board of directors - you dont have to organize your own!
</Bullet>
<Bullet icon="payment">
Fully transparent operational fees, typically ranging from 7-12%
that prevent you from paying operating costs.
</Bullet>
<Bullet icon="door-leave">
The ability to terminate your fiscal sponsorship agreement and
file for separate tax-exempt status at any point.
</Bullet>
</BulletBox>
<Text variant="lead">
If youre brand new to nonprofit organizing or unsure where your
project will take you, fiscal sponsorship is a great tool to help
manage your finances and gauge whether becoming an independent
nonprofit down the line is practical or financially feasible.
</Text>
<Text variant="lead">
Fiscal sponsorship makes it so that hacks arent just for folding
that stubborn fitted sheet or sending an email to hundreds of
people in seconds; theyre also for obtaining nonprofit status.
</Text>
</Section>
<Section id='partner'>
<Text variant='title'>
Partner with Hack&nbsp;Club&nbsp;Bank
</Text>
<Text variant='lead'>
While many fiscal sponsors require that their
partners relate to their mission in similar ways,
at Hack&nbsp;Club&nbsp;Bank, weve built our infrastructure
to support hundreds of causes in all areas of
charitability. Check out some of the resources
weve built our fiscal sponsorship foundation on:
</Text>
<BulletBox>
<Bullet icon='bank-account'>
A web interface that looks and operates
just like a bank account
</Bullet>
<Bullet icon='card'>
Fee-free invoicing, ACH or check
transfers, and reimbursements
</Bullet>
<Bullet icon='link'>
A customizable and embeddable
donations URL
</Bullet>
<Bullet icon='google'>
Perks like Google Workspace,
PVSA certification, and 1Password credits
</Bullet>
<Bullet icon='purse' href='https://bank.hackclub.com/hq'>
Optional transparency mode to show
off finances to donors and the public
</Bullet>
<Bullet icon='friend'>
24 hour weekday turnaround time from a
full-time support team for all queries
</Bullet>
<Bullet icon='everything' href='http://hackclub.com/bank'>
...and more!
</Bullet>
</BulletBox>
<Text variant='lead'>
Looking for nonprofit status and not a religious or
political organization? Wed love to meet you and
chat about working together. Feel free to apply
here or email our team if you have more questions
about fiscal sponsorship!
</Text>
</Section>
<Text variant='lead'>
At its core, Hack Club is a nonprofit encouraging
students to learn how to code by building and making
cool things. Hack&nbsp;Club&nbsp;Bank was built out by teenagers
at Hack Club and continues to be a real-world space
that teens can hack on every day.
</Text>
</FlexCol>
</Container>
<Box sx={{
height: '100px',
position: 'relative',
width: '100%',
overflow: 'hidden',
<Section id="requirements">
<Text variant="title">Requirements for Fiscal Sponsorship</Text>
<Text variant="lead">
Depending on the fiscal sponsor you choose, requirements for
working together can vary. Fiscal sponsors generally ask that your
nonprofits goals be similar to theirs. They usually also ask that
your organization or event commits to remaining charitable in
nature and refrains from activites that may result in loss of
501(c)(3) status.
</Text>
</Section>
'&::after': {
content: '""',
width: '500%',
height: '100%',
<Section id="partner">
<Text variant="title">Partner with Hack&nbsp;Club&nbsp;Bank</Text>
<Text variant="lead">
While many fiscal sponsors require that their partners relate to
their mission in similar ways, at Hack&nbsp;Club&nbsp;Bank, weve
built our infrastructure to support hundreds of causes in all
areas of charitability. Check out some of the resources weve
built our fiscal sponsorship foundation on:
</Text>
<BulletBox>
<Bullet icon="bank-account">
A web interface that looks and operates just like a bank account
</Bullet>
<Bullet icon="card">
Fee-free invoicing, ACH or check transfers, and reimbursements
</Bullet>
<Bullet icon="link">
A customizable and embeddable donations URL
</Bullet>
<Bullet icon="google">
Perks like Google Workspace, PVSA certification, and 1Password
credits
</Bullet>
<Bullet icon="purse" href="https://bank.hackclub.com/hq">
Optional transparency mode to show off finances to donors and
the public
</Bullet>
<Bullet icon="friend">
24 hour weekday turnaround time from a full-time support team
for all queries
</Bullet>
<Bullet icon="everything" href="http://hackclub.com/bank">
...and more!
</Bullet>
</BulletBox>
<Text variant="lead">
Looking for nonprofit status and not a religious or political
organization? Wed love to meet you and chat about working
together. Feel free to apply here or email our team if you have
more questions about fiscal sponsorship!
</Text>
</Section>
position: 'absolute',
translate: '-50% 100%',
boxShadow: '0 -64px 64px #17171d',
}
}}/>
<Footer dark />
</Box>
)
}
<Text variant="lead">
At its core, Hack Club is a nonprofit encouraging students to learn
how to code by building and making cool things.
Hack&nbsp;Club&nbsp;Bank was built out by teenagers at Hack Club and
continues to be a real-world space that teens can hack on every day.
</Text>
</FlexCol>
</Container>
<Box
sx={{
height: '100px',
position: 'relative',
width: '100%',
overflow: 'hidden',
'&::after': {
content: '""',
width: '500%',
height: '100%',
position: 'absolute',
translate: '-50% 100%',
boxShadow: '0 -64px 64px #17171d'
}
}}
/>
<Footer dark />
</Box>
)
}

View file

@ -1,6 +1,16 @@
import { useRouter } from 'next/router'
import { useEffect } from 'react'
import { Box, Button, Card, Container, Divider, Text, Link, Flex, Image } from 'theme-ui'
import {
Box,
Button,
Card,
Container,
Divider,
Text,
Link,
Flex,
Image
} from 'theme-ui'
import ForceTheme from '../../components/force-theme'
import JSConfetti from 'js-confetti'
import Icon from '../../components/icon'
@ -8,12 +18,12 @@ import FlexCol from '../../components/flex-col'
function Option({ icon, label, link }) {
const color =
icon === 'email' ? '#338eda' : icon === 'slack' ? '#a633d6' : '#ec3750';
icon === 'email' ? '#338eda' : icon === 'slack' ? '#a633d6' : '#ec3750'
return (
<Button
variant='outline'
as='a'
variant="outline"
as="a"
href={link}
sx={{
display: 'flex',
@ -25,8 +35,11 @@ function Option({ icon, label, link }) {
}}
>
<Flex sx={{ alignItems: 'center', gap: [0, null, 1], px: 2 }}>
<Icon glyph={icon} sx={{ width: [32, null, 46], height: [32, null, 46] }} />
<Text sx={{ fontSize: [2, null, 3] }}>{ label }</Text>
<Icon
glyph={icon}
sx={{ width: [32, null, 46], height: [32, null, 46] }}
/>
<Text sx={{ fontSize: [2, null, 3] }}>{label}</Text>
</Flex>
</Button>
)
@ -38,15 +51,16 @@ export default function ApplicationSuccess() {
useEffect(() => {
const jsConfetti = new JSConfetti()
jsConfetti.addConfetti({
confettiColors: [ // Hack Club colours!
confettiColors: [
// Hack Club colours!
'#ec3750',
'#ff8c37',
'#f1c40f',
'#33d6a6',
'#5bc0de',
'#338eda',
'#a633d6',
],
'#a633d6'
]
})
}, [router])
@ -65,7 +79,7 @@ export default function ApplicationSuccess() {
<Image
src="/bank/apply/party-orpheus.svg"
alt="Dinosaur partying"
sx={{ width: "40%" }}
sx={{ width: '40%' }}
/>
<FlexCol gap={2}>
<Text variant="title">Thanks for applying!</Text>
@ -81,10 +95,10 @@ export default function ApplicationSuccess() {
</Text>
<Flex
sx={{
flexDirection: ["column", null, "row"],
justifyContent: "space-evenly",
alignItems: "center",
gap: [3, null, 0],
flexDirection: ['column', null, 'row'],
justifyContent: 'space-evenly',
alignItems: 'center',
gap: [3, null, 0]
}}
>
<Option icon="email" label="Mail" link="mailto:bank@hackclub.com">
@ -108,12 +122,12 @@ export default function ApplicationSuccess() {
</FlexCol>
<Button as="a" href="https://bank.hackclub.com">
<Flex sx={{ alignItems: "center", px: [2, null, 3], py: 2 }}>
<Flex sx={{ alignItems: 'center', px: [2, null, 3], py: 2 }}>
<Icon glyph="bank-account" size={36} />
<Text sx={{ fontSize: 3 }}>Head to Bank!</Text>
</Flex>
</Button>
</FlexCol>
</Container>
);
)
}

View file

@ -213,7 +213,7 @@ export default function Donate({ sprig }) {
window.document.getElementById('sprig-donation').scrollIntoView()
}
}, [sprig])
return (
<Box>
<Meta

View file

@ -62,11 +62,11 @@ function Page({
let [slackKey, setSlackKey] = useState(0)
let [key, setKey] = useState(0)
let jsConfetti = useRef();
let jsConfetti = useRef()
useEffect(() => {
jsConfetti.current = new JSConfetti()
window.kc = `In the days of old, when gaming was young \nA mysterious code was found among \nA sequence of buttons, pressed in a row \nIt unlocked something special, we all know \n\nUp, up, down, down, left, right, left, right \nB, A, Start, we all have heard it's plight \nIn the 8-bit days, it was all the rage \nAnd it still lives on, with time, it will never age \n\nKonami Code, it's a legend of days gone by \nIt's a reminder of the classics we still try \nNo matter the game, no matter the system \nThe code will live on, and still be with them \n\nSo the next time you play, take a moment to pause \nAnd remember the code, and the Konami cause \nIt's a part of gaming's history, and a part of our lives \nLet's keep it alive, and let the Konami Code thrive!\n`
window.paper = `Welcome, intrepid hacker! We'd love to have you in our community. Get your invite at hack.af/slack. Under "Why do you want to join the Hack Club Slack?" add a 🦄 and we'll ship you some exclusive stickers! `
}, [])
@ -75,15 +75,16 @@ function Page({
alert('Hey, you typed the Konami Code!')
jsConfetti.current.addConfetti({
confettiColors: [ // Hack Club colours!
confettiColors: [
// Hack Club colours!
'#ec3750',
'#ff8c37',
'#f1c40f',
'#33d6a6',
'#5bc0de',
'#338eda',
'#a633d6',
],
'#a633d6'
]
})
}
@ -664,7 +665,7 @@ function Page({
'flex-start',
'center'
],
gap: '10px',
gap: '10px'
}}
>
<Box sx={{ mb: [3, 0, 0] }}>
@ -673,7 +674,7 @@ function Page({
as="h2"
sx={{
fontSize: ['36px', '48px', '56px'],
maxWidth: '20ch',
maxWidth: '20ch'
}}
>
We build{' '}
@ -719,32 +720,32 @@ function Page({
}
}}
>
<Text
sx={{
fontSize: ['11px', '11px', '14px'],
textAlign: 'left',
lineHeight: '90%',
fontStyle: 'italic',
width: 'fit-content'
}}
>
Live from GitHub
</Text>
{gitHubData
.filter((data) => !data.user.endsWith('[bot]'))
.slice(0, 4)
.map((data, key) => {
return (
<GitHub
type={data.type}
img={data.userImage}
user={data.user}
time={data.time}
message={data.message}
key={key}
/>
)
})}
<Text
sx={{
fontSize: ['11px', '11px', '14px'],
textAlign: 'left',
lineHeight: '90%',
fontStyle: 'italic',
width: 'fit-content'
}}
>
Live from GitHub
</Text>
{gitHubData
.filter(data => !data.user.endsWith('[bot]'))
.slice(0, 4)
.map((data, key) => {
return (
<GitHub
type={data.type}
img={data.userImage}
user={data.user}
time={data.time}
message={data.message}
key={key}
/>
)
})}
</Flex>
)}
</Flex>

View file

@ -38,13 +38,7 @@ import WindyCity from '../../public/donate/6screenshot_2021-10-03_at_3.29.29_pm.
import ZephyrFun from '../../public/donate/0screenshot_2021-10-03_at_3.59.34_pm.png'
import GoldenTrain from '../../public/home/golden-train.png'
import {
BarChart,
Bar,
XAxis,
YAxis,
ResponsiveContainer
} from 'recharts'
import { BarChart, Bar, XAxis, YAxis, ResponsiveContainer } from 'recharts'
const Header = styled(Box)`
background: url('/pattern.svg');
@ -748,7 +742,7 @@ const Philanthropy = ({ posts = [] }) => {
width="20"
height="20"
sx={{ borderRadius: '100%' }}
alt='belle'
alt="belle"
/>
Belle, 17, Malaysia
</Text>
@ -783,7 +777,7 @@ const Philanthropy = ({ posts = [] }) => {
src="/philanthropy/insider-logo.svg"
width={530}
height={150}
alt='insider logo'
alt="insider logo"
/>
</Link>
<Link
@ -795,7 +789,7 @@ const Philanthropy = ({ posts = [] }) => {
src="/philanthropy/wsj-logo.svg"
width={270}
height={100}
alt='wsj logo'
alt="wsj logo"
/>
</Link>
<Link
@ -807,7 +801,7 @@ const Philanthropy = ({ posts = [] }) => {
src="/philanthropy/forbes-logo.svg"
width={500}
height={100}
alt='forbes logo'
alt="forbes logo"
/>
</Link>
<Link
@ -819,7 +813,7 @@ const Philanthropy = ({ posts = [] }) => {
src="/philanthropy/cop.png"
width={750}
height={250}
alt='cop logo'
alt="cop logo"
/>
</Link>
</Grid>
@ -1337,7 +1331,7 @@ const Philanthropy = ({ posts = [] }) => {
src="/philanthropy/christina-s.png"
width={250}
height={100}
alt='christina'
alt="christina"
/>
<Text as="p">Christina Asquith, Co-founder and COO</Text>
</Box>
@ -1348,7 +1342,7 @@ const Philanthropy = ({ posts = [] }) => {
src="/philanthropy/zach-s.png"
width={150}
height={100}
alt='zach'
alt="zach"
/>
<Text as="p">Zach Latta, Founder and Executive Director</Text>
</Box>