LoginView.tsx 3.3 KB

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