fix(ui): clean usage links for sessions without the organizations query

The organizations query only runs for premium sessions, and a disabled query stays
pending forever, so the URL cleanup never ran for other sessions. Gate on isLoading
instead, and give the access denied toast an id so a re-run effect shows it once
This commit is contained in:
Jesus Velarde 2026-09-26 19:12:48 -07:00
parent 8cf03d6fd9
commit a46433f3cb
4 changed files with 18 additions and 6 deletions

View file

@ -170,7 +170,7 @@ vi.mock("@/app/(dashboard)/hooks/useIsOrgAdmin", () => ({
}));
vi.mock("@/app/(dashboard)/hooks/organizations/useOrganizations", () => ({
useOrganizations: vi.fn(() => ({ data: [], isPending: false })),
useOrganizations: vi.fn(() => ({ data: undefined, isPending: true, isLoading: false })),
}));
vi.mock("@/app/(dashboard)/hooks/users/useCurrentUser", () => ({
@ -1495,7 +1495,9 @@ describe("UsagePage", () => {
await waitFor(() => {
expect(lastQuery()).toBe("?range=30d");
});
expect(warning).toHaveBeenCalledWith("You don't have access to that usage view");
expect(warning).toHaveBeenCalledWith("You don't have access to that usage view", {
id: "usage-url-access-denied",
});
expect(screen.queryByTestId("entity-usage")).not.toBeInTheDocument();
});
@ -1508,7 +1510,9 @@ describe("UsagePage", () => {
await waitFor(() => {
expect(lastQuery()).toBe("");
});
expect(warning).toHaveBeenCalledWith("You don't have access to that usage view");
expect(warning).toHaveBeenCalledWith("You don't have access to that usage view", {
id: "usage-url-access-denied",
});
expect(mockUserDailyActivityAggregatedCall).not.toHaveBeenCalledWith(
expect.anything(),
expect.anything(),

View file

@ -150,17 +150,18 @@ const UsagePage: React.FC<UsagePageProps> = ({ teams, organizations }) => {
// For non-admins or "my-usage" view, always pass their own user_id
const effectiveUserId = usageView === "my-usage" || !isAdmin ? userID || null : urlParams.user;
const { isPending: organizationsPending } = useOrganizations();
const { isLoading: organizationsLoading } = useOrganizations();
const allowedViews = useMemo(
() => allowedUsageOptions(userRole, canViewTagUsage, isOrgAdmin),
[userRole, canViewTagUsage, isOrgAdmin],
);
const accessSettled = userRole !== null && !organizationsPending;
const accessSettled = userRole !== null && !organizationsLoading;
useEffect(() => {
if (!accessSettled) return;
const cleanup = cleanUsageUrl(urlParams, { allowedViews, isAdmin });
if (!cleanup) return;
if (cleanup.deniedAccess) toast.warning("You don't have access to that usage view");
if (cleanup.deniedAccess)
toast.warning("You don't have access to that usage view", { id: "usage-url-access-denied" });
void setUrlParams(cleanup.patch);
}, [accessSettled, urlParams, allowedViews, isAdmin, setUrlParams]);

View file

@ -37,6 +37,11 @@ describe("toast", () => {
expect(sonner.success).toHaveBeenCalledWith("saved", { description: "Model x", duration: 1500 });
});
it("passes an id through so repeated toasts collapse into one", () => {
toast.warning("no access", { id: "denied" });
expect(sonner.warning).toHaveBeenCalledWith("no access", expect.objectContaining({ id: "denied" }));
});
it("dismiss clears every toast", () => {
toast.dismiss();
expect(sonner.dismiss).toHaveBeenCalledWith();

View file

@ -7,6 +7,7 @@ export type ToastKind = "success" | "info" | "warning" | "error";
export type ToastOptions = {
readonly description?: ReactNode;
readonly durationMs?: number;
readonly id?: string;
};
type ErrorFacts = {
@ -127,6 +128,7 @@ const titleFor = ({ status, proxyType }: ErrorFacts): string => {
const show = (kind: ToastKind, message: ReactNode, options?: ToastOptions): void => {
sonner[kind](message, {
id: options?.id,
description: options?.description,
duration: options?.durationMs ?? DEFAULT_DURATION_MS[kind],
});