Move /bank page to v3 (#167)

* initial migration

* port landing and feature components

* Features section

* Add event cards in Testimonials (no styles)

* add everything section

* everything but no styling

* fFix some styles in Everything.js

* Add Start section without the form yet

* Fix testimonials section grid

* Fix styles on Landing

* Styles on Everything section

* Fix testimonials cards

* Fix console error and testimonial cards

* Fix landing and style bugs

* Fix styles

* Add selection color

* Fix Grid on everything section

* Fetch transaction data from api

* Style stats

* Fix landing on mobile

* Fix some bugs and responsiveness

* Fix 🐛 so the form exists now

* Fix bugs & add timeline

* Fix Laptop in Grid

* Switch to Dark Laptop

* Fix Stat Text Size

* Form -> Airtable

* Change to hred to primary, align ModuleDetails card

* Remove dates

* Fix Event card to align items center

* Add mtop margin to transparency button

* Fix Run.js icon styles

* Make fiscal sponsor note more readable

* Add 2 tone colors btwn Start and Everything

* Flashing green dot

* Confusion

* Update components/bank/Timeline.js

Co-authored-by: Sam Poder <39828164+sampoder@users.noreply.github.com>

* Fix remaining style bugs

* small fix

* small fix

* Fix Form

* Fixes

- fix timeline horizontal
- green to hack club theme green
- more horizontal margin on everything section
- attempt to fix testimonial grid

* center 7 (percentage)

* Fix testimonial grid layout

* Fix landing

* Fix horizontal scroll on mobile

* Fix styles on event card

* Decrease font size and padding

* Actually fix it this time

* Fix styles

* Responsiveness and mobile styling

* Slight styles changes

* Fix Margins

* Neutralize

Co-authored-by: Sam Poder <39828164+sampoder@users.noreply.github.com>
This commit is contained in:
Ella 2021-08-21 23:05:22 -07:00 committed by GitHub
parent 8178e2e0a7
commit 89881fd7e8
47 changed files with 1814 additions and 8 deletions

View file

@ -0,0 +1,222 @@
import {
Box,
Container,
Heading,
Text,
Avatar,
Badge,
Link,
Grid
} from 'theme-ui'
import Run from './Run'
import { Fade } from 'react-reveal'
import Icon from '../icon'
export default function Everything() {
return (
<>
<Box
sx={{
pt: 6,
pb: [3, 6],
marginTop: 6,
bg: 'darker'
}}
>
<Container mb={[4, 5]} px={3} sx={{ textAlign: 'center' }}>
<Heading variant="ultratitle" sx={{ color: 'smoke' }}>
Everything youll&nbsp;need.
</Heading>
</Container>
<Container px={[3, null, 5]}>
<List>
{Object.entries({
'Legal entity with 501(c)(3) status': 'briefcase',
'We do your taxes': 'checkmark',
'Collect donations via card, check, or ACH': 'enter',
'Share access with your whole team': 'member-add',
'Bank account backed by Silicon Valley Bank': 'bank-account',
'Negotiated nonprofit rates with Stripe': 'enter',
'Instant invoice sending': 'transactions',
'Real-time dashboard of finances': 'analytics',
'Transaction data export': 'download',
'Record shared notes on transactions': 'docs',
'24-hour response support': 'clock',
'Reimbursement process': 'enter'
}).map(([item, icon = 'enter']) => (
<ListItem key={icon} icon={icon}>
{item}
</ListItem>
))}
<ListItem
start={
<Avatar
src="/team/mel.png"
size={32}
alt="Mels avatar"
mr={2}
/>
}
>
Amazing support team
</ListItem>
{Object.entries({
'Physical check sending & voiding': '',
'Online ACH transfers': '',
'Generate attendee legal waivers': '',
'Instant Google Workspace & email addresses': '',
'Virtual debit cards (with Apple Pay)': '',
'Debit card transaction paper trail': '',
'Self-serve, no-contract signup': '',
'Transparency Mode (optional)': '',
'Online, embeddable donation form': ''
}).map(([item, date]) => (
<ListItem
key={item}
icon={
item.startsWith('Instant') || item.includes('signup')
? 'bolt'
: item.includes('card')
? 'card'
: item.includes('Transparency')
? 'explore'
: item.includes('form')
? 'link'
: item.includes('Physical')
? 'email'
: 'enter'
}
>
{item}
</ListItem>
))}
</List>
</Container>
<Run />
<Container px={3} mt={4}>
<Box
sx={{
display: 'flex',
justifyContent: 'center',
flexWrap: 'wrap',
alignItems: 'center',
textAlign: 'center'
}}
>
<Text sx={{ fontSize: 32, mr: 2 }}>You pay just</Text>
<Percentage>7</Percentage>
<Text sx={{ fontSize: 32, ml: 2 }}>
of revenue. No upfront costs.
</Text>
</Box>
<Container
variant="copy"
sx={{
textAlign: 'center',
fontSize: 18,
lineHeight: '1.25',
letterSpacing: '-.03ch',
marginTop: 4,
marginBottom: 5
}}
>
<Container variant="narrow">
<Text sx={{ color: 'muted', lineHeight: 1.375 }}>
Hack Club Bank is a{' '}
<Link
color="primary"
href="https://en.wikipedia.org/wiki/Fiscal_sponsorship"
hoverline
>
fiscal sponsor
</Link>{' '}
for your&nbsp;project. Industry standard varies between 7-14%
of&nbsp;revenue.
</Text>
</Container>
</Container>
</Container>
</Box>
</>
)
}
function List({ children }) {
return (
<Container>
<ol
style={{
paddingLeft: 0,
listStyle: 'none'
}}
>
<Grid gap={2} columns={[1, 1, '1fr 1fr']}>
{children}
</Grid>
</ol>
</Container>
)
}
function ListItem({ icon = 'enter', start, ...props }) {
return (
<Fade left>
<li
style={{
lineHeight: 1.25,
breakInside: 'avoid',
display: 'flex',
alignItems: 'center',
paddingBottom: 4,
marginBottom: 8
}}
>
{start || (
<Icon
glyph={icon}
sx={{ color: 'muted', marginRight: 2 }}
size={32}
mr={2}
/>
)}
<Text sx={{ fontSize: 24, color: 'smoke' }} {...props} />
</li>
</Fade>
)
}
function Percentage({ children }) {
return (
<Box
sx={{
display: 'flex',
bg: 'slate',
color: 'green',
width: [64, 128],
height: [64, 128],
borderRadius: 'circle',
fontWeight: 'bold',
justifyContent: 'center',
boxShadow: '0 8px 32px rgba(255, 255, 255, 0.125)',
fontSize: [48, 96],
'&:after': {
content: '"%"',
mt: [3, 4],
fontSize: [24, 40],
fontWeight: 'normal',
marginRight: -3,
marginLeft: [null, 2],
color: 'muted'
}
}}
>
{children}
</Box>
)
}
const recent = dt => {
const past = new Date()
past.setMonth(past.getMonth() - 2)
return new Date(dt) > past
}

211
components/bank/Features.js Normal file
View file

@ -0,0 +1,211 @@
import { Box, Heading, Link, Text, Container, Grid } from 'theme-ui'
import Icon from '../icon'
export default function Features() {
return (
<Box sx={{ py: 6 }}>
<Container>
<Text variant="heading" sx={{ fontSize: 50 }}>
A full-stack toolkit for organizing anything.
</Text>
<br />
<br />
<Text sx={{ color: 'muted', maxWidth: '48', fontSize: 28 }}>
Invoice sponsors, issue debit cards to your team, and view history.
<br />
Ongoing support so you can focus on organizing, not the paperwork.
</Text>
<br />
<br />
</Container>
<Container>
<Grid gap={4} columns={[1, null, 3]}>
<Box>
<Module
icon="bank-account"
name="Bank account"
body="Backed by Silicon Valley Bank with a custom, beautiful dashboard."
/>
<ModuleDetails>
<Document
name="501(c)(3) nonprofit status"
cost="Become part of Hack Club's legal entity, getting the benefits of our tax status."
/>
<Document
name="Tax filings (990, end-of-year)"
cost="We handle all filings with the IRS, so you can focus on your event, not hiring CPAs."
/>
</ModuleDetails>
</Box>
<Laptop
href="https://bank.hackclub.com/hackpenn"
title="See Hack Pennsylvanias finances in public"
sx={{
gridColumn: [null, null, 'span 2'],
gridRow: [null, null, 'span 2']
}}
/>
<Module
icon="card"
name="Debit cards"
body={
<>
Issue physical debit cards to all your teammates, backed by{' '}
<Link
href="https://stripe.com/issuing"
color="smoke"
hoverline
target="_blank"
>
Stripe
</Link>
.
</>
}
/>
<Module
icon="analytics"
name="Balance &amp; history"
body="Check real-time account balance + transaction history online anytime."
/>
<Module
icon="payment"
name="Built-in invoicing"
body="Accept sponsor payments with low negotiated rates from Stripe."
/>
<Module
icon="docs"
name="Pre-written forms"
body="Download liability + photo forms custom written by expert lawyers."
/>
<Module
icon="explore"
name="Transparency Mode"
body="If youd like, show your finances on public pages for full transparency."
/>
<Module
icon="google"
name="Google Workspace"
body="Get instant, free accounts for your team (like joy@hackpenn.com)."
/>
<Module
icon="support"
name="Support anytime"
body="Well never leave you hanging with best-effort 24hr response time."
/>
</Grid>
</Container>
<Container
variant="copy"
sx={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
textAlign: 'center'
}}
>
<Text
variant="lead"
sx={{
color: 'muted',
fontSize: 3,
marginTop: [4, 5]
}}
>
Have more questions? <br /> Check out the{' '}
<Link
href="https://bank.hackclub.com/faq"
target="_blank"
rel="noreferrer"
hoverline
>
Hack Club Bank FAQ
</Link>
.
</Text>
</Container>
</Box>
)
}
function Module({ icon, name, body }) {
return (
<Box sx={{ display: 'flex', alignItems: 'start' }}>
<Icon
size={48}
glyph={icon}
sx={{ flexShrink: 0, marginRight: 3, color: 'primary' }}
/>
<Box>
<Heading sx={{ color: 'snow', lineHeight: '1.5' }}>{name}</Heading>
<Text
sx={{ color: 'muted', lineHeight: '1.375', fontSize: 17 }}
children={body}
/>
</Box>
</Box>
)
}
function ModuleDetails({ children }) {
return (
<Box
sx={{
bg: '#252429',
color: 'smoke',
mt: 4,
ml: 0,
py: 3,
px: 2,
boxShadow: '0 8px 32px rgba(0, 0, 0, 0.0625)',
borderRadius: 'default'
}}
>
{children}
</Box>
)
}
function Document({ name, cost }) {
return (
<Box sx={{ display: 'flex' }}>
<Icon
size={28}
mr={1}
glyph="payment"
sx={{ flexShrink: 0, color: 'green' }}
/>
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
<Text fontSize={2} children={name} />
{cost && (
<Text
fontSize={1}
color="muted"
style={{ lineHeight: '1.375' }}
children={cost}
/>
)}
</Box>
</Box>
)
}
function Laptop({ href, title, sx }) {
return (
<Link href={href} title={title} sx={sx}>
<Box
sx={{
display: 'block',
width: '100%',
height: '100%',
minHeight: '16rem',
backgroundSize: 'auto 115%',
backgroundImage: "url('/bank/laptop-dark.png')",
backgroundPosition: 'center top',
backgroundRepeat: 'no-repeat'
}}
></Box>
</Link>
)
}

