Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

mail-and-password-auth.tsx 5.0KB

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