| 
                        123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193 | 
                        - 'use client'
 - import { useCallback, useState } from 'react'
 - import { useTranslation } from 'react-i18next'
 - import { useRouter, useSearchParams } from 'next/navigation'
 - import cn from 'classnames'
 - import { RiCheckboxCircleFill } from '@remixicon/react'
 - import { useCountDown } from 'ahooks'
 - import Button from '@/app/components/base/button'
 - import { changePasswordWithToken } from '@/service/common'
 - import Toast from '@/app/components/base/toast'
 - import Input from '@/app/components/base/input'
 - 
 - const validPassword = /^(?=.*[a-zA-Z])(?=.*\d).{8,}$/
 - 
 - const ChangePasswordForm = () => {
 -   const { t } = useTranslation()
 -   const router = useRouter()
 -   const searchParams = useSearchParams()
 -   const token = decodeURIComponent(searchParams.get('token') || '')
 - 
 -   const [password, setPassword] = useState('')
 -   const [confirmPassword, setConfirmPassword] = useState('')
 -   const [showSuccess, setShowSuccess] = useState(false)
 -   const [showPassword, setShowPassword] = useState(false)
 -   const [showConfirmPassword, setShowConfirmPassword] = useState(false)
 - 
 -   const showErrorMessage = useCallback((message: string) => {
 -     Toast.notify({
 -       type: 'error',
 -       message,
 -     })
 -   }, [])
 - 
 -   const getSignInUrl = () => {
 -     if (searchParams.has('invite_token')) {
 -       const params = new URLSearchParams()
 -       params.set('token', searchParams.get('invite_token') as string)
 -       return `/activate?${params.toString()}`
 -     }
 -     return '/signin'
 -   }
 - 
 -   const AUTO_REDIRECT_TIME = 5000
 -   const [leftTime, setLeftTime] = useState<number | undefined>(undefined)
 -   const [countdown] = useCountDown({
 -     leftTime,
 -     onEnd: () => {
 -       router.replace(getSignInUrl())
 -     },
 -   })
 - 
 -   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 () => {
 -     if (!valid())
 -       return
 -     try {
 -       await changePasswordWithToken({
 -         url: '/forgot-password/resets',
 -         body: {
 -           token,
 -           new_password: password,
 -           password_confirm: confirmPassword,
 -         },
 -       })
 -       setShowSuccess(true)
 -       setLeftTime(AUTO_REDIRECT_TIME)
 -     }
 -     catch (error) {
 -       console.error(error)
 -     }
 -   }, [password, token, valid, confirmPassword])
 - 
 -   return (
 -     <div className={
 -       cn(
 -         'flex flex-col items-center w-full grow justify-center',
 -         'px-6',
 -         'md:px-[108px]',
 -       )
 -     }>
 -       {!showSuccess && (
 -         <div className='flex flex-col md:w-[400px]'>
 -           <div className="w-full mx-auto">
 -             <h2 className="title-4xl-semi-bold text-text-primary">
 -               {t('login.changePassword')}
 -             </h2>
 -             <p className='mt-2 body-md-regular text-text-secondary'>
 -               {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 system-md-semibold text-text-secondary">
 -                   {t('common.account.newPassword')}
 -                 </label>
 -                 <div className='relative mt-1'>
 -                   <Input
 -                     id="password" type={showPassword ? 'text' : 'password'}
 -                     value={password}
 -                     onChange={e => setPassword(e.target.value)}
 -                     placeholder={t('login.passwordPlaceholder') || ''}
 -                   />
 - 
 -                   <div className="absolute inset-y-0 right-0 flex items-center">
 -                     <Button
 -                       type="button"
 -                       variant='ghost'
 -                       onClick={() => setShowPassword(!showPassword)}
 -                     >
 -                       {showPassword ? '👀' : '😝'}
 -                     </Button>
 -                   </div>
 -                 </div>
 -                 <div className='mt-1 body-xs-regular text-text-secondary'>{t('login.error.passwordInvalid')}</div>
 -               </div>
 -               {/* Confirm Password */}
 -               <div className='mb-5'>
 -                 <label htmlFor="confirmPassword" className="my-2 system-md-semibold text-text-secondary">
 -                   {t('common.account.confirmPassword')}
 -                 </label>
 -                 <div className='relative mt-1'>
 -                   <Input
 -                     id="confirmPassword"
 -                     type={showConfirmPassword ? 'text' : 'password'}
 -                     value={confirmPassword}
 -                     onChange={e => setConfirmPassword(e.target.value)}
 -                     placeholder={t('login.confirmPasswordPlaceholder') || ''}
 -                   />
 -                   <div className="absolute inset-y-0 right-0 flex items-center">
 -                     <Button
 -                       type="button"
 -                       variant='ghost'
 -                       onClick={() => setShowConfirmPassword(!showConfirmPassword)}
 -                     >
 -                       {showConfirmPassword ? '👀' : '😝'}
 -                     </Button>
 -                   </div>
 -                 </div>
 -               </div>
 -               <div>
 -                 <Button
 -                   variant='primary'
 -                   className='w-full'
 -                   onClick={handleChangePassword}
 -                 >
 -                   {t('login.changePasswordBtn')}
 -                 </Button>
 -               </div>
 -             </div>
 -           </div>
 -         </div>
 -       )}
 -       {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-14 h-14 rounded-2xl border border-components-panel-border-subtle shadow-lg font-bold">
 -               <RiCheckboxCircleFill className='w-6 h-6 text-text-success' />
 -             </div>
 -             <h2 className="title-4xl-semi-bold text-text-primary">
 -               {t('login.passwordChangedTip')}
 -             </h2>
 -           </div>
 -           <div className="w-full mx-auto mt-6">
 -             <Button variant='primary' className='w-full' onClick={() => {
 -               setLeftTime(undefined)
 -               router.replace(getSignInUrl())
 -             }}>{t('login.passwordChanged')} ({Math.round(countdown / 1000)}) </Button>
 -           </div>
 -         </div>
 -       )}
 -     </div>
 -   )
 - }
 - 
 - export default ChangePasswordForm
 
 
  |