181
components/bank/Landing.js Normal file
View file

@ -0,0 +1,181 @@
import {
Box,
Button,
Heading,
Link,
Text,
Flex,
Container,
Badge
} from 'theme-ui'
import Fade from 'react-reveal/Fade'
import ScrollHint from './ScrollHint'
export default function Landing() {
return (
<>
<Slide>
<Vignette />
<Box
sx={{
position: 'absolute',
flexDirection: 'column',
justifyContent: 'center',
bottom: 5,
mx: 'auto',
width: '100%'
}}
>
<Box
sx={{
zIndex: '100',
paddingTop: '96px'
}}
>
<Fade duration={625} bottom>
<Container
variant="container"
sx={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
textAlign: 'center'
}}
>
<Heading
variant="ultratitle"
sx={{
marginBottom: 4,
// lineHeight: 0.875,
textShadow: '0 0 16px rgba(0, 0, 0, 1)',
letterSpacing: '-0.02em',
'@media screen and (max-height: 600px)': {
lineHeight: 0.875
},
'@media screen and (min-height: 610px)': {
lineHeight: 1.125
}
}}
>
The bank for hackers to <Underline>make ideas real</Underline>
.
</Heading>
<Container variant="copy">
<Text
variant="lead"
sx={{
textShadow: '0 3px 6px rgba(0, 0, 0, 0.5)',
'@media screen and (max-height: 600px)': {
lineHeight: 1
}
}}
>
The team behind{' '}
<Link
href="https://hackaz.io/?ref=bank"
target="_blank"
color="inherit"
bold
hoverline
>
Hack&nbsp;Arizona
</Link>{' '}
is one of 100+ teams who uses{' '}
<strong>Hack&nbsp;Club&nbsp;Bank</strong> to run world-class
hackathons.
</Text>
</Container>
</Container>
</Fade>
</Box>
<br />
<Box
sx={{
display: 'flex',
justifyContent: 'center',
marginBottom: 3
}}
>
<Button
variant="outlineLg"
as="a"
href="#apply"
style={{ zIndex: '100' }}
>
Apply Now
</Button>
</Box>
<ScrollHint />
</Box>
<Box sx={{ position: 'absolute', bottom: 3, right: 2 }}>
<Badge
variant="pill"
sx={{
zIndex: '1',
bg: 'muted',
color: 'steel',
fontWeight: 'normal'
}}
>
Tuscon, AZ
</Badge>
</Box>
</Slide>
</>
)
}
function Underline({ children }) {
return (
<span
style={{
backgroundImage: 'url(/underline.svg)',
backgroundRepeat: 'no-repeat',
backgroundSize: '100% 1rem',
backgroundPosition: 'bottom center'
}}
>
{children}
</span>
)
}
function Slide({ children }) {
return (
<Box
style={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'end',
width: '100vw',
background: 'url("/bank/bg.jpg")',
backgroundColor: '#000000',
boxShadow: 'inset 0 0 4rem 1rem rgba(0, 0, 0, 0.5)',
backgroundPosition: 'center',
backgroundSize: 'cover',
width: '100%',
minHeight: '100vh',
position: 'relative'
}}
>
{children}
</Box>
)
}
function Vignette() {
return (
<Box
style={{
backgroundImage:
'linear-gradient(to bottom,rgba(0, 0, 0, 0),rgba(0, 0, 0, 0.25) 25%,rgba(0, 0, 0, 0.625) 50%, rgba(0, 0, 0, 0.75) 100%)',
height: '100vh',
left: '0',
right: '0',
position: 'absolute',
zIndex: '0'
}}
></Box>
)
}

