Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

installForm.tsx 8.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  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, login, setup } from '@/service/common'
  15. import type { InitValidateStatusResponse, SetupStatusResponse } from '@/models/common'
  16. import useDocumentTitle from '@/hooks/use-document-title'
  17. import { useDocLink } from '@/context/i18n'
  18. import { validPassword } from '@/config'
  19. const accountFormSchema = z.object({
  20. email: z
  21. .string()
  22. .min(1, { message: 'login.error.emailInValid' })
  23. .email('login.error.emailInValid'),
  24. name: z.string().min(1, { message: 'login.error.nameEmpty' }),
  25. password: z.string().min(8, {
  26. message: 'login.error.passwordLengthInValid',
  27. }).regex(validPassword, 'login.error.passwordInvalid'),
  28. })
  29. type AccountFormValues = z.infer<typeof accountFormSchema>
  30. const InstallForm = () => {
  31. useDocumentTitle('')
  32. const { t } = useTranslation()
  33. const docLink = useDocLink()
  34. const router = useRouter()
  35. const [showPassword, setShowPassword] = React.useState(false)
  36. const [loading, setLoading] = React.useState(true)
  37. const {
  38. register,
  39. handleSubmit,
  40. formState: { errors, isSubmitting },
  41. } = useForm<AccountFormValues>({
  42. resolver: zodResolver(accountFormSchema),
  43. defaultValues: {
  44. name: '',
  45. password: '',
  46. email: '',
  47. },
  48. })
  49. const onSubmit: SubmitHandler<AccountFormValues> = async (data) => {
  50. // First, setup the admin account
  51. await setup({
  52. body: {
  53. ...data,
  54. },
  55. })
  56. // Then, automatically login with the same credentials
  57. const loginRes = await login({
  58. url: '/login',
  59. body: {
  60. email: data.email,
  61. password: data.password,
  62. },
  63. })
  64. // Store tokens and redirect to apps if login successful
  65. if (loginRes.result === 'success') {
  66. localStorage.setItem('console_token', loginRes.data.access_token)
  67. localStorage.setItem('refresh_token', loginRes.data.refresh_token)
  68. router.replace('/apps')
  69. }
  70. else {
  71. // Fallback to signin page if auto-login fails
  72. router.replace('/signin')
  73. }
  74. }
  75. const handleSetting = async () => {
  76. if (isSubmitting) return
  77. handleSubmit(onSubmit)()
  78. }
  79. const { run: debouncedHandleKeyDown } = useDebounceFn(
  80. (e: React.KeyboardEvent) => {
  81. if (e.key === 'Enter') {
  82. e.preventDefault()
  83. handleSetting()
  84. }
  85. },
  86. { wait: 200 },
  87. )
  88. const handleKeyDown = useCallback(debouncedHandleKeyDown, [debouncedHandleKeyDown])
  89. useEffect(() => {
  90. fetchSetupStatus().then((res: SetupStatusResponse) => {
  91. if (res.step === 'finished') {
  92. localStorage.setItem('setup_status', 'finished')
  93. router.push('/signin')
  94. }
  95. else {
  96. fetchInitValidateStatus().then((res: InitValidateStatusResponse) => {
  97. if (res.status === 'not_started')
  98. router.push('/init')
  99. })
  100. }
  101. setLoading(false)
  102. })
  103. }, [])
  104. return (
  105. loading
  106. ? <Loading />
  107. : <>
  108. <div className="sm:mx-auto sm:w-full sm:max-w-md">
  109. <h2 className="text-[32px] font-bold text-text-primary">{t('login.setAdminAccount')}</h2>
  110. <p className='mt-1 text-sm text-text-secondary'>{t('login.setAdminAccountDesc')}</p>
  111. </div>
  112. <div className="mt-8 grow sm:mx-auto sm:w-full sm:max-w-md">
  113. <div className="relative">
  114. <form onSubmit={handleSubmit(onSubmit)} onKeyDown={handleKeyDown}>
  115. <div className='mb-5'>
  116. <label htmlFor="email" className="my-2 flex items-center justify-between text-sm font-medium text-text-primary">
  117. {t('login.email')}
  118. </label>
  119. <div className="mt-1 rounded-md shadow-sm">
  120. <input
  121. {...register('email')}
  122. placeholder={t('login.emailPlaceholder') || ''}
  123. className={'system-sm-regular w-full appearance-none rounded-md border border-transparent bg-components-input-bg-normal px-3 py-[7px] 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'}
  124. />
  125. {errors.email && <span className='text-sm text-red-400'>{t(`${errors.email?.message}`)}</span>}
  126. </div>
  127. </div>
  128. <div className='mb-5'>
  129. <label htmlFor="name" className="my-2 flex items-center justify-between text-sm font-medium text-text-primary">
  130. {t('login.name')}
  131. </label>
  132. <div className="relative mt-1 rounded-md shadow-sm">
  133. <input
  134. {...register('name')}
  135. placeholder={t('login.namePlaceholder') || ''}
  136. className={'system-sm-regular w-full appearance-none rounded-md border border-transparent bg-components-input-bg-normal px-3 py-[7px] 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'}
  137. />
  138. </div>
  139. {errors.name && <span className='text-sm text-red-400'>{t(`${errors.name.message}`)}</span>}
  140. </div>
  141. <div className='mb-5'>
  142. <label htmlFor="password" className="my-2 flex items-center justify-between text-sm font-medium text-text-primary">
  143. {t('login.password')}
  144. </label>
  145. <div className="relative mt-1 rounded-md shadow-sm">
  146. <input
  147. {...register('password')}
  148. type={showPassword ? 'text' : 'password'}
  149. placeholder={t('login.passwordPlaceholder') || ''}
  150. className={'system-sm-regular w-full appearance-none rounded-md border border-transparent bg-components-input-bg-normal px-3 py-[7px] 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'}
  151. />
  152. <div className="absolute inset-y-0 right-0 flex items-center pr-3">
  153. <button
  154. type="button"
  155. onClick={() => setShowPassword(!showPassword)}
  156. className="text-text-quaternary hover:text-text-tertiary focus:text-text-tertiary focus:outline-none"
  157. >
  158. {showPassword ? '👀' : '😝'}
  159. </button>
  160. </div>
  161. </div>
  162. <div className={classNames('mt-1 text-xs text-text-secondary', {
  163. 'text-red-400 !text-sm': errors.password,
  164. })}>{t('login.error.passwordInvalid')}</div>
  165. </div>
  166. <div>
  167. <Button variant='primary' className='w-full' onClick={handleSetting}>
  168. {t('login.installBtn')}
  169. </Button>
  170. </div>
  171. </form>
  172. <div className="mt-2 block w-full text-xs text-text-secondary">
  173. {t('login.license.tip')}
  174. &nbsp;
  175. <Link
  176. className='text-text-accent'
  177. target='_blank' rel='noopener noreferrer'
  178. href={docLink('/policies/open-source')}
  179. >{t('login.license.link')}</Link>
  180. </div>
  181. </div>
  182. </div>
  183. </>
  184. )
  185. }
  186. export default InstallForm