Skip to content

Commit 43d8ada

Browse files
committed
fix(webapp): make the admin Cmd+Esc shortcut work globally
The admin shortcut (Cmd/Ctrl+Esc to open the admin dashboard, or to stop impersonating while impersonating) was registered inside the account menu, which only mounts on project pages. So it did nothing on the account, organization settings and admin pages. Move it into a GlobalShortcuts component mounted once at the app root, so it works on every page regardless of which side menu is rendered, and remove the account-menu registration so it never fires twice. GlobalShortcuts is the place to add future app-wide shortcuts.
1 parent 289ee72 commit 43d8ada

3 files changed

Lines changed: 42 additions & 19 deletions

File tree

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { useNavigate, useSubmit } from "@remix-run/react";
2+
import { useShortcutKeys } from "~/hooks/useShortcutKeys";
3+
import { useOptionalUser } from "~/hooks/useUser";
4+
import { adminPath } from "~/utils/pathBuilder";
5+
6+
/**
7+
* App-wide keyboard shortcuts, mounted once at the root so they work on every page regardless of
8+
* which side menu (if any) is rendered. Add new global shortcuts here.
9+
*
10+
* Renders nothing. Reads the user from the root loader via `useOptionalUser`, so on unauthenticated
11+
* pages there is simply no admin and nothing is registered.
12+
*/
13+
export function GlobalShortcuts() {
14+
const user = useOptionalUser();
15+
const navigate = useNavigate();
16+
const submit = useSubmit();
17+
18+
const isImpersonating = Boolean(user?.isImpersonating);
19+
const isAdmin = Boolean(user?.admin) || isImpersonating;
20+
21+
// ⌘/Ctrl+Esc: admins jump to the admin dashboard; while impersonating the same shortcut stops
22+
// impersonating instead. `enabledOnInputElements` so it still fires while a field is focused.
23+
useShortcutKeys({
24+
shortcut: isAdmin
25+
? { modifiers: ["mod"], key: "esc", enabledOnInputElements: true }
26+
: undefined,
27+
action: () => {
28+
if (isImpersonating) {
29+
submit(null, { action: "/resources/impersonation", method: "delete" });
30+
} else {
31+
navigate(adminPath());
32+
}
33+
},
34+
});
35+
36+
return null;
37+
}

apps/webapp/app/components/navigation/SideMenu.tsx

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {
33
ChevronRightIcon,
44
ExclamationTriangleIcon,
55
} from "@heroicons/react/24/outline";
6-
import { useFetcher, useNavigate, useNavigation, useSubmit } from "@remix-run/react";
6+
import { useFetcher, useNavigation, useSubmit } from "@remix-run/react";
77
import { LayoutGroup, motion } from "framer-motion";
88
import {
99
type CSSProperties,
@@ -1490,29 +1490,13 @@ function AccountMenuItems({
14901490
function AccountMenu({ isAdmin, isImpersonating }: { isAdmin: boolean; isImpersonating: boolean }) {
14911491
const [isOpen, setIsOpen] = useState(false);
14921492
const navigation = useNavigation();
1493-
const navigate = useNavigate();
1494-
const submit = useSubmit();
14951493

14961494
useEffect(() => {
14971495
setIsOpen(false);
14981496
}, [navigation.location?.pathname]);
14991497

1500-
const stopImpersonating = () =>
1501-
submit(null, { action: "/resources/impersonation", method: "delete" });
1502-
1503-
useShortcutKeys({
1504-
shortcut: isAdmin
1505-
? { modifiers: ["mod"], key: "esc", enabledOnInputElements: true }
1506-
: undefined,
1507-
action: () => {
1508-
if (isImpersonating) {
1509-
stopImpersonating();
1510-
} else {
1511-
navigate(adminPath());
1512-
}
1513-
},
1514-
});
1515-
1498+
// The ⌘/Ctrl+Esc admin shortcut lives in <GlobalShortcuts> so it works everywhere, not only where
1499+
// this menu is mounted.
15161500
return (
15171501
<Popover onOpenChange={(open) => setIsOpen(open)} open={isOpen}>
15181502
<SimpleTooltip

apps/webapp/app/root.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type { ToastMessage } from "~/models/message.server";
77
import { commitSession, getSession } from "~/models/message.server";
88
import tailwindStylesheetUrl from "~/tailwind.css";
99
import { RouteErrorDisplay } from "./components/ErrorDisplay";
10+
import { GlobalShortcuts } from "./components/GlobalShortcuts";
1011
import { AppContainer, MainCenteredContainer } from "./components/layout/AppLayout";
1112
import { ShortcutsProvider } from "./components/primitives/ShortcutsProvider";
1213
import { Toast } from "./components/primitives/Toast";
@@ -131,6 +132,7 @@ export default function App() {
131132
<body className="h-full overflow-hidden bg-background-dimmed antialiased">
132133
<ShortcutsProvider>
133134
<TimezoneSetter />
135+
<GlobalShortcuts />
134136
<Outlet />
135137
<Toast />
136138
</ShortcutsProvider>

0 commit comments

Comments
 (0)