80
components/bank/Run.js Normal file
View file

@ -0,0 +1,80 @@
import { Box, Heading, Text, Container } from 'theme-ui'
import { Fade } from 'react-reveal'
import Icon from '../icon'
export default function Run() {
return (
<>
<Container
variant="container"
sx={{
display: 'flex',
flexDirection: ['column', null, 'row'],
alignItems: 'center',
width: ['100%', '100%', '100%', '85%'],
bg: '#252429',
color: 'smoke',
px: 4,
borderRadius: 'default',
position: 'relative'
}}
>
<Container maxWidth={28} sx={{ mx: 0, py: 4 }}>
<Text variant="heading" sx={{ fontSize: 48 }}>
Bank doesnt stop at closing ceremony.
</Text>
<br />
<Text variant="lead" sx={{ color: 'muted', fontSize: 28 }}>
Setting up a bank account is just the start. Hack Club Bank helps
you handle ongoing obligations while youre organizing.
</Text>
</Container>
<List>
<ListItem
icon="docs"
body="We handle ongoing tax filings including end-of-year taxes"
/>
<ListItem
icon="payment-docs"
body="Our accountants regularly reconcile your books"
/>
<ListItem
icon="history"
body="You always have access to historical financial data"
/>
</List>
</Container>
</>
)
}
function List({ children }) {
return (
<Box>
<ol style={{ listStyle: 'none', paddingLeft: 0 }}>{children}</ol>
</Box>
)
}
function ListItem({ icon, body }) {
return (
<Fade bottom>
<li
style={{
lineHeight: 1.25,
display: 'flex',
alignItems: 'center',
pl: 0
}}
>
<Icon
glyph={icon}
size={45}
sx={{ color: 'primary', flexShrink: 'none', flexShrink: 0, mr: 2 }}
/>
<Text fontSize={[32, 48]} variant="lead" children={body} />
</li>
</Fade>
)
}

View file

@ -0,0 +1,24 @@
import { Box } from 'theme-ui'
export default function ScrollHint() {
return (
<Box
sx={{
display: 'block',
position: 'relative',
height: '32px',
width: '32px',
margin: '0 auto',
borderBottom: '2px solid #fff',
borderRight: '2px solid #fff',
transform: 'rotate(45deg)',
opacity: '.6',
cursor: 'pointer',
transition: 'transform .3s',
'&:hover': { transform: 'translateY(4px) rotate(45deg)' },
'&:active': { transform: ' translateY(6px) rotate(45deg)' }
}}
></Box>
)
}

90
components/bank/Signup.js Normal file
View file

@ -0,0 +1,90 @@
import { Text, Heading, Box, Container, Input, Label, Button } from 'theme-ui'
import { useState } from 'react'
function Base({ children, action, target, method }) {
return (
<Box
as="form"
sx={{ display: 'grid', gridTemplateColumns: '1fr' }}
action={action}
target={target}
method={method}
>
{children}
</Box>
)
}
function Field({
placeholder,
label,
name,
type,
value,
onChange,
}) {
return (
<Box sx={{ my: 2 }}>
<Label htmlFor={name} sx={{ color: 'muted', fontSize: 18 }}>
{label}
</Label>
<Input
id={name}
placeholder={placeholder}
name={name}
type={type}
sx={{ bg: 'dark' }}
onChange={onChange}
value={value}
required
/>
</Box>
)
}
export default function Signup() {
const [values, setValues] = useState({})
return (
<Base
method="get"
target="_blank"
action="https://airtable.com/shrW33gWaPnSDBhYj"
>
<Field
label="Project name"
name="prefill_Event Name"
placeholder="Windy City Hacks"
value={values.name}
onChange={e => setValues({...values, name: e.target.value})}
/>
<Box sx={{ display: 'flex', flexDirection: 'row', gap: 2 }}>
<Field
label="First name"
name="prefill_First Name"
placeholder="Fiona"
value={values.first_name}
onChange={e => setValues({...values, first_name: e.target.value})}
/>
<Field
label="Last name"
name="prefill_Last Name"
placeholder="Hackworth"
value={values.last_name}
onChange={e => setValues({...values, last_name: e.target.value})}
/>
</Box>
<Field
label="Email address"
name="prefill_Email Address"
placeholder="fiona@hackclub.com"
type="email"
value={values.email}
onChange={e => setValues({...values, email: e.target.value})}
/>
<Button sx={{ bg: 'blue', mt: [2, 3], py: 3 }} type="submit">{`Finish ${
10 - Object.values(values).filter(n => n !== '').length
} fields to apply`}</Button>
</Base>
)
}

103
components/bank/Start.js Normal file
View file

@ -0,0 +1,103 @@
import { Box, Container, Link, Text, Heading, Card, Grid } from 'theme-ui'
import { Fade } from 'react-reveal'
import Signup from './Signup'
import Timeline from './Timeline'
import Stats from './Stats'
export default function Start() {
return (
<>
<Box
as="section"
id="apply"
sx={{
// bg: 'darker',
pt: 6,
zIndex: -999
}}
>
<Container
px={3}
mb={[4, 5]}
sx={{
display: 'flex',
flexDirection: 'column',
textAlign: 'center',
justifyContent: 'center'
}}
>
<Heading variant="ultratitle" color="white" mb={2}>
Sign up for Hack&nbsp;Club Bank.
</Heading>
<Container variant="narrow" sx={{ color: 'muted' }}>
<Text variant="lead">
Open to all US-based registered Hack Clubs, hackathons, and your
next amazing project.
</Text>
</Container>
</Container>
<Timeline />
<Grid mt={[4, 5]} mb={[3, 4]} px={3} columns={[1, 1, '1fr 1fr']}>
<Fade bottom>
<Card
variant="primary"
sx={{
backgroundColor: 'darkless',
color: 'snow',
width: ['100%', null, 356],
float: [null, null, 'right']
}}
>
<Text variant="heading" sx={{ fontSize: 24, lineHeight: 2 }}>
Your project
</Text>
<Signup />
</Card>
</Fade>
<Container variant="narrow" sx={{ pr: [null, null, 2, 6], m: 0 }}>
<Stats
color="smoke"
labelColor="muted"
fontSize={[7, 8]}
my={[3, 4]}
px={0}
width="auto"
align="left"
/>
<Text
sx={{
fontSize: 18,
color: 'muted'
}}
>
Starting in February 2020, we started running Hack Club HQ on Bank
(&amp; we dont count our numbers in these stats). &nbsp;
<Link
href="https://bank.hackclub.com/hq"
color="primary"
hoverline
>
See our finances here.
</Link>
</Text>
</Container>
</Grid>
<Container
variant="copy"
sx={{
display: 'flex',
justifyContent: 'center',
textAlign: 'center',
paddingBottom: 6
}}
>
<Text sx={{ fontSize: 18, color: 'muted', mx: [3, null, 6] }}>
Hack Club does not directly provide banking services. Banking
services provided by Silicon Valley Bank, an FDIC-certified
institution.
</Text>
</Container>
</Box>
</>
)
}

