Files
outline/app/hooks/useLastVisitedPath.tsx
Tom Moor 1f097b0fdd chore: resolve no-explicit-any lint warnings in plugins (#12237)
* chore: resolve no-explicit-any lint warnings in plugins

Replaces uses of `any` in the plugins directory with concrete types,
`unknown`, or structured type assertions, addressing the remaining
typescript-eslint(no-explicit-any) warnings flagged by oxlint.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore: address review feedback in GitLabIssueProvider

Drop trailing semicolon from log string and add early return in
`destroyNamespace` when neither `user_id` nor `full_path` is present
to avoid an unnecessary full-scan transaction.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-01 08:29:58 -04:00

116 lines
3.3 KiB
TypeScript

import { useCallback, useRef } from "react";
import { getCookie, removeCookie, setCookie } from "tiny-cookie";
import usePersistedState, {
setPersistedState,
} from "~/hooks/usePersistedState";
import Logger from "~/utils/Logger";
import history from "~/utils/history";
import { isAllowedLoginRedirect } from "~/utils/urls";
/**
* Hook to set locally and return the document or collection that the user last visited. This is
* used to redirect the user back to the last page they were on, if preferred.
*
* @returns A tuple of the last visited path and a method to set it.
*/
export function useLastVisitedPath(): [string, (path: string) => void] {
const [lastVisitedPath, setLastVisitedPath] = usePersistedState<string>(
"lastVisitedPath",
"/",
{ listen: false }
);
const setPathAsLastVisitedPath = useCallback(
(path: string) => {
if (isAllowedLoginRedirect(path) && path !== lastVisitedPath) {
setLastVisitedPath(path);
}
},
[lastVisitedPath, setLastVisitedPath]
);
return [lastVisitedPath, setPathAsLastVisitedPath] as const;
}
/**
* Hook that automatically tracks the current path as the last visited path.
* This uses a ref to track the previous path and updates localStorage directly
* without using useEffect to avoid React Doctor warnings.
*
* @param currentPath The current path to track.
*/
export function useTrackLastVisitedPath(currentPath: string): void {
const prevPathRef = useRef<string>();
// Update localStorage directly if path has changed
if (
prevPathRef.current !== currentPath &&
isAllowedLoginRedirect(currentPath)
) {
prevPathRef.current = currentPath;
setPersistedState("lastVisitedPath", currentPath);
}
}
/**
* Sets the path that the user visited before being asked to login.
*
* @param path The path to set as the post login path.
*/
export function setPostLoginPath(path: string) {
const key = "postLoginRedirectPath";
if (isAllowedLoginRedirect(path)) {
setCookie(key, path, { expires: 1 });
try {
sessionStorage.setItem(key, path);
} catch (_err) {
// If the session storage is full or inaccessible, we can't do anything about it.
}
}
}
/**
* Hook to set locally and return the path that the user visited before being asked
* to login.
*
* @returns A tuple of getter and setter for the post login path.
*/
export function usePostLoginPath() {
const key = "postLoginRedirectPath";
const getter = useCallback(() => {
let path;
try {
path = sessionStorage.getItem(key) || getCookie(key);
} catch (_err) {
// Expected error if the session storage is full or inaccessible.
}
if (path) {
Logger.info("lifecycle", "Spending post login path", { path });
// Remove the cookie once the app has been navigated to the post login path. We dont
// do this immediately as React StrictMode will render multiple times.
const cleanup = history.listen(() => {
try {
sessionStorage.removeItem(key);
} catch (_err) {
// Expected error if the session storage is full or inaccessible.
}
removeCookie(key);
cleanup?.();
});
if (isAllowedLoginRedirect(path)) {
return path;
}
}
return undefined;
}, []);
return [getter, setPostLoginPath] as const;
}