File size: 6,181 Bytes
a8b3f00 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 |
'use client'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import useSWR from 'swr'
import { useSearchParams } from 'next/navigation'
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()
}
}, [password, revalidateToken, token, valid])
return (
<div className={
cn(
'flex flex-col items-center w-full grow justify-center',
'px-6',
'md:px-[108px]',
)
}>
{!verifyTokenRes && <Loading />}
{verifyTokenRes && !verifyTokenRes.is_valid && (
<div className="flex flex-col md:w-[400px]">
<div className="w-full mx-auto">
<div className="mb-3 flex justify-center items-center w-20 h-20 p-5 rounded-[20px] border border-gray-100 shadow-lg text-[40px] font-bold">🤷♂️</div>
<h2 className="text-[32px] font-bold text-gray-900">{t('login.invalid')}</h2>
</div>
<div className="w-full mx-auto mt-6">
<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="w-full mx-auto">
<h2 className="text-[32px] font-bold text-gray-900">
{t('login.changePassword')}
</h2>
<p className='mt-1 text-sm text-gray-600'>
{t('login.changePasswordTip')}
</p>
</div>
<div className="w-full mx-auto mt-6">
<div className="bg-white">
{/* Password */}
<div className='mb-5'>
<label htmlFor="password" className="my-2 flex items-center justify-between text-sm font-medium text-gray-900">
{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-gray-900">
{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="w-full mx-auto">
<div className="mb-3 flex justify-center items-center w-20 h-20 p-5 rounded-[20px] border border-gray-100 shadow-lg text-[40px] font-bold">
<CheckCircleIcon className='w-10 h-10 text-[#039855]' />
</div>
<h2 className="text-[32px] font-bold text-gray-900">
{t('login.passwordChangedTip')}
</h2>
</div>
<div className="w-full mx-auto mt-6">
<Button variant='primary' className='w-full'>
<a href="/signin">{t('login.passwordChanged')}</a>
</Button>
</div>
</div>
)}
</div>
)
}
export default ChangePasswordForm
|