93
components/bank/Stats.js Normal file
View file

@ -0,0 +1,93 @@
import { useState, useEffect } from 'react'
import { Text, Box, Container } from 'theme-ui'
import Stat from '../stat'
import api from '../../lib/api'
import { timeSince } from '../../lib/helpers'
import { keyframes } from '@emotion/react'
const renderMoney = amount =>
Math.floor(amount / 100)
.toLocaleString('en-US', {
style: 'currency',
currency: 'USD'
})
.replace('.00', '')
const flashing = keyframes({
from: { opacity: 0 },
'50%': { opacity: 1 },
to: { opacity: 0 }
})
function Dot() {
return (
<Text
sx={{
bg: 'green',
color: 'white',
borderRadius: 'circle',
display: 'inline-block',
lineHeight: 0,
width: '.4em',
height: '.4em',
marginRight: '.4em',
marginBottom: '.12em',
animationName: `${flashing}`,
animationDuration: '3s',
animationTimingFunction: 'ease-in-out',
animationIterationCount: 'infinite'
// animation: `3s ${flashing} easin-in-out infinite`
}}
/>
)
}
export default props => {
const [volume, setVolume] = useState(100 * 1000 * 1000) // 1MM default
const [raised, setRaised] = useState(100 * 1000 * 500) // half million default
const [lastUpdated, setLastUpdated] = useState(Date.now()) // now default
useEffect(() => {
loadStats()
})
const loadStats = () => {
api.get('https://bank.hackclub.com/stats').then(stats => {
setVolume(renderMoney(stats.transactions_volume))
setRaised(renderMoney(stats.raised))
setLastUpdated(stats.last_transaction_date * 1000)
})
}
return (
<div>
{/* styled-components has a rendering bug that applies classes
to incorrect components in this particular tree, but I didn't
have time to upgrade styled-components or fix root cause.
This <div> soup seemed to remove the symptoms in the UI for now.
- @thesephist */}
<div>
<Text
variant="lead"
fontSize={[2, 3]}
color={props.labelColor}
mt={[2, 4]}
mb={[2, 3]}
>
<span></span>
<Dot />
As of {timeSince(lastUpdated, false, true)}...
</Text>
</div>
<div>
<Stat {...props} value={raised} label="raised on Hack Club Bank" />
<Stat
{...props}
fontSize={[3, 4, 5]}
value={volume}
label="total amount transacted"
/>
</div>
</div>
)
}

View file

@ -0,0 +1,273 @@
import {
Box,
Avatar,
Button,
Image,
Text,
Heading,
Container,
Card,
Grid,
Link
} from 'theme-ui'
import { Slide } from 'react-reveal'
import Stat from '../stat'
import kebabCase from 'lodash/kebabCase'
const events = [
{
transparency: 'hackpenn',
name: 'Hack Pennsylvania',
location: 'State College, PA',
organizer: 'Joy Liu',
budget: 15,
attendees: 115,
testimonial:
'For me, Hack Club Bank unlocked organizing hackathons. Even after as a club leader, raising money seemed insurmountable. Bank directly enabled organizing events in my community with event bank accounts & a supportive community. I couldnt recommend it more highly.'
},
{
name: 'Teenhacks LI',
location: 'Long Island, NY',
organizer: 'Wesley Pergament',
budget: 35,
attendees: 300,
testimonial:
'For our hackathon, Hack Club Bank has given us the tools to make sure our organization is professional with sponsors. Bank and their team have created an easily manageable resource to make sure any event is run successfully. We would highly recommend any organization be a part of the Hack Club ecosystem.'
},
{
name: 'Los Altos Hacks',
location: 'Sunnyvale, CA',
organizer: 'Jamsheed Mistri',
budget: 30,
attendees: 350,
testimonial:
'Hack Club Bank has made it incredibly easy to handle our events funds and has provided countless tools to increase our productivity. With Bank, I can focus on making the event the best it can be.'
},
{
name: 'SLO Hacks',
location: 'San Luis Obispo, CA',
organizer: 'Selynna Sun',
budget: 50,
attendees: 300,
testimonial:
'Hack Club Bank significantly improved the fiscal sponsorship process for SLO Hacks, through a beautifully-designed platform full of useful features, in addition to a responsive team addressed our questions as quickly as possible.'
},
{
name: 'MAHacks',
location: 'Boston, MA',
organizer: 'Kat Huang',
budget: 1.5,
attendees: 70,
testimonial:
'Hack Club Bank removed the barriers to starting fundraising for MAHacks. In mere days, vs months of nonprofit paperwork, Bank enabled my team to invoice sponsors professionally and manage our finances on a clear, up-to-date dashboard. I highly recommend using Bank & joining the Hack Club community.'
},
{
transparency: 'dv-hacks',
name: 'DV Hacks',
location: 'Santa Clara, CA',
organizer: 'Khushi Wadhwa',
budget: 12,
attendees: 150,
testimonial:
'Hack Club Bank is an essential platform for any hackathon organizer! It made us look both professional and credible in front of our sponsors and it relieved us of legal/financial burdens. Hack Club Bank was there for us every step of the way and for a first-year hackathon, that support was priceless.'
}
]
export default function Testimonials() {
return (
<>
<Box>
<Container
variant="copy"
sx={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
textAlign: 'center'
}}
>
<Heading variant="title">
The best events across the country run on Bank.
</Heading>
<Text variant="lead" color="muted">
Everywhere from Philadelphia to Phoenix to Portland,
Hack&nbsp;Club&nbsp;Bank powers events of all sizes.
</Text>
</Container>
<Container
sx={{
color: 'smoke',
px: [null, null, 4],
mx: 'auto',
mt: 2,
borderRadius: 0,
position: 'relative'
}}
>
<Grid
gap={4}
sx={{
// gridTemplateColumns: 'repeat(auto-fill, minmax(18em, 1fr))'
gridTemplateColumns: ['100%', null, null, '1fr 1fr']
}}
>
{events.map(event => {
const id = kebabCase(event.name)
return (
<Event {...event} img={`/bank/events/${id}.jpg`} key={id} />
)
})}
</Grid>
</Container>
</Box>
</>
)
}
function Event({
img,
name,
location,
budget,
attendees,
organizer,
testimonial,
transparency
}) {
return (
<Slide bottom>
<Box
sx={{
backgroundColor: 'darkless',
color: 'smoke',
borderRadius: 'extra',
mx: 'auto'
}}
>
<Container sx={{ padding: 0, margin: 0 }}>
<Image
src={img}
alt={location}
sx={{
maxHeight: '20rem',
objectFit: 'cover',
width: '100%',
borderRadius: 'default',
mb: -3
}}
/>
<Box p={[3, null, 4]}>
<Box
sx={{
display: 'flex',
flexDirection: ['column', 'row', 'row'],
alignItems: ['baseline', 'center'],
justifyContent: 'space-between',
marginBottom: -3
}}
>
<Text
color="white"
variant="headline"
sx={{ fontSize: [48, null, 30], letterSpacing: -0.1 }}
children={name}
/>
<Box
sx={{
display: 'flex',
flexDirection: 'row',
justifyContent: 'flex-start',
my: 0,
ml: -2,
pl: 0
}}
>
<DetailStat value={attendees} label="attendees" />
<DetailStat value={`$${budget}k`} label="budget" />
</Box>
</Box>
<br />
<Quote>"{testimonial}"</Quote>
<Box
sx={{
display: 'flex',
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'space-between',
marginTop: ['0px', 3]
}}
>
<Box
sx={{
display: 'flex',
alignItems: 'center',
mt: ['16px', '0px']
}}
>
<Avatar
src={`/hackers/${organizer.split(' ')[0].toLowerCase()}.jpg`}
size={48}
mr={2}
/>
<Text
color="white"
sx={{
fontSize: 19,
display: 'flex',
flexDirection: 'column'
}}
>
<Text sx={{ fontWeight: 'bold', lineHeight: 1.125 }}>
{organizer}
</Text>
<Text>Lead Organizer</Text>
</Text>
</Box>
{transparency && (
<Link
href={`https://bank.hackclub.com/${transparency}`}
target="_blank"
rel="noreferrer"
sx={{mt: ['16px', '0px']}}
>
<Button
mt={[null, null, 4, 0]}
ml={[0, 'auto']}
sx={{ textTransform: 'none' }}
variant="primary"
>
See Finances
</Button>
</Link>
)}
</Box>
</Box>
</Container>
</Box>
</Slide>
)
}
function DetailStat({ value, label }) {
return (
<Box sx={{ px: 0, mb: [3, 0], ml: [-1, 0], mx: 3 }}>
<Stat value={value} label={label} sm />
</Box>
)
}
function Quote({ children }) {
return (
<Text
sx={{
fontSize: 2,
color: 'muted',
textIndent: '-.375em',
lineHeight: 'caption',
fontSize: 18
}}
>
{children}
</Text>
)
}

