mail-and-password-auth.tsx 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. import Link from 'next/link'
  2. import { useState } from 'react'
  3. import { useTranslation } from 'react-i18next'
  4. import { useRouter, useSearchParams } from 'next/navigation'
  5. import { useContext } from 'use-context-selector'
  6. import Button from '@/app/components/base/button'
  7. import Toast from '@/app/components/base/toast'
  8. import { emailRegex } from '@/config'
  9. import { login } from '@/service/common'
  10. import Input from '@/app/components/base/input'
  11. import I18NContext from '@/context/i18n'
  12. type MailAndPasswordAuthProps = {
  13. isInvite: boolean
  14. allowRegistration: boolean
  15. }
  16. const passwordRegex = /^(?=.*[a-zA-Z])(?=.*\d).{8,}$/
  17. export default function MailAndPasswordAuth({ isInvite, allowRegistration }: MailAndPasswordAuthProps) {
  18. const { t } = useTranslation()
  19. const { locale } = useContext(I18NContext)
  20. const router = useRouter()
  21. const searchParams = useSearchParams()
  22. const [showPassword, setShowPassword] = useState(false)
  23. const emailFromLink = decodeURIComponent(searchParams.get('email') || '')
  24. const [email, setEmail] = useState(emailFromLink)
  25. const [password, setPassword] = useState('')
  26. const [isLoading, setIsLoading] = useState(false)
  27. const handleEmailPasswordLogin = async () => {
  28. if (!email) {
  29. Toast.notify({ type: 'error', message: t('login.error.emailEmpty') })
  30. return
  31. }
  32. if (!emailRegex.test(email)) {
  33. Toast.notify({
  34. type: 'error',
  35. message: t('login.error.emailInValid'),
  36. })
  37. return
  38. }
  39. if (!password?.trim()) {
  40. Toast.notify({ type: 'error', message: t('login.error.passwordEmpty') })
  41. return
  42. }
  43. if (!passwordRegex.test(password)) {
  44. Toast.notify({
  45. type: 'error',
  46. message: t('login.error.passwordInvalid'),
  47. })
  48. return
  49. }
  50. try {
  51. setIsLoading(true)
  52. const loginData: Record<string, any> = {
  53. email,
  54. password,
  55. language: locale,
  56. remember_me: true,
  57. }
  58. if (isInvite)
  59. loginData.invite_token = decodeURIComponent(searchParams.get('invite_token') as string)
  60. const res = await login({
  61. url: '/login',
  62. body: loginData,
  63. })
  64. if (res.result === 'success') {
  65. if (isInvite) {
  66. router.replace(`/signin/invite-settings?${searchParams.toString()}`)
  67. }
  68. else {
  69. localStorage.setItem('console_token', res.data.access_token)
  70. localStorage.setItem('refresh_token', res.data.refresh_token)
  71. router.replace('/apps')
  72. }
  73. }
  74. else if (res.code === 'account_not_found') {
  75. if (allowRegistration) {
  76. const params = new URLSearchParams()
  77. params.append('email', encodeURIComponent(email))
  78. params.append('token', encodeURIComponent(res.data))
  79. router.replace(`/reset-password/check-code?${params.toString()}`)
  80. }
  81. else {
  82. Toast.notify({
  83. type: 'error',
  84. message: t('login.error.registrationNotAllowed'),
  85. })
  86. }
  87. }
  88. else {
  89. Toast.notify({
  90. type: 'error',
  91. message: res.data,
  92. })
  93. }
  94. }
  95. finally {
  96. setIsLoading(false)
  97. }
  98. }
  99. return <form onSubmit={() => { }}>
  100. {/* 登录页输入框 */}
  101. <div className='mb-3'>
  102. <label htmlFor="email" className="my-2 system-md-semibold text-text-secondary">
  103. {t('login.email')}
  104. </label>
  105. <div className="mt-1">
  106. <Input
  107. value={email}
  108. onChange={e => setEmail(e.target.value)}
  109. disabled={isInvite}
  110. id="email"
  111. type="email"
  112. autoComplete="email"
  113. placeholder={t('login.emailPlaceholder') || ''}
  114. tabIndex={1}
  115. />
  116. </div>
  117. </div>
  118. <div className='mb-3'>
  119. <label htmlFor="password" className="my-2 flex items-center justify-between">
  120. <span className='system-md-semibold text-text-secondary'>{t('login.password')}</span>
  121. </label>
  122. <div className="relative mt-1">
  123. <Input
  124. id="password"
  125. value={password}
  126. onChange={e => setPassword(e.target.value)}
  127. onKeyDown={(e) => {
  128. if (e.key === 'Enter')
  129. handleEmailPasswordLogin()
  130. }}
  131. type={showPassword ? 'text' : 'password'}
  132. autoComplete="current-password"
  133. placeholder={t('login.passwordPlaceholder') || ''}
  134. tabIndex={2}
  135. />
  136. {/* <div className="absolute inset-y-0 right-0 flex items-center">
  137. <Button
  138. type="button"
  139. variant='ghost'
  140. onClick={() => setShowPassword(!showPassword)}
  141. >
  142. {showPassword ? '👀' : '😝'}
  143. </Button>
  144. </div> */}
  145. </div>
  146. <div style={{textAlign: 'right'}}>
  147. <Link href={`/reset-password?${searchParams.toString()}`} className='system-xs-regular text-components-button-secondary-accent-text'>
  148. {t('login.forget')}
  149. </Link>
  150. </div>
  151. </div>
  152. <div className='mb-2'>
  153. <Button
  154. tabIndex={2}
  155. variant='primary'
  156. onClick={handleEmailPasswordLogin}
  157. disabled={isLoading || !email || !password}
  158. className="w-full"
  159. >{t('login.signBtn')}</Button>
  160. </div>
  161. </form>
  162. }