mirror of
https://github.com/System-End/site.git
synced 2026-04-19 15:18:18 +00:00
🎨 Run prettier
This commit is contained in:
parent
928a4092f2
commit
fb7e4df565
41 changed files with 1551 additions and 1408 deletions
|
|
@ -36,11 +36,7 @@ const BGImg = ({
|
|||
'~ *': { position: 'relative' }
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
layout="responsive"
|
||||
alt={alt}
|
||||
{...props}
|
||||
/>
|
||||
<Image layout="responsive" alt={alt} {...props} />
|
||||
</Box>
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 you’re a for-profit entity, then Bank is not for you.
|
||||
|
|
@ -92,5 +82,5 @@ export default function BankInfo() {
|
|||
</FlexCol>
|
||||
</FlexCol>
|
||||
</Box>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 don’t 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 don’t 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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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='We’ll 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="We’ll 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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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 Club 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 our finances.
|
||||
</Link>
|
||||
</Text>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ const flashing = keyframes({
|
|||
to: { opacity: 0 }
|
||||
})
|
||||
|
||||
export default function Dot({hideOnMobile}) {
|
||||
export default function Dot({ hideOnMobile }) {
|
||||
return (
|
||||
<Text
|
||||
sx={{
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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' }}
|
||||
|
|
|
|||
|
|
@ -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 building–mentors, 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 building–mentors, 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>
|
||||
|
|
|
|||
|
|
@ -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' }}>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ const CardModel = ({
|
|||
mt: ['-24px', '-32px', '-32px', '-32px'],
|
||||
zIndex: 0
|
||||
}}
|
||||
alt=''
|
||||
alt=""
|
||||
/>
|
||||
)}
|
||||
{children}
|
||||
|
|
|
|||
|
|
@ -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 ? (
|
||||
|
|
|
|||
|
|
@ -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 }}>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ export default function CarouselCards({
|
|||
>
|
||||
<Image
|
||||
src={img}
|
||||
alt='carousel card'
|
||||
alt="carousel card"
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: ['-26px', '-30px', '-35px'],
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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 ?? ''
|
||||
)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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 Club 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 Club 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) 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) 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'>What’s Fiscal Sponsorship?</Link>
|
||||
<Link sx={{fontSize: 2 }} href='#requirements'>Requirements for Fiscal Sponsorship</Link>
|
||||
<Link sx={{fontSize: 2 }} href='#partner'>Partner with Hack Club 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,
|
||||
it’s 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 it’s 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">
|
||||
What’s Fiscal Sponsorship?
|
||||
</Link>
|
||||
<Link sx={{ fontSize: 2 }} href="#requirements">
|
||||
Requirements for Fiscal Sponsorship
|
||||
</Link>
|
||||
<Link sx={{ fontSize: 2 }} href="#partner">
|
||||
Partner with Hack Club 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, it’s 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 it’s 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, there’s 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, there’s 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'>
|
||||
What’s 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
|
||||
don’t 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 you’re 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
|
||||
aren’t just for folding that stubborn fitted
|
||||
sheet or sending an email to hundreds of
|
||||
people in seconds; they’re 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
|
||||
nonprofit’s 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">What’s 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 don’t 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 you’re 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 aren’t just for folding
|
||||
that stubborn fitted sheet or sending an email to hundreds of
|
||||
people in seconds; they’re also for obtaining nonprofit status.
|
||||
</Text>
|
||||
</Section>
|
||||
|
||||
<Section id='partner'>
|
||||
<Text variant='title'>
|
||||
Partner with Hack Club Bank
|
||||
</Text>
|
||||
<Text variant='lead'>
|
||||
While many fiscal sponsors require that their
|
||||
partners relate to their mission in similar ways,
|
||||
at Hack Club Bank, we’ve built our infrastructure
|
||||
to support hundreds of causes in all areas of
|
||||
charitability. Check out some of the resources
|
||||
we’ve 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? We’d 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 Club 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
|
||||
nonprofit’s 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 Club Bank</Text>
|
||||
<Text variant="lead">
|
||||
While many fiscal sponsors require that their partners relate to
|
||||
their mission in similar ways, at Hack Club Bank, we’ve
|
||||
built our infrastructure to support hundreds of causes in all
|
||||
areas of charitability. Check out some of the resources we’ve
|
||||
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? We’d 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 Club 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>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -213,7 +213,7 @@ export default function Donate({ sprig }) {
|
|||
window.document.getElementById('sprig-donation').scrollIntoView()
|
||||
}
|
||||
}, [sprig])
|
||||
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Meta
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue