CreateAccountView.tsx 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. import './CreateAccountView.scss'
  2. import * as validate from '@/lib/validate'
  3. import Button from '@/components/button/Button'
  4. import ButtonSet from '@/components/ButtonSet'
  5. import Chip from '@/components/Chip'
  6. import FormGroup from '@/components/form/FormGroup'
  7. import FormInput from '@/components/form/FormInput'
  8. import HideShowButton from '@/components/button/HideShowButton'
  9. import { Link } from 'react-router-dom'
  10. import Main from '@/components/Main'
  11. import Notice from '@/components/Notice'
  12. import Row from '@/components/Row'
  13. import api from '@/api'
  14. import { useForm } from 'react-hook-form'
  15. import { useSession } from '@/hooks'
  16. import { Navigate, useSearchParams } from 'react-router-dom'
  17. import { useConnection, useDocument } from '@/hooks'
  18. import { useEffect, useState } from 'react'
  19. interface AccountCreateFormData {
  20. email: string
  21. password: string
  22. }
  23. function useAccountCreateForm() {
  24. const form = useForm<AccountCreateFormData>({ mode: 'onBlur' })
  25. const inputs = {
  26. email: form.register('email', { validate: value => {
  27. if (!value) return 'Required'
  28. return validate.email(value)
  29. }}),
  30. password: form.register('password', { validate: value => {
  31. if (!value) return 'Required'
  32. return validate.password(value)
  33. }}),
  34. }
  35. return { ...form, inputs }
  36. }
  37. export default function CreateAccountView() {
  38. const doc = useDocument()
  39. const form = useAccountCreateForm()
  40. const [params] = useSearchParams()
  41. const { options } = useConnection()
  42. const session = useSession()
  43. const [busy, setBusy] = useState(false)
  44. const [error, setError] = useState<Error>()
  45. const [passwordVisible, setPasswordVisible] = useState(false)
  46. const redirectTo = params.get('redirect') || '/'
  47. async function submit(data: AccountCreateFormData) {
  48. if (busy) return
  49. setError(undefined)
  50. setBusy(true)
  51. try {
  52. await api.createAccount(options, {
  53. account: {
  54. email: data.email,
  55. password: data.password,
  56. },
  57. })
  58. await session.login(data.email, data.password)
  59. } catch (err) {
  60. setError(err as Error)
  61. } finally {
  62. setBusy(false)
  63. }
  64. }
  65. useEffect(() => {
  66. doc.setTitle('Create Account')
  67. }, [doc])
  68. if (session.ready && session.loggedIn) {
  69. return (
  70. <Navigate to={redirectTo} />
  71. )
  72. }
  73. return (
  74. <Main className="center">
  75. <form onSubmit={form.handleSubmit(submit)}>
  76. <FormGroup name="Create Account">
  77. <FormInput id="email" label="Email address">
  78. <input id="email" disabled={busy} type="text" {...form.inputs.email} />
  79. {form.formState.errors.email && <Chip className="mini" error={form.formState.errors.email} />}
  80. </FormInput>
  81. <FormInput id="password" label="Password">
  82. <Row className="hidden">
  83. <input id="password" disabled={busy} type={passwordVisible ? 'text' : 'password'} {...form.inputs.password} />
  84. <ButtonSet>
  85. <HideShowButton disabled={busy} visible={passwordVisible} onClick={() => setPasswordVisible(!passwordVisible)} />
  86. </ButtonSet>
  87. </Row>
  88. {form.formState.errors.password && <Chip className="mini" error={form.formState.errors.password} />}
  89. </FormInput>
  90. <ButtonSet>
  91. <Button disabled={busy} className="wide fill positive" type="submit">Create and log in</Button>
  92. </ButtonSet>
  93. <Notice error={error} />
  94. <section className="create-account">
  95. Have an account already? <Link to="/login">Log in</Link>
  96. </section>
  97. </FormGroup>
  98. </form>
  99. </Main>
  100. )
  101. }