/* Copyright (C) 2023-2026 QuantumNous This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ import { useNavigate } from '@tanstack/react-router' import { CheckIcon, CopyIcon } from 'lucide-react' import { useState } from 'react' import { useTranslation } from 'react-i18next' import { toast } from 'sonner' import { Alert, AlertDescription } from '@/components/ui/alert' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { useCountdown } from '@/hooks/use-countdown' import { api } from '@/lib/api' import { copyToClipboard } from '@/lib/copy-to-clipboard' import { handleServerError } from '@/lib/handle-server-error' import { AuthOperationError } from '@/lib/secure-verification' import { createServerError } from '@/lib/server-error-message' import { AuthLayout } from '../auth-layout' export type ResetPasswordSearchParams = { email?: string token?: string } type ResetPasswordConfirmProps = ResetPasswordSearchParams export function ResetPasswordConfirm({ email, token, }: ResetPasswordConfirmProps) { const { t } = useTranslation() const navigate = useNavigate() const [newPassword, setNewPassword] = useState('') const [loading, setLoading] = useState(false) const [copied, setCopied] = useState(false) const { secondsLeft, isActive, start: startCountdown, } = useCountdown({ initialSeconds: 30 }) const isValidResetLink = Boolean(email && token) async function handleSubmit() { if (!isValidResetLink || !email || !token) { toast.error(t('Invalid reset link, please request a new password reset')) return } startCountdown() setLoading(true) try { const res = await api.post('/api/user/reset', { email, token }, { skipBusinessError: true, } as Record) if (res?.data?.success) { const password = res.data.data setNewPassword(password) const copySuccess = await copyToClipboard(password) if (copySuccess) { toast.success( t('Password reset and copied to clipboard: {{password}}', { password, }) ) } else { toast.success(t('Password reset: {{password}}', { password })) } } else { handleServerError(createServerError(res.data, t('Request failed'))) } } catch (error) { handleServerError(AuthOperationError.from(error)) } finally { setLoading(false) } } async function handleCopy() { if (!newPassword) return const copySuccess = await copyToClipboard(newPassword) if (copySuccess) { setCopied(true) toast.success( t('Password copied to clipboard: {{password}}', { password: newPassword, }) ) setTimeout(() => setCopied(false), 2000) } } return (

{t('Reset password')}

{newPassword ? t('auth.resetPasswordConfirm.success') : t('auth.resetPasswordConfirm.description')}

{!isValidResetLink && ( {t('Invalid reset link, please request a new password reset.')} )}
{newPassword && (

{t('Password has been copied to clipboard')}

)} {!newPassword && ( )}
) }