Procházet zdrojové kódy

add account create view

Aneurin Barker Snook před 1 rokem
rodič
revize
85794c592d

+ 20 - 0
web/src/routes.tsx

@@ -6,6 +6,7 @@ import HerdView from './views/HerdView'
 import LoginView from './views/LoginView'
 import { Outlet } from 'react-router-dom'
 import type { RouteObject } from 'react-router-dom'
+import CreateAccountView from './views/CreateAccountView'
 
 const coreRoutes: RouteObject[] = [
   {
@@ -56,6 +57,25 @@ const routes: RouteObject[] = [
       },
     ],
   },
+  {
+    path: '/account/create',
+    element: (
+      <AppLayout>
+        <Outlet />
+      </AppLayout>
+    ),
+    errorElement: (
+      <AppLayout>
+        <ErrorView />
+      </AppLayout>
+    ),
+    children: [
+      {
+        path: '',
+        element: <CreateAccountView />,
+      },
+    ],
+  },
 ]
 
 export default routes

+ 7 - 0
web/src/views/CreateAccountView.scss

@@ -0,0 +1,7 @@
+@import '@/vars.scss';
+
+.create-account {
+  font-size: $font-size-s;
+  margin-top: $space-l;
+  text-align: center;
+}

+ 116 - 0
web/src/views/CreateAccountView.tsx

@@ -0,0 +1,116 @@
+import './CreateAccountView.scss'
+import Button from '@/components/button/Button'
+import ButtonSet from '@/components/ButtonSet'
+import Chip from '@/components/Chip'
+import FormGroup from '@/components/form/FormGroup'
+import FormInput from '@/components/form/FormInput'
+import HideShowButton from '@/components/button/HideShowButton'
+import { Link } from 'react-router-dom'
+import Main from '@/components/Main'
+import Notice from '@/components/Notice'
+import Row from '@/components/Row'
+import type { SubmitHandler } from 'react-hook-form'
+import api from '@/api'
+import { useForm } from 'react-hook-form'
+import { useSession } from '@/hooks'
+import { Navigate, useSearchParams } from 'react-router-dom'
+import { useConnection, useDocument } from '@/hooks'
+import { useEffect, useState } from 'react'
+
+interface AccountCreateFormData {
+  email: string
+  password: string
+}
+
+function useAccountCreateForm() {
+  const form = useForm<AccountCreateFormData>()
+
+  const [busy, setBusy] = useState(false)
+  const [error, setError] = useState<Error>()
+
+  const inputs = {
+    email: form.register('email', { validate: {
+      required: value => value.length >= 1 || 'Required',
+    }}),
+    password: form.register('password', { validate: {
+      minLength: value => value.length >= 8 || 'Must be at least 8 characters',
+    }}),
+  }
+
+  return {
+    ...form,
+    inputs,
+    busy, setBusy,
+    error, setError,
+  }
+}
+
+export default function CreateAccountView() {
+  const doc = useDocument()
+  const [params] = useSearchParams()
+  const { options } = useConnection()
+  const session = useSession()
+
+  const { formState: { errors }, ...form } = useAccountCreateForm()
+
+  const [passwordVisible, setPasswordVisible] = useState(false)
+  const redirectTo = params.get('redirect') || '/'
+
+  const submit: SubmitHandler<AccountCreateFormData> = async ({ email, password }) => {
+    form.setError(undefined)
+    form.setBusy(true)
+    try {
+      await api.createAccount(options, {
+        account: { email, password },
+      })
+      await session.login(email, password)
+    } catch (err) {
+      form.setError(err as Error)
+    } finally {
+      form.setBusy(false)
+    }
+  }
+
+  useEffect(() => {
+    doc.setTitle('Create Account')
+  }, [doc])
+
+  if (session.ready && session.loggedIn) {
+    return (
+      <Navigate to={redirectTo} />
+    )
+  }
+
+  return (
+    <Main className="center">
+      <form onSubmit={form.handleSubmit(submit)}>
+        <FormGroup name="Create Account">
+          <FormInput id="email" label="Username">
+            <input id="email" type="text" {...form.inputs.email} />
+            {errors.email && <Chip className="mini" error={errors.email} />}
+          </FormInput>
+
+          <FormInput id="password" label="Password">
+            <Row className="hidden">
+              <input id="password" type={passwordVisible ? 'text' : 'password'} {...form.inputs.password} />
+              <ButtonSet>
+                <HideShowButton visible={passwordVisible} onClick={() => setPasswordVisible(!passwordVisible)} />
+              </ButtonSet>
+            </Row>
+            {errors.password && <Chip className="mini" error={errors.password} />}
+          </FormInput>
+
+          <ButtonSet>
+            <Button className="wide fill positive" disabled={form.busy} type="submit">Create and log in</Button>
+          </ButtonSet>
+
+          <Notice error={form.error} />
+
+          <section className="create-account">
+            Have an account already? <Link to="/login">Log in</Link>
+          </section>
+        </FormGroup>
+      </form>
+    </Main>
+  )
+}

+ 7 - 0
web/src/views/LoginView.scss

@@ -0,0 +1,7 @@
+@import '@/vars.scss';
+
+.create-account {
+  font-size: $font-size-s;
+  margin-top: $space-l;
+  text-align: center;
+}

+ 6 - 0
web/src/views/LoginView.tsx

@@ -1,9 +1,11 @@
+import './LoginView.scss'
 import Button from '@/components/button/Button'
 import ButtonSet from '@/components/ButtonSet'
 import Chip from '@/components/Chip'
 import FormGroup from '@/components/form/FormGroup'
 import FormInput from '@/components/form/FormInput'
 import HideShowButton from '@/components/button/HideShowButton'
+import { Link } from 'react-router-dom'
 import Main from '@/components/Main'
 import Notice from '@/components/Notice'
 import Row from '@/components/Row'
@@ -98,6 +100,10 @@ export default function LoginView() {
           </ButtonSet>
 
           <Notice error={form.error} />
+
+          <section className="create-account">
+            Don't have an account yet? <Link to="/account/create">Create one</Link>
+          </section>
         </FormGroup>
       </form>
     </Main>