Files
gridpilot.gg/apps/website/client-wrapper/ForgotPasswordClient.tsx
2026-01-18 23:43:58 +01:00

130 lines
3.9 KiB
TypeScript

/**
* Forgot Password Client Component
*
* Handles client-side forgot password flow.
*/
'use client';
import { useState } from 'react';
import { ForgotPasswordViewData } from '@/lib/builders/view-data/types/ForgotPasswordViewData';
import { ForgotPasswordTemplate } from '@/templates/auth/ForgotPasswordTemplate';
import { ForgotPasswordMutation } from '@/lib/mutations/auth/ForgotPasswordMutation';
import { ForgotPasswordViewModelBuilder } from '@/lib/builders/view-models/ForgotPasswordViewModelBuilder';
import { ForgotPasswordViewModel } from '@/lib/view-models/auth/ForgotPasswordViewModel';
import { ForgotPasswordFormValidation } from '@/lib/utilities/authValidation';
interface ForgotPasswordClientProps {
viewData: ForgotPasswordViewData;
}
export function ForgotPasswordClient({ viewData }: ForgotPasswordClientProps) {
// Build ViewModel from ViewData
const [viewModel, setViewModel] = useState<ForgotPasswordViewModel>(() =>
ForgotPasswordViewModelBuilder.build(viewData)
);
// Handle form field changes
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setViewModel(prev => {
const newFormState = {
...prev.formState,
fields: {
...prev.formState.fields,
[name]: {
...prev.formState.fields[name as keyof typeof prev.formState.fields],
value,
touched: true,
error: undefined,
},
},
};
return prev.withFormState(newFormState);
});
};
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = {
email: viewModel.formState.fields.email.value as string,
};
// Validate form
const validationErrors = ForgotPasswordFormValidation.validateForm(formData);
if (validationErrors.length > 0) {
setViewModel(prev => {
const newFormState = {
...prev.formState,
isValid: false,
submitCount: prev.formState.submitCount + 1,
fields: {
...prev.formState.fields,
email: {
...prev.formState.fields.email,
error: validationErrors.find(e => e.field === 'email')?.message,
touched: true,
},
},
};
return prev.withFormState(newFormState);
});
return;
}
// Update submitting state
setViewModel(prev => prev.withMutationState(true, null));
try {
// Execute forgot password mutation
const mutation = new ForgotPasswordMutation();
const result = await mutation.execute(formData);
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, data.magicLink || null));
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Failed to send reset link';
setViewModel(prev => prev.withMutationState(false, errorMessage));
}
};
// Build viewData for template
const templateViewData: ForgotPasswordViewData = {
...viewData,
showSuccess: viewModel.showSuccess,
successMessage: viewModel.successMessage || undefined,
magicLink: viewModel.magicLink || undefined,
formState: viewModel.formState,
isSubmitting: viewModel.isSubmitting,
submitError: viewModel.submitError,
};
return (
<ForgotPasswordTemplate
viewData={templateViewData}
formActions={{
handleChange,
handleSubmit,
setShowSuccess: (show) => {
if (!show) {
// Reset to initial state
setViewModel(() => ForgotPasswordViewModelBuilder.build(viewData));
}
},
}}
mutationState={{
isPending: viewModel.mutationPending,
error: viewModel.mutationError,
}}
/>
);
}