mirror of
https://github.com/System-End/site.git
synced 2026-04-19 19:45:07 +00:00
Create form abstraction
This commit is contained in:
parent
0c4f9d31c6
commit
dc62a6d564
5 changed files with 154 additions and 71 deletions
|
|
@ -1,24 +1,9 @@
|
|||
import { useState, useEffect } from 'react'
|
||||
import { Card, Label, Input, Button, Checkbox, Textarea } from 'theme-ui'
|
||||
import fetch from 'isomorphic-unfetch'
|
||||
import { Card, Label, Input, Checkbox, Textarea } from 'theme-ui'
|
||||
import useForm from '../../lib/use-form'
|
||||
import Submit from '../submit'
|
||||
|
||||
const JoinForm = () => {
|
||||
const [name, setName] = useState('')
|
||||
const [email, setEmail] = useState('')
|
||||
const [teen, setTeen] = useState(false)
|
||||
const [reason, setReason] = useState('')
|
||||
|
||||
const [status, setStatus] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
setName('')
|
||||
setEmail('')
|
||||
setTeen(false)
|
||||
setReason('')
|
||||
setStatus('')
|
||||
}, 1500)
|
||||
}, [status])
|
||||
const { status, formProps, useField } = useForm('/api/join')
|
||||
|
||||
return (
|
||||
<Card
|
||||
|
|
@ -35,73 +20,38 @@ const JoinForm = () => {
|
|||
}
|
||||
}}
|
||||
>
|
||||
<form
|
||||
action="https://v3.hackclub.com/api/join"
|
||||
method="post"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault()
|
||||
fetch('https://v3.hackclub.com/api/join', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ name, email, teen, reason })
|
||||
})
|
||||
.then((r) => r.json())
|
||||
.then((r) => setStatus(r.status))
|
||||
.catch((e) => console.error(e))
|
||||
}}
|
||||
>
|
||||
<Label htmlFor="name">
|
||||
<form {...formProps}>
|
||||
<Label>
|
||||
Full name
|
||||
<Input
|
||||
name="name"
|
||||
placeholder="Fiona Hackworth"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
/>
|
||||
<Input {...useField('name')} placeholder="Fiona Hackworth" required />
|
||||
</Label>
|
||||
<Label htmlFor="email">
|
||||
<Label>
|
||||
Email address
|
||||
<Input
|
||||
name="email"
|
||||
type="email"
|
||||
value={email}
|
||||
{...useField('email')}
|
||||
placeholder="fiona@hackclub.com"
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</Label>
|
||||
<Label sx={{ flexDirection: 'row !important', alignItems: 'center' }}>
|
||||
<Checkbox
|
||||
name="teen"
|
||||
sx={{ color: 'muted' }}
|
||||
checked={teen}
|
||||
onChange={(e) => setTeen(e.target.checked)}
|
||||
/>
|
||||
<Label variant="labelCheckbox">
|
||||
<Checkbox {...useField('teen', 'checkbox')} />
|
||||
Are you a teenager?
|
||||
</Label>
|
||||
<Label htmlFor="reason">
|
||||
<Label>
|
||||
Why do you want to join Hack Club?
|
||||
<Textarea
|
||||
name="reason"
|
||||
{...useField('reason')}
|
||||
placeholder="Write a few sentences."
|
||||
variant="forms.input"
|
||||
value={reason}
|
||||
onChange={(e) => setReason(e.target.value)}
|
||||
sx={{ boxShadow: 'none !important' }}
|
||||
required
|
||||
/>
|
||||
</Label>
|
||||
<Button
|
||||
as="input"
|
||||
type="submit"
|
||||
variant="cta"
|
||||
sx={{
|
||||
py: 2,
|
||||
px: 3,
|
||||
mt: 3,
|
||||
fontSize: 2,
|
||||
width: '100%',
|
||||
fontFamily: 'inherit',
|
||||
backgroundImage: (theme) => theme.util.gradient('cyan', 'blue')
|
||||
<Submit
|
||||
status={status}
|
||||
labels={{
|
||||
default: 'Request invitation',
|
||||
error: 'Something went wrong',
|
||||
success: 'Submitted!'
|
||||
}}
|
||||
value={status === 'success' ? 'Submitted!' : 'Queue signup'}
|
||||
/>
|
||||
</form>
|
||||
</Card>
|
||||
|
|
|
|||
44
components/submit.js
Normal file
44
components/submit.js
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import { Button } from 'theme-ui'
|
||||
import theme from '../lib/theme'
|
||||
|
||||
const bg = {
|
||||
default: {
|
||||
bg: 'blue',
|
||||
backgroundImage: theme.util.gradient('cyan', 'blue')
|
||||
},
|
||||
success: {
|
||||
bg: 'green',
|
||||
backgroundImage: theme.util.gradient('green', 'cyan')
|
||||
},
|
||||
error: {
|
||||
bg: 'orange',
|
||||
backgroundImage: theme.util.gradient('orange', 'red'),
|
||||
boxShadow: `0 0 0 1px ${theme.colors.white}, 0 0 0 4px ${theme.colors.primary}`
|
||||
}
|
||||
}
|
||||
|
||||
const Submit = ({
|
||||
status,
|
||||
labels = { default: 'Submit', error: 'Error!', success: 'Submitted!' },
|
||||
width = '100%',
|
||||
sx,
|
||||
...props
|
||||
}) => (
|
||||
<Button
|
||||
as="button"
|
||||
type="submit"
|
||||
sx={{
|
||||
py: 2,
|
||||
px: 3,
|
||||
mt: 3,
|
||||
fontSize: 2,
|
||||
width,
|
||||
...bg[status],
|
||||
...sx
|
||||
}}
|
||||
children={labels[status]}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
||||
export default Submit
|
||||
19
lib/theme.js
19
lib/theme.js
|
|
@ -31,6 +31,7 @@ theme.util.gradientText = (from, to) => ({
|
|||
})
|
||||
|
||||
theme.buttons.primary = merge(theme.buttons.primary, {
|
||||
justifyContent: 'center',
|
||||
fontFamily: 'inherit',
|
||||
borderRadius: 'circle',
|
||||
boxShadow: 'card',
|
||||
|
|
@ -81,4 +82,22 @@ theme.cards.translucentDark = {
|
|||
}
|
||||
}
|
||||
|
||||
theme.forms.input = merge(theme.forms.input, { boxShadow: 'none !important' })
|
||||
theme.forms.textarea = { variant: 'forms.input' }
|
||||
theme.forms.label = merge(theme.forms.label, {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
textAlign: 'left',
|
||||
fontSize: 2,
|
||||
mb: 3
|
||||
})
|
||||
theme.forms.labelCheckbox = {
|
||||
variant: 'forms.label',
|
||||
flexDirection: 'row !important',
|
||||
alignItems: 'center',
|
||||
svg: { color: 'muted' }
|
||||
}
|
||||
|
||||
theme.text.lead = {}
|
||||
|
||||
export default theme
|
||||
|
|
|
|||
69
lib/use-form.js
Normal file
69
lib/use-form.js
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
import { useState, useEffect } from 'react'
|
||||
import fetch from 'isomorphic-unfetch'
|
||||
|
||||
const useForm = (
|
||||
submitURL = '/',
|
||||
callback,
|
||||
options = { clearOnSubmit: 2000, method: 'post' }
|
||||
) => {
|
||||
const [status, setStatus] = useState('default')
|
||||
const [data, setData] = useState({})
|
||||
const [touched, setTouched] = useState({})
|
||||
|
||||
const onFieldChange = (e, name, type) => {
|
||||
e.persist()
|
||||
const value = e.target[type === 'checkbox' ? 'checked' : 'value']
|
||||
setData((data) => ({ ...data, [name]: value }))
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setTouched(Object.keys(data))
|
||||
}, [data])
|
||||
|
||||
const useField = (name, type = 'text', ...props) => {
|
||||
const checkbox = type === 'checkbox'
|
||||
const empty = checkbox ? false : ''
|
||||
const onChange = (e) => onFieldChange(e, name, type)
|
||||
const value = data[name]
|
||||
return {
|
||||
name,
|
||||
type: name === 'email' ? 'email' : type,
|
||||
[checkbox ? 'checked' : 'value']: value || empty,
|
||||
onChange,
|
||||
...props
|
||||
}
|
||||
}
|
||||
|
||||
const { method = 'post' } = options
|
||||
const action =
|
||||
submitURL?.startsWith('/') && process.env.NODE_ENV !== 'development'
|
||||
? `https://v3.hackclub.com${submitURL}`
|
||||
: submitURL
|
||||
|
||||
const onSubmit = (e) => {
|
||||
e.preventDefault()
|
||||
fetch(action, {
|
||||
method,
|
||||
body: JSON.stringify(data)
|
||||
})
|
||||
.then((r) => r.json())
|
||||
.then((r) => {
|
||||
setStatus('success')
|
||||
if (callback) callback(r)
|
||||
setTimeout(() => setStatus('default'), 2000)
|
||||
if (options.clearOnSubmit) {
|
||||
setTimeout(() => setData({}), options.clearOnSubmit)
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e)
|
||||
setStatus('error')
|
||||
})
|
||||
}
|
||||
|
||||
const formProps = { method, action, onSubmit }
|
||||
|
||||
return { status, data, touched, useField, formProps }
|
||||
}
|
||||
|
||||
export default useForm
|
||||
|
|
@ -99,6 +99,7 @@ const Window = ({ title, children, ...props }) => (
|
|||
</Card>
|
||||
)
|
||||
|
||||
export default () => (
|
||||
<>
|
||||
<Head>
|
||||
<Meta
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue