303 lines
8.9 KiB
TypeScript
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,
|
||
|
|
};
|
||
|
|
};
|