138
components/bank/Timeline.js Normal file
View file

@ -0,0 +1,138 @@
import { Box, Flex, Avatar, Container, Text, Badge } from 'theme-ui'
import { Slide } from 'react-reveal'
import Icon from '../icon'
function Timeline({ children }) {
return (
<Flex
sx={{ flexDirection: ['column', null, 'row'], justifyContent: 'center' }}
>
{children}
</Flex>
)
}
function TimelineStep({ children }) {
return (
<Flex
sx={{
marginX: [4, null, null],
paddingX: [null, null, 3, 4],
paddingY: [4, null, 0],
flexDirection: ['row', null, 'column'],
alignItems: 'center',
'&:before': {
content: '""',
background: '#3c4858',
height: ['420px', null, '4px'],
width: ['4px', null, '50%'],
marginLeft: [26, null, 0],
marginTop: [null, null, '34px'],
position: 'absolute',
zIndex: -1
},
'&:first-of-type:before': {
top: [0, null, 'auto'],
width: [0, null, 0],
left: [0, null, 0]
},
'&:last-of-type:before': {
bottom: [0, null, 'auto'],
left: [null, null, 0],
width: [null, null, 0]
}
}}
>
{children}
</Flex>
)
}
function Circle({ children }) {
return (
<Box
style={{
padding: 12,
background: 'red',
color: 'white',
backgroundImage:
'radial-gradient(ellipse farthest-corner at top left, #ff8c37, #ec3750)',
borderRadius: '100%',
display: 'inline-block',
lineHeight: 0,
position: 'relative',
zIndex: 999
}}
>
{children}
</Box>
)
}
function Step({ icon, name, duration }) {
return (
<TimelineStep pb={4}>
<Slide left>
<Circle mr={[3, null, 0]} mb={[null, null, 4]}>
<Icon glyph={icon} size={48} />
</Circle>
<Container
sx={{
marginTop: 3,
display: 'flex',
justifyContent: ['left', null, 'center'],
flexDirection: 'column',
textAlign: ['left', null, 'center']
}}
>
<Badge
variant="pill"
sx={{
bg: 'muted',
color: 'darker',
fontWeight: 'normal',
textTransform: 'uppercase',
width: 64,
fontSize: 18,
px: 2,
mx: [null, null, 'auto']
}}
children={duration}
/>
<Text
sx={{ color: 'white', fontSize: 24, maxWidth: [200, null, 300] }}
children={name}
/>
</Container>
</Slide>
</TimelineStep>
)
}
export default function RealTimeline() {
return (
<Timeline px={3}>
<Step
icon="send"
name="Sign up, explore, order debit cards"
duration="Day 1"
/>
<Step
icon="welcome"
name="Intro meeting with Hack Club Bank"
duration="Day 3"
/>
<Step
icon="post"
name="Sign the contract &amp; unlock full access"
duration="Day 4"
/>
<Step
icon="card"
name="Receive debit cards in the mail"
duration="Day 10"
mb={0}
/>
</Timeline>
)
}

View file

