dify/web/app/signin/components/mail-and-password-auth.tsx

168 lines
5.4 KiB
TypeScript

import type { ResponseError } from '@/service/fetch'
import { noop } from 'es-toolkit/function'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { trackEvent } from '@/app/components/base/amplitude'
import Button from '@/app/components/base/button'
import Input from '@/app/components/base/input'
import { toast } from '@/app/components/base/ui/toast'
import { emailRegex } from '@/config'
import { useLocale } from '@/context/i18n'
import Link from '@/next/link'
import { useRouter, useSearchParams } from '@/next/navigation'
import { login } from '@/service/common'
import { setWebAppAccessToken } from '@/service/webapp-auth'
import { encryptPassword } from '@/utils/encryption'
import { resolvePostLoginRedirect } from '../utils/post-login-redirect'
type MailAndPasswordAuthProps = {
isInvite: boolean
isEmailSetup: boolean
allowRegistration: boolean
}
export default function MailAndPasswordAuth({ isInvite, isEmailSetup, allowRegistration: _allowRegistration }: MailAndPasswordAuthProps) {
const { t } = useTranslation()
const locale = useLocale()
const router = useRouter()
const searchParams = useSearchParams()
const [showPassword, setShowPassword] = useState(false)
const emailFromLink = decodeURIComponent(searchParams.get('email') || '')
const [email, setEmail] = useState(emailFromLink)
const [password, setPassword] = useState('')
const [isLoading, setIsLoading] = useState(false)
const handleEmailPasswordLogin = async () => {
if (!email) {
toast.error(t('error.emailEmpty', { ns: 'login' }))
return
}
if (!emailRegex.test(email)) {
toast.error(t('error.emailInValid', { ns: 'login' }))
return
}
if (!password?.trim()) {
toast.error(t('error.passwordEmpty', { ns: 'login' }))
return
}
try {
setIsLoading(true)
const loginData: Record<string, any> = {
email,
password: encryptPassword(password),
language: locale,
remember_me: true,
}
if (isInvite)
loginData.invite_token = decodeURIComponent(searchParams.get('invite_token') as string)
const res = await login({
url: '/login',
body: loginData,
})
if (res.result === 'success') {
if (res?.data?.access_token) {
// Track login success event
setWebAppAccessToken(res.data.access_token)
}
trackEvent('user_login_success', {
method: 'email_password',
is_invite: isInvite,
})
if (isInvite) {
router.replace(`/signin/invite-settings?${searchParams.toString()}`)
}
else {
const redirectUrl = resolvePostLoginRedirect()
router.replace(redirectUrl || '/apps')
}
}
else {
toast.error(res.data)
}
}
catch (error) {
if ((error as ResponseError).code === 'authentication_failed') {
toast.error(t('error.invalidEmailOrPassword', { ns: 'login' }))
}
}
finally {
setIsLoading(false)
}
}
return (
<form onSubmit={noop}>
<div className="mb-3">
<label htmlFor="email" className="my-2 text-text-secondary system-md-semibold">
{t('email', { ns: 'login' })}
</label>
<div className="mt-1">
<Input
value={email}
onChange={e => setEmail(e.target.value)}
disabled={isInvite}
id="email"
type="email"
autoComplete="email"
placeholder={t('emailPlaceholder', { ns: 'login' }) || ''}
tabIndex={1}
/>
</div>
</div>
<div className="mb-3">
<label htmlFor="password" className="my-2 flex items-center justify-between">
<span className="text-text-secondary system-md-semibold">{t('password', { ns: 'login' })}</span>
<Link
href={`/reset-password?${searchParams.toString()}`}
className={`system-xs-regular ${isEmailSetup ? 'text-components-button-secondary-accent-text' : 'pointer-events-none text-components-button-secondary-accent-text-disabled'}`}
tabIndex={isEmailSetup ? 0 : -1}
aria-disabled={!isEmailSetup}
>
{t('forget', { ns: 'login' })}
</Link>
</label>
<div className="relative mt-1">
<Input
id="password"
value={password}
onChange={e => setPassword(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter')
handleEmailPasswordLogin()
}}
type={showPassword ? 'text' : 'password'}
autoComplete="current-password"
placeholder={t('passwordPlaceholder', { ns: 'login' }) || ''}
tabIndex={2}
/>
<div className="absolute inset-y-0 right-0 flex items-center">
<Button
type="button"
variant="ghost"
onClick={() => setShowPassword(!showPassword)}
>
{showPassword ? '👀' : '😝'}
</Button>
</div>
</div>
</div>
<div className="mb-2">
<Button
tabIndex={2}
variant="primary"
onClick={handleEmailPasswordLogin}
disabled={isLoading || !email || !password}
className="w-full"
>
{t('signBtn', { ns: 'login' })}
</Button>
</div>
</form>
)
}