Some checks failed
CI / lint-typecheck (pull_request) Failing after 12s
CI / tests (pull_request) Has been skipped
CI / contract-tests (pull_request) Has been skipped
CI / e2e-tests (pull_request) Has been skipped
CI / comment-pr (pull_request) Has been skipped
CI / commit-types (pull_request) Has been skipped
127 lines
3.9 KiB
TypeScript
127 lines
3.9 KiB
TypeScript
/**
|
|
* Forgot Password Client Component
|
|
*
|
|
* Handles client-side forgot password flow.
|
|
*/
|
|
|
|
'use client';
|
|
|
|
import { ClientWrapperProps } from '@/lib/contracts/components/ComponentContracts';
|
|
import { ForgotPasswordMutation } from '@/lib/mutations/auth/ForgotPasswordMutation';
|
|
import { ForgotPasswordFormValidation } from '@/lib/utilities/authValidation';
|
|
import { ForgotPasswordViewData } from '@/lib/view-data/ForgotPasswordViewData';
|
|
import { ForgotPasswordViewModel } from '@/lib/view-models/auth/ForgotPasswordViewModel';
|
|
import { ForgotPasswordTemplate } from '@/templates/auth/ForgotPasswordTemplate';
|
|
import { useState } from 'react';
|
|
|
|
export function ForgotPasswordClient({ viewData }: ClientWrapperProps<ForgotPasswordViewData>) {
|
|
// Build ViewModel from ViewData
|
|
const [viewModel, setViewModel] = useState<ForgotPasswordViewModel>(() =>
|
|
new ForgotPasswordViewModel(viewData.returnTo, viewData.formState)
|
|
);
|
|
|
|
// 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(() => new ForgotPasswordViewModel(viewData.returnTo, viewData.formState));
|
|
}
|
|
},
|
|
}}
|
|
mutationState={{
|
|
isPending: viewModel.mutationPending,
|
|
error: viewModel.mutationError,
|
|
}}
|
|
/>
|
|
);
|
|
}
|