FactuGES_ERP/modules/identity/src/web/account/controllers/use-account-page-controller.ts
david 64b90888c3 feat(identity): add Spanish localization for account management
- Introduced `es.json` for Spanish translations in account management.
- Created account routes and controllers for managing user accounts.
- Implemented hooks and API calls for fetching and updating account profiles.
- Developed forms and validation schemas for account profile and password management.
- Added error handling and feedback mechanisms for account actions.
- Built UI components for account settings page, including password change dialog.
- Integrated translations for new UI components and error messages.
2026-08-18 00:56:04 +02:00

303 lines
8.9 KiB
TypeScript

import { applyValidationErrorCollection } from "@erp/core";
import { formHasAnyDirty, getUserErrorMessage } from "@erp/core/client";
import { useHookForm } from "@erp/core/hooks";
import { useIdentityAuthSession } from "@erp/identity/client";
import { isValidationErrorCollection } from "@repo/rdx-ddd";
import {
focusFirstInputFormError,
getFirstFormErrorMessage,
preventEnterKeySubmitForm,
showErrorToast,
showSuccessToast,
showWarningToast,
} from "@repo/rdx-ui/helpers";
import { useCallback, useEffect, useId, useMemo, useState, type SubmitEvent } from "react";
import type { FieldErrors } from "react-hook-form";
import { useTranslation } from "../../i18n";
import {
type AccountPasswordForm,
AccountPasswordFormSchema,
type AccountProfileForm,
AccountProfileFormSchema,
} from "../entities";
import {
useChangeCurrentAccountPasswordMutation,
useCurrentAccountQuery,
useUpdateCurrentAccountMutation,
} from "../shared";
import {
buildAccountPageErrorViewModel,
buildAccountProfileForm,
buildUpdateAccountProfileParams,
} from "../utils";
const EMPTY_PROFILE_FORM: AccountProfileForm = {
name: "",
email: "",
avatarUrl: "",
languageCode: "es",
};
const EMPTY_PASSWORD_FORM: AccountPasswordForm = {
currentPassword: "",
newPassword: "",
confirmPassword: "",
};
export const useAccountPageController = () => {
const { t } = useTranslation();
const { account: sessionAccount, logout, updateAccount } = useIdentityAuthSession();
const profileFormId = useId();
const passwordFormId = useId();
const [passwordSuccessVersion, setPasswordSuccessVersion] = useState(0);
const accountQuery = useCurrentAccountQuery();
const updateProfileMutation = useUpdateCurrentAccountMutation();
const changePasswordMutation = useChangeCurrentAccountPasswordMutation();
const profileInitialValues = useMemo<AccountProfileForm>(() => {
if (!accountQuery.data) {
return EMPTY_PROFILE_FORM;
}
return buildAccountProfileForm(accountQuery.data);
}, [accountQuery.data]);
const profileForm = useHookForm<AccountProfileForm>({
resolverSchema: AccountProfileFormSchema,
initialValues: profileInitialValues,
disabled: updateProfileMutation.isPending,
});
const passwordForm = useHookForm<AccountPasswordForm>({
resolverSchema: AccountPasswordFormSchema,
initialValues: EMPTY_PASSWORD_FORM,
disabled: changePasswordMutation.isPending,
});
useEffect(() => {
if (!accountQuery.data || profileForm.formState.isDirty) {
return;
}
profileForm.reset(buildAccountProfileForm(accountQuery.data), {
keepDirty: false,
});
}, [accountQuery.data, profileForm, profileForm.formState.isDirty]);
const pageError = useMemo(() => {
if (!accountQuery.error) {
return null;
}
return buildAccountPageErrorViewModel(accountQuery.error, t);
}, [accountQuery.error, t]);
const inlineProfileError = useMemo(() => {
if (!updateProfileMutation.error) {
return null;
}
const userMessage = getUserErrorMessage(updateProfileMutation.error);
return userMessage;
}, [updateProfileMutation.error]);
const inlinePasswordError = useMemo(() => {
if (!changePasswordMutation.error) {
return null;
}
const userMessage = getUserErrorMessage(changePasswordMutation.error);
return userMessage;
}, [changePasswordMutation.error]);
const resetProfileForm = useCallback(() => {
if (!accountQuery.data) {
return;
}
profileForm.reset(buildAccountProfileForm(accountQuery.data), {
keepDirty: false,
});
}, [accountQuery.data, profileForm]);
const resetPasswordForm = useCallback(() => {
passwordForm.reset(EMPTY_PASSWORD_FORM, {
keepDirty: false,
});
}, [passwordForm]);
const clearPasswordDialogState = useCallback(() => {
passwordForm.reset(EMPTY_PASSWORD_FORM, {
keepDirty: false,
keepErrors: false,
keepTouched: false,
keepIsSubmitted: false,
keepSubmitCount: false,
});
passwordForm.clearErrors();
changePasswordMutation.reset();
}, [changePasswordMutation, passwordForm]);
const profileSubmitHandler = profileForm.handleSubmit(
async (formData) => {
if (!formHasAnyDirty(profileForm.formState.dirtyFields)) {
showWarningToast(
t("account.feedback.noProfileChanges.title"),
t("account.feedback.noProfileChanges.message")
);
return;
}
try {
const updated = await updateProfileMutation.mutateAsync(
buildUpdateAccountProfileParams({
form: formData,
dirtyFields: profileForm.formState.dirtyFields,
})
);
updateAccount({
id: updated.id,
email: updated.email,
name: updated.name,
avatarUrl: updated.avatarUrl,
languageCode: updated.languageCode,
});
profileForm.reset(buildAccountProfileForm(updated), {
keepDirty: false,
});
showSuccessToast(
t("account.feedback.profileSuccess.title"),
t("account.feedback.profileSuccess.message")
);
} catch (error: unknown) {
if (isValidationErrorCollection(error)) {
applyValidationErrorCollection(profileForm, error);
focusFirstInputFormError(profileForm.formState.errors, profileForm);
showWarningToast(
t("account.feedback.validation.title"),
t("account.feedback.validation.message")
);
return;
}
const userMessage = getUserErrorMessage(error);
showErrorToast(userMessage.title, userMessage.description);
}
},
(errors: FieldErrors<AccountProfileForm>) => {
focusFirstInputFormError(errors, profileForm);
showWarningToast(
t("account.feedback.validation.title"),
getFirstFormErrorMessage(errors, profileForm) ?? t("account.feedback.validation.message")
);
}
);
const passwordSubmitHandler = passwordForm.handleSubmit(
async (formData) => {
if (!formHasAnyDirty(passwordForm.formState.dirtyFields)) {
showWarningToast(
t("account.feedback.noPasswordChanges.title"),
t("account.feedback.noPasswordChanges.message")
);
return;
}
try {
await changePasswordMutation.mutateAsync({
current_password: formData.currentPassword,
new_password: formData.newPassword,
});
passwordForm.reset(EMPTY_PASSWORD_FORM, {
keepDirty: false,
});
setPasswordSuccessVersion((value) => value + 1);
showSuccessToast(
t("account.feedback.passwordSuccess.title"),
t("account.feedback.passwordSuccess.message")
);
} catch (error: unknown) {
if (isValidationErrorCollection(error)) {
applyValidationErrorCollection(passwordForm, error);
focusFirstInputFormError(passwordForm.formState.errors, passwordForm);
showWarningToast(
t("account.feedback.validation.title"),
t("account.feedback.validation.message")
);
return;
}
const userMessage = getUserErrorMessage(error);
showErrorToast(userMessage.title, userMessage.description);
}
},
(errors: FieldErrors<AccountPasswordForm>) => {
focusFirstInputFormError(errors, passwordForm);
showWarningToast(
t("account.feedback.validation.title"),
getFirstFormErrorMessage(errors, passwordForm) ?? t("account.feedback.validation.message")
);
}
);
const onProfileSubmit = (event: SubmitEvent<HTMLFormElement>) => {
event.stopPropagation();
profileSubmitHandler(event);
};
const onPasswordSubmit = (event: SubmitEvent<HTMLFormElement>) => {
event.stopPropagation();
passwordSubmitHandler(event);
};
const retry = useCallback(async () => {
await accountQuery.refetch();
}, [accountQuery]);
const handleLogout = useCallback(() => {
logout()
.then(() => {
showSuccessToast(
t("account.feedback.logoutSuccess.title"),
t("account.feedback.logoutSuccess.message")
);
})
.catch((error: unknown) => {
const userMessage = getUserErrorMessage(error);
showErrorToast(userMessage.title, userMessage.description);
});
}, [logout, t]);
return {
account: accountQuery.data,
sessionAccount,
profileForm,
profileFormId,
passwordForm,
passwordFormId,
pageError,
inlineProfileError,
inlinePasswordError,
isInitialLoading: accountQuery.isLoading,
isRetrying: accountQuery.isFetching && !accountQuery.isLoading,
isSavingProfile: updateProfileMutation.isPending,
isChangingPassword: changePasswordMutation.isPending,
onFormKeyDown: preventEnterKeySubmitForm,
onProfileSubmit,
onPasswordSubmit,
retry,
resetProfileForm,
resetPasswordForm,
clearPasswordDialogState,
passwordSuccessVersion,
handleLogout,
};
};