dify/web/app/forgot-password/ChangePasswordForm.tsx
GQ1994 e7659ecd9d
revert https://github.com/langgenius/dify/pull/19497 (19497) (#19807)
Co-authored-by: qingguo <qingguo@lexin.com>
2025-05-17 12:32:27 +08:00

177 lines
6.2 KiB
TypeScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import useSWR from 'swr'
import { useSearchParams } from 'next/navigation'
import { basePath } from '@/utils/var'
import cn from 'classnames'
import { CheckCircleIcon } from '@heroicons/react/24/solid'
import Input from '../components/base/input'
import Button from '@/app/components/base/button'
import { changePasswordWithToken, verifyForgotPasswordToken } from '@/service/common'
import Toast from '@/app/components/base/toast'
import Loading from '@/app/components/base/loading'
const validPassword = /^(?=.*[a-zA-Z])(?=.*\d).{8,}$/
const ChangePasswordForm = () => {
const { t } = useTranslation()
const searchParams = useSearchParams()
const token = searchParams.get('token')
const verifyTokenParams = {
url: '/forgot-password/validity',
body: { token },
}
const { data: verifyTokenRes, mutate: revalidateToken } = useSWR(verifyTokenParams, verifyForgotPasswordToken, {
revalidateOnFocus: false,
})
const [password, setPassword] = useState('')
const [confirmPassword, setConfirmPassword] = useState('')
const [showSuccess, setShowSuccess] = useState(false)
const showErrorMessage = useCallback((message: string) => {
Toast.notify({
type: 'error',
message,
})
}, [])
const valid = useCallback(() => {
if (!password.trim()) {
showErrorMessage(t('login.error.passwordEmpty'))
return false
}
if (!validPassword.test(password)) {
showErrorMessage(t('login.error.passwordInvalid'))
return false
}
if (password !== confirmPassword) {
showErrorMessage(t('common.account.notEqual'))
return false
}
return true
}, [password, confirmPassword, showErrorMessage, t])
const handleChangePassword = useCallback(async () => {
const token = searchParams.get('token') || ''
if (!valid())
return
try {
await changePasswordWithToken({
url: '/forgot-password/resets',
body: {
token,
new_password: password,
password_confirm: confirmPassword,
},
})
setShowSuccess(true)
}
catch {
await revalidateToken()
}
}, [confirmPassword, password, revalidateToken, searchParams, valid])
return (
<div className={
cn(
'flex w-full grow flex-col items-center justify-center',
'px-6',
'md:px-[108px]',
)
}>
{!verifyTokenRes && <Loading />}
{verifyTokenRes && !verifyTokenRes.is_valid && (
<div className="flex flex-col md:w-[400px]">
<div className="mx-auto w-full">
<div className="mb-3 flex h-20 w-20 items-center justify-center rounded-[20px] border border-divider-regular bg-components-option-card-option-bg p-5 text-[40px] font-bold shadow-lg">🤷</div>
<h2 className="text-[32px] font-bold text-text-primary">{t('login.invalid')}</h2>
</div>
<div className="mx-auto mt-6 w-full">
<Button variant='primary' className='w-full !text-sm'>
<a href="https://dify.ai">{t('login.explore')}</a>
</Button>
</div>
</div>
)}
{verifyTokenRes && verifyTokenRes.is_valid && !showSuccess && (
<div className='flex flex-col md:w-[400px]'>
<div className="mx-auto w-full">
<h2 className="text-[32px] font-bold text-text-primary">
{t('login.changePassword')}
</h2>
<p className='mt-1 text-sm text-text-secondary'>
{t('login.changePasswordTip')}
</p>
</div>
<div className="mx-auto mt-6 w-full">
<div className="relative">
{/* Password */}
<div className='mb-5'>
<label htmlFor="password" className="my-2 flex items-center justify-between text-sm font-medium text-text-primary">
{t('common.account.newPassword')}
</label>
<Input
id="password"
type='password'
value={password}
onChange={e => setPassword(e.target.value)}
placeholder={t('login.passwordPlaceholder') || ''}
className='mt-1'
/>
<div className='mt-1 text-xs text-text-secondary'>{t('login.error.passwordInvalid')}</div>
</div>
{/* Confirm Password */}
<div className='mb-5'>
<label htmlFor="confirmPassword" className="my-2 flex items-center justify-between text-sm font-medium text-text-primary">
{t('common.account.confirmPassword')}
</label>
<Input
id="confirmPassword"
type='password'
value={confirmPassword}
onChange={e => setConfirmPassword(e.target.value)}
placeholder={t('login.confirmPasswordPlaceholder') || ''}
className='mt-1'
/>
</div>
<div>
<Button
variant='primary'
className='w-full !text-sm'
onClick={handleChangePassword}
>
{t('common.operation.reset')}
</Button>
</div>
</div>
</div>
</div>
)}
{verifyTokenRes && verifyTokenRes.is_valid && showSuccess && (
<div className="flex flex-col md:w-[400px]">
<div className="mx-auto w-full">
<div className="mb-3 flex h-20 w-20 items-center justify-center rounded-[20px] border border-divider-regular bg-components-option-card-option-bg p-5 text-[40px] font-bold shadow-lg">
<CheckCircleIcon className='h-10 w-10 text-[#039855]' />
</div>
<h2 className="text-[32px] font-bold text-text-primary">
{t('login.passwordChangedTip')}
</h2>
</div>
<div className="mx-auto mt-6 w-full">
<Button variant='primary' className='w-full'>
<a href={`${basePath}/signin`}>{t('login.passwordChanged')}</a>
</Button>
</div>
</div>
)}
</div>
)
}
export default ChangePasswordForm