Files
triggerdotdev--trigger.dev/apps/webapp/app/hooks/useUser.ts
James Ritchie 49de105862 Feat(webapp): collapsible side menu (#2939)
- Better separation between main feature pages and manage pages
- Toggle via keyboard shortcut Cmd/Ctrl + B or click collapse button
- Collapsed state persisted to database (survives page refresh)
- All menu items show tooltips when collapsed
- Smooth animations using Framer Motion and CSS transitions
- Impersonation-safe (preferences not modified when impersonating)


https://github.com/user-attachments/assets/35827922-b80a-418e-8341-eb22c9bb5ed4


<!-- devin-review-badge-begin -->

---

<a
href="https://app.devin.ai/review/triggerdotdev/trigger.dev/pull/2939">
  <picture>
<source media="(prefers-color-scheme: dark)"
srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1">
<img
src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1"
alt="Open with Devin">
  </picture>
</a>
<!-- devin-review-badge-end -->
2026-01-27 17:13:21 +00:00

39 lines
1.2 KiB
TypeScript

import { type UIMatch } from "@remix-run/react";
import type { UserWithDashboardPreferences } from "~/models/user.server";
import { type loader } from "~/root";
import { useChanged } from "./useChanged";
import { useTypedMatchesData } from "./useTypedMatchData";
import { useIsImpersonating } from "./useOrganizations";
export type User = UserWithDashboardPreferences;
export function useOptionalUser(matches?: UIMatch[]): User | undefined {
const routeMatch = useTypedMatchesData<typeof loader>({
id: "root",
matches,
});
return routeMatch?.user ?? undefined;
}
export function useUser(matches?: UIMatch[]): User {
const maybeUser = useOptionalUser(matches);
if (!maybeUser) {
throw new Error(
"No user found in root loader, but user is required by useUser. If user is optional, try useOptionalUser instead."
);
}
return maybeUser;
}
export function useUserChanged(callback: (user: User | undefined) => void) {
useChanged(useOptionalUser, callback);
}
export function useHasAdminAccess(matches?: UIMatch[]): boolean {
const user = useOptionalUser(matches);
const isImpersonating = useIsImpersonating(matches);
return Boolean(user?.admin) || isImpersonating;
}