@ -11,6 +11,7 @@ const Stat = ({
reversed = false,
half = false,
lg = false,
sm = false,
...props
}) => (
<Flex
@ -33,7 +34,7 @@ const Stat = ({
as="span"
sx={{
color,
fontSize: lg ? [5, 6, 7] : [4, 5, 6],
fontSize: lg ? [5, 6, 7] : sm ? [3, 4] : [4, 5, 6],
fontWeight: 'heading',
letterSpacing: 'title',
my: 0
@ -44,7 +45,7 @@ const Stat = ({
<Text
as="sup"
sx={{
fontSize: lg ? [2, 3] : [1, 2],
fontSize: lg ? [2, 3] : sm ? [1, 1] : [1, 2],
color: color === 'text' ? 'secondary' : color,
ml: [null, unit === '%' ? 1 : null],
mr: [null, 1],

78
lib/api.js Normal file
View file

@ -0,0 +1,78 @@
import storage from './storage'
export const url = 'https://api.hackclub.com/'
const methods = ['GET', 'PUT', 'POST', 'PATCH', 'DELETE']
const generateMethod =
method =>
(path, options = {}, fetchOptions = {}) => {
let filteredOptions = {}
const authToken = storage.get('authToken')
if (authToken) {
options.authToken = authToken
}
for (let [key, value] of Object.entries(options)) {
switch (key) {
case 'authToken':
filteredOptions.headers = filteredOptions.headers || {}
filteredOptions.headers['Authorization'] = `Bearer ${value}`
break
case 'data':
if (value instanceof FormData) {
filteredOptions.body = value
} else {
filteredOptions.body = JSON.stringify(value)
filteredOptions.headers = filteredOptions.headers || {}
filteredOptions.headers['Content-Type'] = 'application/json'
}
break
default:
filteredOptions[key] = value
break
}
}
if (fetchOptions.noAuth) {
if (filteredOptions.headers && filteredOptions.headers['Authorization']) {
delete filteredOptions.headers['Authorization']
}
}
const foreignUrl = path.startsWith('http')
const urlPath = foreignUrl ? path : url + path
return fetch(urlPath, { method, ...filteredOptions })
.then(res => {
if (res.ok) {
const contentType = res.headers.get('content-type')
if (contentType && contentType.indexOf('application/json') !== -1) {
return res.json()
} else {
return res.text()
}
} else {
if (res.status === 422) {
return res.json().then(json => {
// eslint-disable-next-line
throw { ...res, errors: json.errors }
})
} else {
throw res
}
}
})
.catch(err => {
throw err
})
}
let api = {}
methods.forEach(method => {
api[method.toLowerCase()] = generateMethod(method)
})
api.currentUser = () => api.get(`v1/users/current`)
export default api

135
lib/helpers.js Normal file
View file

@ -0,0 +1,135 @@
export const dt = d => new Date(d).toLocaleDateString()
const year = new Date().getFullYear()
export const tinyDt = d => dt(d).replace(`/${year}`, '').replace(`${year}-`, '')
// based on https://github.com/withspectrum/spectrum/blob/alpha/src/helpers/utils.js#L146
export const timeSince = (
previous,
absoluteDuration = false,
longForm = false,
current = new Date()
) => {
const msPerMinute = 60 * 1000
const msPerHour = msPerMinute * 60
const msPerDay = msPerHour * 24
const msPerWeek = msPerDay * 7
const msPerMonth = msPerDay * 30 * 2
const msPerYear = msPerDay * 365
const elapsed = new Date(current) - new Date(previous)
let humanizedTime
if (elapsed < msPerMinute) {
humanizedTime = '< 1m'
} else if (elapsed < msPerHour) {
const now = Math.round(elapsed / msPerMinute)
humanizedTime = longForm ? `${now} minute${now > 1 ? 's' : ''}` : `${now}m`
} else if (elapsed < msPerDay) {
const now = Math.round(elapsed / msPerHour)
humanizedTime = longForm ? `${now} hour${now > 1 ? 's' : ''}` : `${now}h`
} else if (elapsed < msPerWeek) {
const now = Math.round(elapsed / msPerDay)
humanizedTime = longForm ? `${now} day${now > 1 ? 's' : ''}` : `${now}d`
} else if (elapsed < msPerMonth) {
const now = Math.round(elapsed / msPerWeek)
humanizedTime = longForm ? `${now} week${now > 1 ? 's' : ''}` : `${now}w`
} else if (elapsed < msPerYear) {
const now = Math.round(elapsed / msPerMonth)
humanizedTime = longForm ? `${now} month${now > 1 ? 's' : ''}` : `${now}mo`
} else {
const now = Math.round(elapsed / msPerYear)
humanizedTime = longForm ? `${now} year${now > 1 ? 's' : ''}` : `${now}y`
}
if (absoluteDuration) {
return humanizedTime
} else {
return elapsed > 0 ? `${humanizedTime} ago` : `in ${humanizedTime}`
}
}
// NOTE(@lachlanjc): I know this is bad, Im trying to get it out the door okay???
export const timeTo = (time, current = new Date(), longForm = true) => {
const msPerMinute = 60 * 1000
const msPerHour = msPerMinute * 60
const msPerDay = msPerHour * 64 // getting close to a day
const msPerYear = msPerDay * 365
const elapsed = new Date(time) - new Date(current)
let humanizedTime
if (elapsed < msPerMinute) {
humanizedTime = '< 1m'
} else if (elapsed < msPerHour) {
const now = Math.round(elapsed / msPerMinute)
humanizedTime = longForm ? `${now} more minutes` : `${now}m`
} else if (elapsed < msPerDay) {
const now = Math.round(elapsed / msPerHour)
humanizedTime = longForm ? `${now} more hours` : `${now}h`
} else if (elapsed < msPerYear) {
const now = Math.round(elapsed / msPerDay)
humanizedTime = longForm ? `${now} days` : `${now}d`
} else {
const now = Math.round(elapsed / msPerYear)
humanizedTime = longForm ? `${now} years` : `${now}y`
}
return humanizedTime
}
function formatChunk(type, date) {
const days = [
'Sunday',
'Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday',
'Saturday'
]
const months = [
'January',
'Febuary',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December'
]
switch (type) {
case 'dddd':
return days[date.getDay()]
case 'ddd':
return formatChunk('dddd', date).slice(0, 3)
case 'dd':
return ('00' + formatChunk('d', date)).slice(-2)
case 'd':
return date.getDate()
case 'mmmm':
return months[date.getMonth()]
case 'mmm':
return formatChunk('mmmm', date).slice(0, 3)
case 'mm':
return ('00' + formatChunk('m', date)).slice(-2)
case 'm':
return (date.getMonth() + 1).toString()
case 'yyyy':
return date.getFullYear().toString()
case 'yy':
return formatChunk('yyyy', date).slice(-2)
default:
return null
}
}
export const formatDate = (format, date, divider = ' ') => {
return format
.split(divider)
.map(chunk => formatChunk(chunk, new Date(date)))
.join(divider)
}

33
lib/storage.js Normal file
View file

@ -0,0 +1,33 @@
const stubbedStorage = {}
'get set remove keys'
.split(' ')
.forEach(method => (stubbedStorage[method] = () => null))
let localStorage
try {
localStorage = window.localStorage
} catch (e) {
if (e instanceof ReferenceError) {
localStorage = stubbedStorage
}
}
const storage = {
get: key => {
try {
// (max@maxwofford.com) Values that were set before values were stringified might fail to parse, so we return the raw storage item if we can't parse it
return JSON.parse(localStorage.getItem(key))
} catch (e) {
if (e.name === 'SyntaxError') {
return localStorage.getItem(key)
} else {
console.error(e)
}
}
},
set: (key, value) => localStorage.setItem(key, JSON.stringify(value)),
remove: key => localStorage.removeItem(key),
keys: () => Object.keys(localStorage)
}
export default storage

View file

@ -122,10 +122,6 @@ module.exports = withMDX({
source: '/summer/_next/:path*',
destination: 'https://summer.hackclub.com/_next/:path*'
},
{
source: '/bank/',
destination: 'https://v2.hackclub.dev/bank/'
},
{
source: '/sponsorship/',
destination: 'https://workshops.hackclub.com/sponsorship/'

View file

@ -28,5 +28,6 @@
"react-use-websocket": "2.7.1",
"resnow": "^1.0.0",
"theme-ui": "^0.10"
}
},
"devDependencies": {}
}

56
pages/bank.js Normal file
View file

@ -0,0 +1,56 @@
import {
Card,
Box,
Button,
Container,
Flex,
Heading,
Image,
Text,
Grid,
theme
} from 'theme-ui'
import ForceTheme from '../components/force-theme'
import Meta from '@hackclub/meta'
import Head from 'next/head'
import Nav from '../components/nav'
import Footer from '../components/footer'
import Landing from '../components/bank/Landing'
import Features from '../components/bank/Features'
import Testimonials from '../components/bank/Testimonials'
import Everything from '../components/bank/Everything'
import Start from '../components/bank/Start'
const styles = `
::selection {
background-color: #e42d42;
color: #ffffff;
text-shadow: none;
}
`
export default function Bank() {
return (
<>
<Box as="main" key="main">
<Nav dark />
<ForceTheme theme="dark" />
<Meta
as={Head}
title="Bank"
description="Hack Club Bank provides a 501(c)(3) status-backed bank account optimized for high school hackathons including invoicing, debit cards, check sending, pre-written legal forms, automated tax filing, and transparent finances. Get fiscal sponsorship designed to help you run great events."
/>
<style children={styles} />
<Box>
<Landing />
<Features />
<Testimonials />
<Everything />
<Start />
</Box>
</Box>
<Footer dark key="footer" />
</>
)
}

BIN
public/bank/bg.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 KiB

BIN
public/bank/events/dv-hacks.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 577 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 KiB

BIN
public/bank/events/ma-hacks.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 366 KiB

BIN
public/bank/events/slo-hacks.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 689 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 366 KiB

BIN
public/bank/laptop-dark.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 536 KiB

BIN
public/bank/laptop-light.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 538 KiB

BIN
public/bank/logo-512.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
public/bank/logo.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

16
public/bank/logo.svg Executable file
View file

@ -0,0 +1,16 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<circle cx="16" cy="16" r="15.25" fill="#E42D45" stroke="#F9D5D9" stroke-width="1.5"/>
<path d="M16.1942 8.096C16.1042 8.056 16.0412 8.032 16.0002 8.018V6C16.3582 6 16.7352 6.149 16.9972 6.264C17.2942 6.394 17.6732 6.59 18.0742 6.819C18.8632 7.27 19.8722 7.921 20.9522 8.683C23.1012 10.201 23.5 10.5 25.7072 12.293C26.0982 12.683 26.0982 13.317 25.7072 13.707C25.3172 14.098 24.6832 14.098 24.2932 13.707C22 12 21.8992 11.799 19.7982 10.317C18.7532 9.579 17.8242 8.98 17.0822 8.556C16.7172 8.347 16.4332 8.199 16.1942 8.096Z" fill="white"/>
<path d="M15.8062 8.096C15.8962 8.056 15.9592 8.032 16.0002 8.018V6C15.6422 6 15.2652 6.149 15.0032 6.264C14.7062 6.394 14.3272 6.59 13.9262 6.819C13.1372 7.27 12.1282 7.921 11.0482 8.683C8.89924 10.201 8.50049 10.5 6.29324 12.293C5.90224 12.683 5.90224 13.317 6.29324 13.707C6.68324 14.098 7.31724 14.098 7.70724 13.707C10.0005 12 10.1012 11.799 12.2022 10.317C13.2472 9.579 14.1762 8.98 14.9182 8.556C15.2832 8.347 15.5672 8.199 15.8062 8.096Z" fill="white"/>
<path d="M7 24C7 23.448 7.448 23 8 23H24C24.552 23 25 23.448 25 24C25 24.552 24.552 25 24 25H8C7.448 25 7 24.552 7 24Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M16 22C15.448 22 15 21.552 15 21V14C15 13.448 15.448 13 16 13C16.552 13 17 13.448 17 14V21C17 21.552 16.552 22 16 22Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M21 22C20.448 22 20 21.552 20 21V14C20 13.448 20.448 13 21 13C21.552 13 22 13.448 22 14V21C22 21.552 21.552 22 21 22Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M11 22C10.448 22 10 21.552 10 21V14C10 13.448 10.448 13 11 13C11.552 13 12 13.448 12 14V21C12 21.552 11.552 22 11 22Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0">
<rect width="32" height="32" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

31
public/bank/possibility.svg Executable file
View file

@ -0,0 +1,31 @@
<svg width="128" height="64" viewBox="0 0 128 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.27886 27.6738C4.18716 27.628 3.89436 28.9425 3.89136 28.9525C3.65996 29.7189 3.39136 30.4736 3.15516 31.2386C2.71306 32.67 2.32956 34.1184 1.99266 35.5783C1.87356 36.0946 1.45776 37.0344 1.64396 37.5932C1.75756 37.934 2.76676 38.4034 3.00016 38.5231C4.20426 39.1406 5.46996 39.6301 6.68116 40.228C7.44646 40.6058 9.66476 42.145 9.66476 42.0492" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2.76631 38.2891C2.55291 37.8624 3.21621 37.3088 3.45461 37.0108C4.65531 35.5099 6.06591 34.2281 7.68311 33.1756C13.1648 29.6082 19.9058 27.3738 26.4653 27.3738" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M26.2285 15.8117C28.2995 15.8117 30.9855 14.855 32.9965 14.3819C33.6625 14.2252 34.3295 14.0486 34.9975 13.9053C35.1785 13.8667 36.2145 13.6784 36.0465 13.7623" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M31.2803 15.5259C31.8293 18.8225 32.9483 22.1952 32.9483 25.5347" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M40.0488 12.3325C41.3168 14.8679 41.2358 18.4479 42.0508 21.1974C42.3028 22.0468 42.9088 25.0383 42.9088 24.915" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M42.1816 18.7633C43.501 18.5 45.0166 18.1454 46.1586 17.9008L48.001 17.5" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M47.627 11.9512C48 14.5 48.398 17.1159 48.628 19.577C48.706 20.4013 48.744 21.2307 48.819 22.0553C48.838 22.2691 49.0099 23.6282 49.0099 23.6282" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M54.0163 16.3196C54.0163 17.6231 53.8983 18.9991 54.0163 20.2967C54.0633 20.8148 54.1603 22.1175 54.1603 22.1175" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M53.249 12.2466V12.1508V11.9591V11.8633V11.7675V11.6716" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M63.569 9.55737C62.627 9.55737 61.993 9.64737 61.109 10.0094C60.527 10.2474 60.08 10.5491 59.552 10.9133C55.072 14.0029 62.386 14.5499 64.373 15.6337C64.914 15.9288 65.519 16.3333 65.628 16.9896C65.889 18.5562 63.398 20.012 62.213 20.555C61.787 20.7503 60.798 21.0892 60.506 20.5048" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M79.2374 9.65771C75.4874 9.65771 73.1394 15.887 75.3204 18.8474C76.7734 20.8191 78.8384 19.6007 80.6434 19.6007" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M86.5252 9.10524C86.6502 9.23694 85.6172 10.9597 85.3882 11.5869C84.5272 13.9569 84.1942 18.9148 87.9402 19.2405C91.7462 19.5715 91.4962 12.3574 90.3522 10.4272C89.6762 9.28614 86.3102 8.87824 86.5252 9.10524Z" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M96.8927 9.31396C96.5567 9.81756 96.5607 11.1555 96.5217 11.5869C96.3147 13.8618 96.2647 16.9305 97.6817 18.8695C99.5997 21.494 102.33 20.0555 103.526 17.6634C103.958 16.7993 104.262 15.7981 104.361 14.8339C104.563 12.8677 103.711 11.9548 103.387 10.3344" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M108.762 9.69287C108.762 12.509 108.634 15.3838 108.762 18.1986C108.792 18.8704 108.803 20.8759 109.751 21.0339C111.627 21.3467 113.546 19.913 115.355 19.913" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M119.171 9.61719C118.939 10.3157 119.191 11.3439 119.234 12.0744C119.335 13.8263 119.327 15.6377 119.234 17.3933C119.211 17.8178 118.794 19.6907 119.296 20.1927" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M119.297 8.87088C120.629 7.98288 123.034 9.10868 124.118 10.1772C126.644 12.667 126.701 16.4657 124.087 18.9487C123.112 19.8748 121.366 21.5323 119.857 20.8461" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M29.1592 38.2793C29.8752 41.1454 31.1862 43.8818 32.0002 46.7305C32.1362 47.2067 32.8742 51.4929 32.8742 50.1547" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M29.6689 37.8422C29.8699 37.4396 30.7439 37.2604 30.9799 37.1865C32.6199 36.6742 34.3709 36.5308 36.0799 36.5308C36.7299 36.5308 38.4169 36.5693 38.5569 37.6236C38.8999 40.1981 36.1559 41.8354 34.2589 42.942C34.1909 42.9818 31.882 44.2807 32.146 44.5448C32.522 44.9212 33.8509 44.2195 34.1859 44.1077C35.6379 43.6237 39.22 42.2586 40.16 44.3263C41.78 47.8905 35.8429 50.5221 33.2389 50.9561" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M47.4458 34.1265C46.4078 34.1265 45.1058 34.7496 44.3138 35.0008C43.7178 35.1896 42.1588 35.6036 41.9818 36.3122C41.8428 36.8702 42.2857 37.6659 42.4187 38.1336C42.7417 39.2632 42.9658 40.412 43.2208 41.5578C43.6228 43.3661 44.2868 47.0234 45.9168 48.1876C46.5048 48.608 47.5228 47.7048 47.9558 47.4591C49.4378 46.6196 51.0238 45.8562 52.7648 45.8562" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M43.6572 41.4119C45.3102 41.1757 46.4712 40.4791 48.2472 39.8091" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M59.4678 33.2523C59.4678 36.8428 61.7118 39.4608 65.0048 38.8621C66.4788 38.5941 68.3758 37.1847 68.7928 35.6565C68.9618 35.0379 69.1258 34.0697 68.7928 33.4708C68.5368 33.0097 67.8458 32.504 67.8458 33.3251" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M64.7852 39.5176C64.7852 41.7075 64.7662 40.0493 65.6592 45.3461C65.6942 45.5507 65.8782 46.1364 65.8782 45.9289" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M74.402 31.7952C72.4997 34.5 72.5 35.2963 72.5004 37C72.5009 39 72.4595 40.6967 73.163 42.1406C73.866 43.5845 75.442 44.7516 77.098 44.1076C80.645 42.7282 80.967 37.9536 80.303 34.855C80.083 33.8275 79.82 32.6063 78.846 32.0136C77.443 31.1595 75.878 31.3497 74.402 31.7952Z" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M84.1654 31C84 31.5 83.9438 34.7816 84.1648 35.9477C84.6358 38.4416 87.0329 42.9072 90.1389 42.2861C91.1099 42.092 92.1148 40.9722 92.6888 40.2462C93.4598 39.2695 93.4169 39.0817 93.7809 37.5505C94.0439 36.4465 94.0328 35.1772 93.9998 34.0535C93.9548 32.5301 93.2778 31.3706 92.6158 30.0464" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M99.2925 38.1646C99.0001 35 99.501 31.5 101 29" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M99.3916 42.2863C99.3916 42.2378 99.3916 42.1892 99.3916 42.1406" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M104 37.5C103.682 33.6884 104.85 31.3665 107 28.5" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M103.398 41.7034C103.578 41.5233 103.544 41.489 103.544 41.7034" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M67.3672 54.7853C68.0732 53.8449 69.7332 53.0178 70.4442 52.5972C73.9052 50.5523 77.8122 49.0719 81.7272 48.2209C82.5132 48.05 85.0332 47.326 85.6252 48.0158C86.1512 48.6297 84.5462 49.8913 84.2572 50.1355C82.6162 51.5204 80.9492 52.8754 79.4702 54.4434C79.2872 54.638 78.4872 55.6851 78.7872 56.0844C79.3622 56.8511 80.7192 56.1357 81.6592 55.9478C83.5072 55.578 85.3042 54.9869 87.1292 54.5118C96.5232 52.0654 105.783 49.2032 115.164 46.7166" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 7.7 KiB

BIN
public/hackers/athul.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
public/hackers/connie.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
public/hackers/jamsheed.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
public/hackers/joy.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
public/hackers/kat.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
public/hackers/khushi.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
public/hackers/lachlan.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

BIN
public/hackers/megan.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

BIN
public/hackers/michael.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

BIN
public/hackers/michael.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 492 KiB

BIN
public/hackers/rashid.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
public/hackers/selynna.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
public/hackers/victor.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
public/hackers/wesley.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

BIN
public/team/mel.png Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 846 KiB

After

Width:  |  Height:  |  Size: 2.4 MiB

1
public/underline.svg Executable file
View file

@ -0,0 +1 @@
<svg preserveAspectRatio="none" width="119" height="6" viewBox="0 0 119 6" xmlns="http://www.w3.org/2000/svg"><path d="M117.434 3.853C59.027 5.933 84.784-2.46 1.566 3.436" stroke="#e42d42" stroke-width="2" fill="none" stroke-linecap="round"/></svg>

After

Width:  |  Height:  |  Size: 249 B

View file

@ -1346,6 +1346,11 @@ debug@^4.1.0:
dependencies:
ms "2.1.2"
deepmerge@^2.1.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170"
integrity sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==
deepmerge@^4.2.2:
version "4.2.2"
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
@ -1630,6 +1635,19 @@ form-data@~2.3.2:
combined-stream "^1.0.6"
mime-types "^2.1.12"
formik@^2.2.9:
version "2.2.9"
resolved "https://registry.yarnpkg.com/formik/-/formik-2.2.9.tgz#8594ba9c5e2e5cf1f42c5704128e119fc46232d0"
integrity sha512-LQLcISMmf1r5at4/gyJigGn0gOwFbeEAlji+N9InZF6LIMXnFNkO42sCI8Jt84YZggpD4cPWObAZaxpEFtSzNA==
dependencies:
deepmerge "^2.1.1"
hoist-non-react-statics "^3.3.0"
lodash "^4.17.21"
lodash-es "^4.17.21"
react-fast-compare "^2.0.1"
tiny-warning "^1.0.2"
tslib "^1.10.0"
fsevents@~2.3.1:
version "2.3.2"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
@ -1849,7 +1867,7 @@ hmac-drbg@^1.0.1:
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.1"
hoist-non-react-statics@^3.3.1:
hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1:
version "3.3.2"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
@ -2225,6 +2243,11 @@ locate-path@^5.0.0:
dependencies:
p-locate "^4.1.0"
lodash-es@^4.17.21:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==
lodash.sortby@^4.7.0:
version "4.7.0"
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
@ -2837,6 +2860,11 @@ react-dom@^17.0.2:
object-assign "^4.1.1"
scheduler "^0.20.2"
react-fast-compare@^2.0.1:
version "2.0.4"
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9"
integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==
react-is@17.0.2:
version "17.0.2"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
@ -3336,6 +3364,11 @@ timers-browserify@2.0.12, timers-browserify@^2.0.4:
dependencies:
setimmediate "^1.0.4"
tiny-warning@^1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
to-arraybuffer@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"
@ -3393,6 +3426,11 @@ ts-pnp@^1.1.6:
resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92"
integrity sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==
tslib@^1.10.0:
version "1.14.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
tty-browserify@0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6"
@ -3430,6 +3468,11 @@ unbox-primitive@^1.0.0:
has-symbols "^1.0.2"
which-boxed-primitive "^1.0.2"
unfetch@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-4.2.0.tgz#7e21b0ef7d363d8d9af0fb929a5555f6ef97a3be"
integrity sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==
unherit@^1.0.4:
version "1.1.3"
resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.3.tgz#6c9b503f2b41b262330c80e91c8614abdaa69c22"