Files
gridpilot.gg/apps/website/app/auth/reset-password/ResetPasswordClient.tsx
2026-01-14 10:51:05 +01:00

140 lines
4.3 KiB
TypeScript

/**
* Reset Password Client Component
*
* Handles client-side reset password flow.
*/
'use client';
import { useState } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
import { ResetPasswordViewData } from '@/lib/builders/view-data/types/ResetPasswordViewData';
import { ResetPasswordTemplate } from '@/templates/auth/ResetPasswordTemplate';
import { ResetPasswordMutation } from '@/lib/mutations/auth/ResetPasswordMutation';
import { ResetPasswordViewModelBuilder } from '@/lib/builders/view-models/ResetPasswordViewModelBuilder';
import { ResetPasswordViewModel } from '@/lib/view-models/auth/ResetPasswordViewModel';
import { routes } from '@/lib/routing/RouteConfig';
interface ResetPasswordClientProps {
viewData: ResetPasswordViewData;
}
export function ResetPasswordClient({ viewData }: ResetPasswordClientProps) {
const router = useRouter();
const searchParams = useSearchParams();
// Build ViewModel from ViewData
const [viewModel, setViewModel] = useState<ResetPasswordViewModel>(() =>
ResetPasswordViewModelBuilder.build(viewData)
);
const [formData, setFormData] = useState({
newPassword: '',
confirmPassword: ''
});
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
// Validate passwords match
if (formData.newPassword !== formData.confirmPassword) {
setViewModel(prev => prev.withMutationState(false, 'Passwords do not match'));
return;
}
// Update submitting state
setViewModel(prev => prev.withMutationState(true, null));
try {
const token = searchParams.get('token');
if (!token) {
setViewModel(prev => prev.withMutationState(false, 'Invalid reset link'));
return;
}
// Execute reset password mutation
const mutation = new ResetPasswordMutation();
const result = await mutation.execute({
token,
newPassword: formData.newPassword,
});
if (result.isErr()) {
const error = result.getError();
setViewModel(prev => prev.withMutationState(false, error));
return;
}
// Success
const data = result.unwrap();
setViewModel(prev => prev.withSuccess(data.message));
// Redirect to login after a delay
setTimeout(() => {
router.push(routes.auth.login);
}, 3000);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Failed to reset password';
setViewModel(prev => prev.withMutationState(false, errorMessage));
}
};
// Toggle password visibility
const togglePassword = () => {
setViewModel(prev => prev.withUIState({
...prev.uiState,
showPassword: !prev.uiState.showPassword,
}));
};
const toggleConfirmPassword = () => {
setViewModel(prev => prev.withUIState({
...prev.uiState,
showConfirmPassword: !prev.uiState.showConfirmPassword,
}));
};
// Build viewData for template
const templateViewData: ResetPasswordViewData = {
...viewData,
showSuccess: viewModel.showSuccess,
successMessage: viewModel.successMessage || undefined,
formState: viewModel.formState,
isSubmitting: viewModel.isSubmitting,
submitError: viewModel.submitError,
};
return (
<ResetPasswordTemplate
// Spread the viewData properties
token={templateViewData.token}
returnTo={templateViewData.returnTo}
showSuccess={templateViewData.showSuccess}
successMessage={templateViewData.successMessage}
formState={templateViewData.formState}
isSubmitting={templateViewData.isSubmitting}
submitError={templateViewData.submitError}
// Add the additional props
formActions={{
setFormData,
handleSubmit,
setShowSuccess: (show) => {
if (!show) {
// Reset to initial state
setViewModel(() => ResetPasswordViewModelBuilder.build(viewData));
}
},
setShowPassword: togglePassword,
setShowConfirmPassword: toggleConfirmPassword,
}}
uiState={{
showPassword: viewModel.uiState.showPassword,
showConfirmPassword: viewModel.uiState.showConfirmPassword,
}}
mutationState={{
isPending: viewModel.mutationPending,
error: viewModel.mutationError,
}}
/>
);
}