installForm.tsx 7.0 KB

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