Files
gitmost/apps/client/src/features/auth/hooks/use-auth.ts
T
agent_coder e9d5d493d3 fix(#290 review): drop stale cached children on boot + gate size warn (F1/F2/F3)
F1 (MEDIUM regression): a collapsed-but-cached branch showed STALE children on
re-expand after reload (the cache keeps children of any ever-expanded branch;
refreshOpenBranches only refreshes OPEN branches, but the fetch guard skips a branch
that has cached children). New pruneCollapsedChildren(tree, openIds) resets children
to [] (keeps hasChildren) for every node NOT in the persisted open-set, recursing
into open nodes — a once-per-mount boot effect. A pruned collapsed branch is then the
'unloaded' shape handleToggle re-fetches, so its first expand reconciles fresh (as
pre-cache). Open branches keep their children (refreshOpenBranches handles them, no
double fetch). Test: a collapsed cached branch with a stale child fetches fresh on
first expand after boot.
F2: gate the >4MB size-guard console.warn behind the writeFailureWarned once-flag
(like the quota branch) so editing a huge tree no longer re-warns every ~500ms; test
that an oversized tree is not persisted + warns exactly once.
F3: narrow the use-auth privacy comment (only tree caches are swept; other
localStorage entries remain).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 22:25:05 +03:00

182 lines
4.6 KiB
TypeScript

import { useState } from "react";
import {
forgotPassword,
login,
logout,
passwordReset,
setupWorkspace,
verifyUserToken,
} from "@/features/auth/services/auth-service";
import { useNavigate } from "react-router-dom";
import { useAtom } from "jotai";
import { currentUserAtom } from "@/features/user/atoms/current-user-atom";
import {
IForgotPassword,
ILogin,
IPasswordReset,
ISetupWorkspace,
IVerifyUserToken,
} from "@/features/auth/types/auth.types";
import { notifications } from "@mantine/notifications";
import { IAcceptInvite } from "@/features/workspace/types/workspace.types.ts";
import { acceptInvitation } from "@/features/workspace/services/workspace-service.ts";
import APP_ROUTE, { getPostLoginRedirect } from "@/lib/app-route.ts";
import { RESET } from "jotai/utils";
import { useTranslation } from "react-i18next";
import { clearPersistedTreeCaches } from "@/features/page/tree/atoms/tree-data-atom";
export default function useAuth() {
const { t } = useTranslation();
const [isLoading, setIsLoading] = useState(false);
const navigate = useNavigate();
const [, setCurrentUser] = useAtom(currentUserAtom);
const handleSignIn = async (data: ILogin) => {
setIsLoading(true);
try {
await login(data);
setIsLoading(false);
navigate(getPostLoginRedirect());
} catch (err) {
setIsLoading(false);
const message = err.response?.data?.message;
notifications.show({
message,
color: "red",
});
}
};
const handleInvitationSignUp = async (data: IAcceptInvite) => {
setIsLoading(true);
try {
const response = await acceptInvitation(data);
setIsLoading(false);
if (response?.requiresLogin) {
notifications.show({
message: t(
"Account created successfully. Please log in to set up two-factor authentication.",
),
});
navigate(APP_ROUTE.AUTH.LOGIN);
} else {
navigate(APP_ROUTE.HOME);
}
} catch (err) {
setIsLoading(false);
notifications.show({
message: err.response?.data.message,
color: "red",
});
}
};
const handleSetupWorkspace = async (data: ISetupWorkspace) => {
setIsLoading(true);
try {
await setupWorkspace(data);
setIsLoading(false);
navigate(APP_ROUTE.HOME);
} catch (err) {
setIsLoading(false);
notifications.show({
message: err.response?.data.message,
color: "red",
});
}
};
const handlePasswordReset = async (data: IPasswordReset) => {
setIsLoading(true);
try {
const response = await passwordReset(data);
setIsLoading(false);
if (response?.requiresLogin) {
notifications.show({
message: t(
"Password reset was successful. Please log in with your new password.",
),
});
navigate(APP_ROUTE.AUTH.LOGIN);
} else {
navigate(APP_ROUTE.HOME);
notifications.show({
message: t("Password reset was successful"),
});
}
} catch (err) {
setIsLoading(false);
notifications.show({
message: err.response?.data.message,
color: "red",
});
}
};
const handleLogout = async () => {
setCurrentUser(RESET);
// Purge the persisted sidebar tree caches (they contain page titles) so the
// cached page titles aren't left readable in localStorage on a shared
// machine. (Only the tree caches are swept; other localStorage entries
// remain.)
clearPersistedTreeCaches();
await logout();
window.location.replace(`${APP_ROUTE.AUTH.LOGIN}?logout=1`);
};
const handleForgotPassword = async (data: IForgotPassword) => {
setIsLoading(true);
try {
await forgotPassword(data);
setIsLoading(false);
return true;
} catch (err) {
console.log(err);
setIsLoading(false);
notifications.show({
message: err.response?.data.message,
color: "red",
});
return false;
}
};
const handleVerifyUserToken = async (data: IVerifyUserToken) => {
setIsLoading(true);
try {
await verifyUserToken(data);
setIsLoading(false);
} catch (err) {
console.log(err);
setIsLoading(false);
notifications.show({
message: err.response?.data.message,
color: "red",
});
}
};
return {
signIn: handleSignIn,
invitationSignup: handleInvitationSignUp,
setupWorkspace: handleSetupWorkspace,
forgotPassword: handleForgotPassword,
passwordReset: handlePasswordReset,
verifyUserToken: handleVerifyUserToken,
logout: handleLogout,
isLoading,
};
}