mirror of
https://github.com/langgenius/dify.git
synced 2024-11-16 19:59:50 +08:00
feat: supports email & code login
This commit is contained in:
parent
cea867cd06
commit
a2214e3249
|
@ -6,7 +6,11 @@ import { useTranslation } from 'react-i18next'
|
|||
const COUNT_DOWN_TIME_MS = 59000
|
||||
const COUNT_DOWN_KEY = 'leftTime'
|
||||
|
||||
export default function Countdown() {
|
||||
type CountdownProps = {
|
||||
onResend?: () => void
|
||||
}
|
||||
|
||||
export default function Countdown({ onResend }: CountdownProps) {
|
||||
const { t } = useTranslation()
|
||||
const [leftTime, setLeftTime] = useState(Number(localStorage.getItem(COUNT_DOWN_KEY) || COUNT_DOWN_TIME_MS))
|
||||
const [time] = useCountDown({
|
||||
|
@ -17,6 +21,7 @@ export default function Countdown() {
|
|||
const resend = async function () {
|
||||
setLeftTime(COUNT_DOWN_TIME_MS)
|
||||
localStorage.setItem(COUNT_DOWN_KEY, `${COUNT_DOWN_TIME_MS}`)
|
||||
onResend?.()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -3,18 +3,24 @@ import Link from 'next/link'
|
|||
import { RiArrowLeftLine, RiMailSendFill } from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useState } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { useRouter, useSearchParams } from 'next/navigation'
|
||||
import Countdown from './countdown'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Input from '@/app/components/base/input'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { emailLoginWithCode, getEMailLoginCode } from '@/service/common'
|
||||
|
||||
export default function CheckCode() {
|
||||
const router = useRouter()
|
||||
const { t } = useTranslation()
|
||||
const router = useRouter()
|
||||
const searchParams = useSearchParams()
|
||||
const email = searchParams.get('email') as string
|
||||
const token = searchParams.get('token') as string
|
||||
const [code, setVerifyCode] = useState('')
|
||||
const [loading, setIsLoading] = useState(false)
|
||||
|
||||
const verify = async () => {
|
||||
try {
|
||||
if (!code.trim()) {
|
||||
Toast.notify({
|
||||
type: 'error',
|
||||
|
@ -29,7 +35,23 @@ export default function CheckCode() {
|
|||
})
|
||||
return
|
||||
}
|
||||
router.replace('/signin?console_token=123')
|
||||
setIsLoading(true)
|
||||
const ret = await emailLoginWithCode({ email, code, token })
|
||||
localStorage.setItem('console_token', ret.data)
|
||||
router.replace('/apps')
|
||||
}
|
||||
catch (error) { console.error(error) }
|
||||
finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const resendCode = async () => {
|
||||
try {
|
||||
const ret = await getEMailLoginCode(email)
|
||||
router.replace(`/signin/check-code?token=${ret.token}&email=${encodeURIComponent(email)}`)
|
||||
}
|
||||
catch (error) { console.error(error) }
|
||||
}
|
||||
|
||||
return <div className='flex flex-col gap-3'>
|
||||
|
@ -39,7 +61,7 @@ export default function CheckCode() {
|
|||
<div className='pt-3 pb-4'>
|
||||
<h2 className='text-4xl font-semibold'>{t('login.checkCode.checkYourEmail')}</h2>
|
||||
<p className='text-text-secondary text-sm mt-2 leading-5'>
|
||||
<span dangerouslySetInnerHTML={{ __html: t('login.checkCode.tips', { email: 'evan@dify.ai' }) as string }}></span>
|
||||
<span dangerouslySetInnerHTML={{ __html: t('login.checkCode.tips', { email }) as string }}></span>
|
||||
<br />
|
||||
{t('login.checkCode.validTime')}
|
||||
</p>
|
||||
|
@ -47,9 +69,9 @@ export default function CheckCode() {
|
|||
|
||||
<form action="">
|
||||
<label htmlFor="code" className='text-text-secondary text-sm font-semibold mb-1'>{t('login.checkCode.verificationCode')}</label>
|
||||
<Input value={code} onChange={setVerifyCode} max-length={6} className='px-3 mt-1 leading-5 h-9 appearance-none' placeholder={t('login.checkCode.verificationCodePlaceholder') as string} />
|
||||
<Button className='my-3 w-full' variant='primary' onClick={verify}>{t('login.checkCode.verify')}</Button>
|
||||
<Countdown />
|
||||
<Input value={code} onChange={e => setVerifyCode(e.target.value)} max-length={6} className='px-3 mt-1 leading-5 h-9 appearance-none' placeholder={t('login.checkCode.verificationCodePlaceholder') as string} />
|
||||
<Button loading={loading} disabled={loading} className='my-3 w-full' variant='primary' onClick={verify}>{t('login.checkCode.verify')}</Button>
|
||||
<Countdown onResend={resendCode} />
|
||||
</form>
|
||||
<div className='py-2'>
|
||||
<div className='bg-gradient-to-r from-white/[0.01] via-[#101828]/8 to-white/[0.01] h-px'></div>
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSearchParams } from 'next/navigation'
|
||||
import { useRouter, useSearchParams } from 'next/navigation'
|
||||
import Input from '@/app/components/base/input'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { emailRegex } from '@/config'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { getEMailLoginCode } from '@/service/common'
|
||||
|
||||
type MailAndCodeAuthProps = {
|
||||
isInvite: boolean
|
||||
|
@ -12,11 +13,14 @@ type MailAndCodeAuthProps = {
|
|||
|
||||
export default function MailAndCodeAuth({ isInvite }: MailAndCodeAuthProps) {
|
||||
const { t } = useTranslation()
|
||||
const router = useRouter()
|
||||
const searchParams = useSearchParams()
|
||||
const emailFromLink = searchParams.get('email') as string
|
||||
const [email, setEmail] = useState(isInvite ? emailFromLink : '')
|
||||
const [loading, setIsLoading] = useState(false)
|
||||
|
||||
const handleGetEMailVerificationCode = async () => {
|
||||
try {
|
||||
if (!email) {
|
||||
Toast.notify({ type: 'error', message: t('login.error.emailEmpty') })
|
||||
return
|
||||
|
@ -29,7 +33,16 @@ export default function MailAndCodeAuth({ isInvite }: MailAndCodeAuthProps) {
|
|||
})
|
||||
return
|
||||
}
|
||||
window.location.href = '/signin/check-code'
|
||||
setIsLoading(true)
|
||||
const ret = await getEMailLoginCode(email)
|
||||
router.push(`/signin/check-code?token=${ret.token}&email=${encodeURIComponent(email)}`)
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (<form onSubmit={() => { }}>
|
||||
|
@ -39,7 +52,7 @@ export default function MailAndCodeAuth({ isInvite }: MailAndCodeAuthProps) {
|
|||
<Input id='email' type="email" disabled={isInvite} value={email} placeholder={t('login.emailPlaceholder') as string} onChange={e => setEmail(e.target.value)} className="px-3 h-9" />
|
||||
</div>
|
||||
<div className='mt-3'>
|
||||
<Button variant='primary' className='w-full' onClick={handleGetEMailVerificationCode}>{t('login.continueWithCode')}</Button>
|
||||
<Button loading={loading} disabled={loading} variant='primary' className='w-full' onClick={handleGetEMailVerificationCode}>{t('login.continueWithCode')}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -308,3 +308,9 @@ export const verifyForgotPasswordToken: Fetcher<CommonResponse & { is_valid: boo
|
|||
|
||||
export const changePasswordWithToken: Fetcher<CommonResponse, { url: string; body: { token: string; new_password: string; password_confirm: string } }> = ({ url, body }) =>
|
||||
post<CommonResponse>(url, { body })
|
||||
|
||||
export const getEMailLoginCode = (email: string) =>
|
||||
post<CommonResponse & { token: string }>('/email-code-login', { body: { email } })
|
||||
|
||||
export const emailLoginWithCode = (data: { email: string;code: string;token: string }) =>
|
||||
post<CommonResponse & { data: string }>('/email-code-login/validity', { body: data })
|
||||
|
|
Loading…
Reference in New Issue
Block a user