You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

installForm.tsx 7.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. 'use client'
  2. import React, { useCallback, useEffect } from 'react'
  3. import { useTranslation } from 'react-i18next'
  4. import { useDebounceFn } from 'ahooks'
  5. import Link from 'next/link'
  6. import { useRouter } from 'next/navigation'
  7. import type { SubmitHandler } from 'react-hook-form'
  8. import { useForm } from 'react-hook-form'
  9. import { z } from 'zod'
  10. import { zodResolver } from '@hookform/resolvers/zod'
  11. import Loading from '../components/base/loading'
  12. import classNames from '@/utils/classnames'
  13. import Button from '@/app/components/base/button'
  14. import { fetchInitValidateStatus, fetchSetupStatus, setup } from '@/service/common'
  15. import type { InitValidateStatusResponse, SetupStatusResponse } from '@/models/common'
  16. import { basePath } from '@/utils/var'
  17. const validPassword = /^(?=.*[a-zA-Z])(?=.*\d).{8,}$/
  18. const accountFormSchema = z.object({
  19. email: z
  20. .string()
  21. .min(1, { message: 'login.error.emailInValid' })
  22. .email('login.error.emailInValid'),
  23. name: z.string().min(1, { message: 'login.error.nameEmpty' }),
  24. password: z.string().min(8, {
  25. message: 'login.error.passwordLengthInValid',
  26. }).regex(validPassword, 'login.error.passwordInvalid'),
  27. })
  28. type AccountFormValues = z.infer<typeof accountFormSchema>
  29. const InstallForm = () => {
  30. const { t } = useTranslation()
  31. const router = useRouter()
  32. const [showPassword, setShowPassword] = React.useState(false)
  33. const [loading, setLoading] = React.useState(true)
  34. const {
  35. register,
  36. handleSubmit,
  37. formState: { errors, isSubmitting },
  38. } = useForm<AccountFormValues>({
  39. resolver: zodResolver(accountFormSchema),
  40. defaultValues: {
  41. name: '',
  42. password: '',
  43. email: '',
  44. },
  45. })
  46. const onSubmit: SubmitHandler<AccountFormValues> = async (data) => {
  47. await setup({
  48. body: {
  49. ...data,
  50. },
  51. })
  52. router.push('/signin')
  53. }
  54. const handleSetting = async () => {
  55. if (isSubmitting) return
  56. handleSubmit(onSubmit)()
  57. }
  58. const { run: debouncedHandleKeyDown } = useDebounceFn(
  59. (e: React.KeyboardEvent) => {
  60. if (e.key === 'Enter') {
  61. e.preventDefault()
  62. handleSetting()
  63. }
  64. },
  65. { wait: 200 },
  66. )
  67. const handleKeyDown = useCallback(debouncedHandleKeyDown, [debouncedHandleKeyDown])
  68. useEffect(() => {
  69. fetchSetupStatus().then((res: SetupStatusResponse) => {
  70. if (res.step === 'finished') {
  71. localStorage.setItem('setup_status', 'finished')
  72. router.push(`${basePath}/signin`)
  73. }
  74. else {
  75. fetchInitValidateStatus().then((res: InitValidateStatusResponse) => {
  76. if (res.status === 'not_started')
  77. router.push(`${basePath}/init`)
  78. })
  79. }
  80. setLoading(false)
  81. })
  82. }, [])
  83. return (
  84. loading
  85. ? <Loading />
  86. : <>
  87. <div className="sm:mx-auto sm:w-full sm:max-w-md">
  88. <h2 className="text-[32px] font-bold text-text-primary">{t('login.setAdminAccount')}</h2>
  89. <p className='mt-1 text-sm text-text-secondary'>{t('login.setAdminAccountDesc')}</p>
  90. </div>
  91. <div className="mt-8 grow sm:mx-auto sm:w-full sm:max-w-md">
  92. <div className="relative">
  93. <form onSubmit={handleSubmit(onSubmit)} onKeyDown={handleKeyDown}>
  94. <div className='mb-5'>
  95. <label htmlFor="email" className="my-2 flex items-center justify-between text-sm font-medium text-text-primary">
  96. {t('login.email')}
  97. </label>
  98. <div className="mt-1 rounded-md shadow-sm">
  99. <input
  100. {...register('email')}
  101. placeholder={t('login.emailPlaceholder') || ''}
  102. className={'w-full appearance-none rounded-md border border-transparent bg-components-input-bg-normal py-[7px] pl-2 text-components-input-text-filled caret-primary-600 outline-none placeholder:text-components-input-text-placeholder hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:border-components-input-border-active focus:bg-components-input-bg-active focus:shadow-xs'}
  103. />
  104. {errors.email && <span className='text-sm text-red-400'>{t(`${errors.email?.message}`)}</span>}
  105. </div>
  106. </div>
  107. <div className='mb-5'>
  108. <label htmlFor="name" className="my-2 flex items-center justify-between text-sm font-medium text-text-primary">
  109. {t('login.name')}
  110. </label>
  111. <div className="relative mt-1 rounded-md shadow-sm">
  112. <input
  113. {...register('name')}
  114. placeholder={t('login.namePlaceholder') || ''}
  115. className={'w-full appearance-none rounded-md border border-transparent bg-components-input-bg-normal py-[7px] pl-2 text-components-input-text-filled caret-primary-600 outline-none placeholder:text-components-input-text-placeholder hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:border-components-input-border-active focus:bg-components-input-bg-active focus:shadow-xs'}
  116. />
  117. </div>
  118. {errors.name && <span className='text-sm text-red-400'>{t(`${errors.name.message}`)}</span>}
  119. </div>
  120. <div className='mb-5'>
  121. <label htmlFor="password" className="my-2 flex items-center justify-between text-sm font-medium text-text-primary">
  122. {t('login.password')}
  123. </label>
  124. <div className="relative mt-1 rounded-md shadow-sm">
  125. <input
  126. {...register('password')}
  127. type={showPassword ? 'text' : 'password'}
  128. placeholder={t('login.passwordPlaceholder') || ''}
  129. className={'w-full appearance-none rounded-md border border-transparent bg-components-input-bg-normal py-[7px] pl-2 text-components-input-text-filled caret-primary-600 outline-none placeholder:text-components-input-text-placeholder hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:border-components-input-border-active focus:bg-components-input-bg-active focus:shadow-xs'}
  130. />
  131. <div className="absolute inset-y-0 right-0 flex items-center pr-3">
  132. <button
  133. type="button"
  134. onClick={() => setShowPassword(!showPassword)}
  135. className="text-text-quaternary hover:text-text-tertiary focus:text-text-tertiary focus:outline-none"
  136. >
  137. {showPassword ? '👀' : '😝'}
  138. </button>
  139. </div>
  140. </div>
  141. <div className={classNames('mt-1 text-xs text-text-tertiary', {
  142. 'text-red-400 !text-sm': errors.password,
  143. })}>{t('login.error.passwordInvalid')}</div>
  144. </div>
  145. <div>
  146. <Button variant='primary' className='w-full' onClick={handleSetting}>
  147. {t('login.installBtn')}
  148. </Button>
  149. </div>
  150. </form>
  151. <div className="mt-2 block w-full text-xs text-text-tertiary">
  152. {t('login.license.tip')}
  153. &nbsp;
  154. <Link
  155. className='text-text-accent'
  156. target='_blank' rel='noopener noreferrer'
  157. href={'https://docs.dify.ai/user-agreement/open-source'}
  158. >{t('login.license.link')}</Link>
  159. </div>
  160. </div>
  161. </div>
  162. </>
  163. )
  164. }
  165. export default InstallForm