Selaa lähdekoodia

refactor: install form (#4154)

TinsFox 11 kuukautta sitten
vanhempi
commit
cc835d523c
4 muutettua tiedostoa jossa 71 lisäystä ja 94 poistoa
  1. 49 66
      web/app/install/installForm.tsx
  2. 1 0
      web/i18n/en-US/login.ts
  3. 3 0
      web/package.json
  4. 18 28
      web/yarn.lock

+ 49 - 66
web/app/install/installForm.tsx

@@ -1,73 +1,67 @@
 'use client'
 import React, { useEffect } from 'react'
 import { useTranslation } from 'react-i18next'
+
 import Link from 'next/link'
 import { useRouter } from 'next/navigation'
-// import { useContext } from 'use-context-selector'
-import Toast from '../components/base/toast'
+
+import type { SubmitHandler } from 'react-hook-form'
+import { useForm } from 'react-hook-form'
+import { z } from 'zod'
+import { zodResolver } from '@hookform/resolvers/zod'
+import classNames from 'classnames'
 import Loading from '../components/base/loading'
 import Button from '@/app/components/base/button'
-// import I18n from '@/context/i18n'
 
 import { fetchInitValidateStatus, fetchSetupStatus, setup } from '@/service/common'
 import type { InitValidateStatusResponse, SetupStatusResponse } from '@/models/common'
 
-const validEmailReg = /^[\w\.-]+@([\w-]+\.)+[\w-]{2,}$/
 const validPassword = /^(?=.*[a-zA-Z])(?=.*\d).{8,}$/
 
+const accountFormSchema = z.object({
+  email: z
+    .string()
+    .min(1, { message: 'login.error.emailInValid' })
+    .email('login.error.emailInValid'),
+  name: z.string().min(1, { message: 'login.error.nameEmpty' }),
+  password: z.string().min(8, {
+    message: 'login.error.passwordLengthInValid',
+  }).regex(validPassword, 'login.error.passwordInvalid'),
+})
+
+type AccountFormValues = z.infer<typeof accountFormSchema>
+
 const InstallForm = () => {
   const { t } = useTranslation()
   const router = useRouter()
-
-  const [email, setEmail] = React.useState('')
-  const [name, setName] = React.useState('')
-  const [password, setPassword] = React.useState('')
   const [showPassword, setShowPassword] = React.useState(false)
   const [loading, setLoading] = React.useState(true)
+  const {
+    register,
+    handleSubmit,
+    formState: { errors },
+  } = useForm<AccountFormValues>({
+    resolver: zodResolver(accountFormSchema),
+    defaultValues: {
+      name: '',
+      password: '',
+      email: '',
+    },
+  })
 
-  const showErrorMessage = (message: string) => {
-    Toast.notify({
-      type: 'error',
-      message,
-    })
-  }
-  const valid = () => {
-    if (!email) {
-      showErrorMessage(t('login.error.emailEmpty'))
-      return false
-    }
-    if (!validEmailReg.test(email)) {
-      showErrorMessage(t('login.error.emailInValid'))
-      return false
-    }
-    if (!name.trim()) {
-      showErrorMessage(t('login.error.nameEmpty'))
-      return false
-    }
-    if (!password.trim()) {
-      showErrorMessage(t('login.error.passwordEmpty'))
-      return false
-    }
-    if (!validPassword.test(password)) {
-      showErrorMessage(t('login.error.passwordInvalid'))
-      return false
-    }
-
-    return true
-  }
-  const handleSetting = async () => {
-    if (!valid())
-      return
+  const onSubmit: SubmitHandler<AccountFormValues> = async (data) => {
     await setup({
       body: {
-        email,
-        name,
-        password,
+        ...data,
       },
     })
     router.push('/signin')
   }
 
+  const handleSetting = async () => {
+    handleSubmit(onSubmit)()
+  }
+
   useEffect(() => {
     fetchSetupStatus().then((res: SetupStatusResponse) => {
       if (res.step === 'finished') {
@@ -93,24 +87,22 @@ const InstallForm = () => {
           mt-1 text-sm text-gray-600
         '>{t('login.setAdminAccountDesc')}</p>
         </div>
-
         <div className="grow mt-8 sm:mx-auto sm:w-full sm:max-w-md">
           <div className="bg-white ">
-            <form onSubmit={() => { }}>
+            <form onSubmit={handleSubmit(onSubmit)}>
               <div className='mb-5'>
                 <label htmlFor="email" className="my-2 flex items-center justify-between text-sm font-medium text-gray-900">
                   {t('login.email')}
                 </label>
                 <div className="mt-1">
                   <input
-                    id="email"
-                    type="email"
-                    value={email}
-                    onChange={e => setEmail(e.target.value)}
+                    {...register('email')}
                     placeholder={t('login.emailPlaceholder') || ''}
                     className={'appearance-none block w-full rounded-lg pl-[14px] px-3 py-2 border border-gray-200 hover:border-gray-300 hover:shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 placeholder-gray-400 caret-primary-600 sm:text-sm'}
                   />
+                  {errors.email && <span className='text-red-400 text-sm'>{t(`${errors.email?.message}`)}</span>}
                 </div>
+
               </div>
 
               <div className='mb-5'>
@@ -119,14 +111,12 @@ const InstallForm = () => {
                 </label>
                 <div className="mt-1 relative rounded-md shadow-sm">
                   <input
-                    id="name"
-                    type="text"
-                    value={name}
-                    onChange={e => setName(e.target.value)}
+                    {...register('name')}
                     placeholder={t('login.namePlaceholder') || ''}
                     className={'appearance-none block w-full rounded-lg pl-[14px] px-3 py-2 border border-gray-200 hover:border-gray-300 hover:shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 placeholder-gray-400 caret-primary-600 sm:text-sm pr-10'}
                   />
                 </div>
+                {errors.name && <span className='text-red-400 text-sm'>{t(`${errors.name.message}`)}</span>}
               </div>
 
               <div className='mb-5'>
@@ -135,13 +125,12 @@ const InstallForm = () => {
                 </label>
                 <div className="mt-1 relative rounded-md shadow-sm">
                   <input
-                    id="password"
+                    {...register('password')}
                     type={showPassword ? 'text' : 'password'}
-                    value={password}
-                    onChange={e => setPassword(e.target.value)}
                     placeholder={t('login.passwordPlaceholder') || ''}
                     className={'appearance-none block w-full rounded-lg pl-[14px] px-3 py-2 border border-gray-200 hover:border-gray-300 hover:shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 placeholder-gray-400 caret-primary-600 sm:text-sm pr-10'}
                   />
+
                   <div className="absolute inset-y-0 right-0 flex items-center pr-3">
                     <button
                       type="button"
@@ -152,18 +141,12 @@ const InstallForm = () => {
                     </button>
                   </div>
                 </div>
-                <div className='mt-1 text-xs text-gray-500'>{t('login.error.passwordInvalid')}</div>
 
+                <div className={classNames('mt-1 text-xs text-gray-500', {
+                  'text-red-400 !text-sm': errors.password,
+                })}>{t('login.error.passwordInvalid')}</div>
               </div>
 
-              {/* <div className="flex items-center justify-between">
-              <div className="text-sm">
-                <div className="flex items-center mb-4">
-                  <input id="default-checkbox" type="checkbox" className="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 rounded" />
-                  <label htmlFor="default-checkbox" className="ml-2 text-sm font-medium cursor-pointer text-primary-600 hover:text-gray-500">{t('login.acceptPP')}</label>
-                </div>
-              </div>
-            </div> */}
               <div>
                 <Button type='primary' className='w-full !fone-medium !text-sm' onClick={handleSetting}>
                   {t('login.installBtn')}

+ 1 - 0
web/i18n/en-US/login.ts

@@ -40,6 +40,7 @@ const translation = {
     emailInValid: 'Please enter a valid email address',
     nameEmpty: 'Name is required',
     passwordEmpty: 'Password is required',
+    passwordLengthInValid: 'Password must be at least 8 characters',
     passwordInvalid: 'Password must contain letters and numbers, and the length must be greater than 8',
   },
   license: {

+ 3 - 0
web/package.json

@@ -21,6 +21,7 @@
     "@formatjs/intl-localematcher": "^0.5.4",
     "@headlessui/react": "^1.7.13",
     "@heroicons/react": "^2.0.16",
+    "@hookform/resolvers": "^3.3.4",
     "@lexical/react": "^0.12.2",
     "@mdx-js/loader": "^2.3.0",
     "@mdx-js/react": "^2.3.0",
@@ -60,6 +61,7 @@
     "react-dom": "^18.2.0",
     "react-error-boundary": "^4.0.2",
     "react-headless-pagination": "^1.1.4",
+    "react-hook-form": "^7.51.4",
     "react-i18next": "^12.2.0",
     "react-infinite-scroll-component": "^6.1.0",
     "react-markdown": "^8.0.6",
@@ -84,6 +86,7 @@
     "swr": "^2.1.0",
     "use-context-selector": "^1.4.1",
     "uuid": "^9.0.1",
+    "zod": "^3.23.6",
     "zustand": "^4.5.1"
   },
   "devDependencies": {

+ 18 - 28
web/yarn.lock

@@ -216,6 +216,11 @@
   resolved "https://registry.npmjs.org/@heroicons/react/-/react-2.0.18.tgz"
   integrity sha512-7TyMjRrZZMBPa+/5Y8lN0iyvUU/01PeMGX2+RE7cQWpEUIcb4QotzUObFkJDejj/HUH4qjP/eQ0gzzKs2f+6Yw==
 
+"@hookform/resolvers@^3.3.4":
+  version "3.3.4"
+  resolved "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.3.4.tgz#de9b668c2835eb06892290192de6e2a5c906229b"
+  integrity sha512-o5cgpGOuJYrd+iMKvkttOclgwRW86EsWJZZRC23prf0uU2i48Htq4PuT73AVb9ionFyZrwYEITuOFGF+BydEtQ==
+
 "@humanwhocodes/config-array@^0.11.8":
   version "0.11.10"
   resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz"
@@ -5773,6 +5778,11 @@ react-headless-pagination@^1.1.4:
   dependencies:
     classnames "2.3.1"
 
+react-hook-form@^7.51.4:
+  version "7.51.4"
+  resolved "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.51.4.tgz#c3a47aeb22b699c45de9fc12b58763606cb52f0c"
+  integrity sha512-V14i8SEkh+V1gs6YtD0hdHYnoL4tp/HX/A45wWQN15CYr9bFRmmRdYStSO5L65lCCZRF+kYiSKhm9alqbcdiVA==
+
 react-i18next@^12.2.0:
   version "12.3.1"
   resolved "https://registry.npmjs.org/react-i18next/-/react-i18next-12.3.1.tgz"
@@ -6436,16 +6446,7 @@ string-argv@^0.3.1:
   resolved "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz"
   integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==
 
-"string-width-cjs@npm:string-width@^4.2.0":
-  version "4.2.3"
-  resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
-  integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
-  dependencies:
-    emoji-regex "^8.0.0"
-    is-fullwidth-code-point "^3.0.0"
-    strip-ansi "^6.0.1"
-
-string-width@^4.1.0, string-width@^4.2.0:
+"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0:
   version "4.2.3"
   resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
   integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -6513,14 +6514,7 @@ stringify-entities@^4.0.0:
     character-entities-html4 "^2.0.0"
     character-entities-legacy "^3.0.0"
 
-"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
-  version "6.0.1"
-  resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz"
-  integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
-  dependencies:
-    ansi-regex "^5.0.1"
-
-strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
   version "6.0.1"
   resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz"
   integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -7133,7 +7127,7 @@ word-wrap@^1.2.3:
   resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"
   integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
 
-"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
+"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
   version "7.0.0"
   resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz"
   integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -7151,15 +7145,6 @@ wrap-ansi@^6.2.0:
     string-width "^4.1.0"
     strip-ansi "^6.0.0"
 
-wrap-ansi@^7.0.0:
-  version "7.0.0"
-  resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz"
-  integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
-  dependencies:
-    ansi-styles "^4.0.0"
-    string-width "^4.1.0"
-    strip-ansi "^6.0.0"
-
 wrap-ansi@^8.1.0:
   version "8.1.0"
   resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz"
@@ -7208,6 +7193,11 @@ yocto-queue@^0.1.0:
   resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz"
   integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
 
+zod@^3.23.6:
+  version "3.23.6"
+  resolved "https://registry.npmjs.org/zod/-/zod-3.23.6.tgz#c08a977e2255dab1fdba933651584a05fcbf19e1"
+  integrity sha512-RTHJlZhsRbuA8Hmp/iNL7jnfc4nZishjsanDAfEY1QpDQZCahUp3xDzl+zfweE9BklxMUcgBgS1b7Lvie/ZVwA==
+
 zrender@5.4.3:
   version "5.4.3"
   resolved "https://registry.npmjs.org/zrender/-/zrender-5.4.3.tgz"