e9d5d493d3
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>
182 lines
4.6 KiB
TypeScript
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,
|
|
};
|
|
}
|