website refactor

This commit is contained in:
2026-01-14 16:28:39 +01:00
parent 85e09b6f4d
commit 4b7d82ab43
119 changed files with 2403 additions and 1615 deletions

View File

@@ -1,6 +1,6 @@
/**
* Forgot Password Client Component
*
*
* Handles client-side forgot password flow.
*/
@@ -12,6 +12,7 @@ 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;
@@ -19,24 +20,67 @@ interface ForgotPasswordClientProps {
export function ForgotPasswordClient({ viewData }: ForgotPasswordClientProps) {
// Build ViewModel from ViewData
const [viewModel, setViewModel] = useState<ForgotPasswordViewModel>(() =>
const [viewModel, setViewModel] = useState<ForgotPasswordViewModel>(() =>
ForgotPasswordViewModelBuilder.build(viewData)
);
const [formData, setFormData] = useState({ email: '' });
// 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({
email: formData.email,
});
const result = await mutation.execute(formData);
if (result.isErr()) {
const error = result.getError();
@@ -68,7 +112,7 @@ export function ForgotPasswordClient({ viewData }: ForgotPasswordClientProps) {
<ForgotPasswordTemplate
viewData={templateViewData}
formActions={{
setFormData,
handleChange,
handleSubmit,
setShowSuccess: (show) => {
if (!show) {

View File

@@ -1,6 +1,6 @@
/**
* Reset Password Client Component
*
*
* Handles client-side reset password flow.
*/
@@ -13,6 +13,7 @@ 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 { ResetPasswordFormValidation } from '@/lib/utilities/authValidation';
import { routes } from '@/lib/routing/RouteConfig';
interface ResetPasswordClientProps {
@@ -22,23 +23,63 @@ interface ResetPasswordClientProps {
export function ResetPasswordClient({ viewData }: ResetPasswordClientProps) {
const router = useRouter();
const searchParams = useSearchParams();
// Build ViewModel from ViewData
const [viewModel, setViewModel] = useState<ResetPasswordViewModel>(() =>
const [viewModel, setViewModel] = useState<ResetPasswordViewModel>(() =>
ResetPasswordViewModelBuilder.build(viewData)
);
const [formData, setFormData] = useState({
newPassword: '',
confirmPassword: ''
});
// 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();
// Validate passwords match
if (formData.newPassword !== formData.confirmPassword) {
setViewModel(prev => prev.withMutationState(false, 'Passwords do not match'));
const formData = {
newPassword: viewModel.formState.fields.newPassword.value as string,
confirmPassword: viewModel.formState.fields.confirmPassword.value as string,
};
// Validate form
const validationErrors = ResetPasswordFormValidation.validateForm(formData);
if (validationErrors.length > 0) {
setViewModel(prev => {
const newFormState = {
...prev.formState,
isValid: false,
submitCount: prev.formState.submitCount + 1,
fields: {
...prev.formState.fields,
...validationErrors.reduce((acc, error) => ({
...acc,
[error.field]: {
...prev.formState.fields[error.field],
error: error.message,
touched: true,
},
}), {}),
},
};
return prev.withFormState(newFormState);
});
return;
}
@@ -68,7 +109,7 @@ export function ResetPasswordClient({ viewData }: ResetPasswordClientProps) {
// Success
const data = result.unwrap();
setViewModel(prev => prev.withSuccess(data.message));
// Redirect to login after a delay
setTimeout(() => {
router.push(routes.auth.login);
@@ -116,7 +157,7 @@ export function ResetPasswordClient({ viewData }: ResetPasswordClientProps) {
submitError={templateViewData.submitError}
// Add the additional props
formActions={{
setFormData,
handleChange,
handleSubmit,
setShowSuccess: (show) => {
if (!show) {

View File

@@ -1,6 +1,6 @@
/**
* Signup Client Component
*
*
* Handles client-side signup flow.
*/
@@ -14,6 +14,7 @@ import { SignupTemplate } from '@/templates/auth/SignupTemplate';
import { SignupMutation } from '@/lib/mutations/auth/SignupMutation';
import { SignupViewModelBuilder } from '@/lib/builders/view-models/SignupViewModelBuilder';
import { SignupViewModel } from '@/lib/view-models/auth/SignupViewModel';
import { SignupFormValidation } from '@/lib/utilities/authValidation';
interface SignupClientProps {
viewData: SignupViewData;
@@ -23,26 +24,66 @@ export function SignupClient({ viewData }: SignupClientProps) {
const router = useRouter();
const searchParams = useSearchParams();
const { refreshSession } = useAuth();
// Build ViewModel from ViewData
const [viewModel, setViewModel] = useState<SignupViewModel>(() =>
const [viewModel, setViewModel] = useState<SignupViewModel>(() =>
SignupViewModelBuilder.build(viewData)
);
const [formData, setFormData] = useState({
firstName: '',
lastName: '',
email: '',
password: '',
confirmPassword: ''
});
// 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();
// Validate passwords match
if (formData.password !== formData.confirmPassword) {
setViewModel(prev => prev.withMutationState(false, 'Passwords do not match'));
const formData = {
firstName: viewModel.formState.fields.firstName.value as string,
lastName: viewModel.formState.fields.lastName.value as string,
email: viewModel.formState.fields.email.value as string,
password: viewModel.formState.fields.password.value as string,
confirmPassword: viewModel.formState.fields.confirmPassword.value as string,
};
// Validate form
const validationErrors = SignupFormValidation.validateForm(formData);
if (validationErrors.length > 0) {
setViewModel(prev => {
const newFormState = {
...prev.formState,
isValid: false,
submitCount: prev.formState.submitCount + 1,
fields: {
...prev.formState.fields,
...validationErrors.reduce((acc, error) => ({
...acc,
[error.field]: {
...prev.formState.fields[error.field],
error: error.message,
touched: true,
},
}), {}),
},
};
return prev.withFormState(newFormState);
});
return;
}
@@ -50,15 +91,15 @@ export function SignupClient({ viewData }: SignupClientProps) {
setViewModel(prev => prev.withMutationState(true, null));
try {
// Transform to DTO format
const displayName = `${formData.firstName} ${formData.lastName}`.trim();
// Generate display name
const displayName = SignupFormValidation.generateDisplayName(formData.firstName, formData.lastName);
// Execute signup mutation
const mutation = new SignupMutation();
const result = await mutation.execute({
email: formData.email,
password: formData.password,
displayName: displayName || formData.firstName || formData.lastName,
displayName,
});
if (result.isErr()) {
@@ -69,7 +110,7 @@ export function SignupClient({ viewData }: SignupClientProps) {
// Success - refresh session and redirect
await refreshSession();
const returnTo = searchParams.get('returnTo') ?? '/onboarding';
router.push(returnTo);
} catch (error) {
@@ -105,7 +146,7 @@ export function SignupClient({ viewData }: SignupClientProps) {
<SignupTemplate
viewData={templateViewData}
formActions={{
setFormData,
handleChange,
handleSubmit,
setShowPassword: togglePassword,
setShowConfirmPassword: toggleConfirmPassword,