Skip to content

Commit 249b70b

Browse files
committed
Moved the Kapa/Help stuff into a component, out of root
1 parent a1a6c96 commit 249b70b

File tree

2 files changed

+110
-94
lines changed

2 files changed

+110
-94
lines changed

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

Lines changed: 90 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ import { SideMenuSection } from "./SideMenuSection";
7979
import { useShortcutKeys } from "~/hooks/useShortcutKeys";
8080
import { AISparkleIcon } from "~/assets/icons/AISparkleIcon";
8181
import { ShortcutKey } from "../primitives/ShortcutKey";
82+
import { useFeatures } from "~/hooks/useFeatures";
83+
import { useKapa } from "~/root";
8284

8385
type SideMenuUser = Pick<User, "email" | "admin"> & { isImpersonating: boolean };
8486
export type SideMenuProject = Pick<
@@ -109,17 +111,6 @@ export function SideMenu({
109111
const currentPlan = useCurrentPlan();
110112
const { isConnected } = useDevPresence();
111113
const isFreeUser = currentPlan?.v3Subscription?.isPaying === false;
112-
const buttonRef = useRef<HTMLButtonElement>(null);
113-
114-
useShortcutKeys({
115-
shortcut: { key: "a", modifiers: ["mod", "shift"] },
116-
action: (e) => {
117-
e.preventDefault();
118-
if (buttonRef.current) {
119-
buttonRef.current.click();
120-
}
121-
},
122-
});
123114

124115
useEffect(() => {
125116
const handleScroll = () => {
@@ -291,37 +282,7 @@ export function SideMenu({
291282
</div>
292283
<div className="flex flex-col gap-1 border-t border-grid-bright p-1">
293284
<div className="flex w-full items-center justify-between">
294-
<HelpAndFeedback />
295-
<TooltipProvider disableHoverableContent>
296-
<Tooltip>
297-
<TooltipTrigger asChild>
298-
<div>
299-
<Button
300-
ref={buttonRef}
301-
variant="small-menu-item"
302-
data-action="ask-ai"
303-
shortcut={{ modifiers: ["mod"], key: "/", enabledOnInputElements: true }}
304-
hideShortcutKey
305-
data-modal-override-open-class-ask-ai="true"
306-
onClick={() => {
307-
if (typeof window.Kapa === "function") {
308-
window.Kapa("open");
309-
}
310-
}}
311-
>
312-
<AISparkleIcon className="size-5" />
313-
</Button>
314-
</div>
315-
</TooltipTrigger>
316-
<TooltipContent
317-
side="top"
318-
className="flex items-center gap-1 py-1.5 pl-2.5 pr-2 text-xs"
319-
>
320-
Ask AI
321-
<ShortcutKey shortcut={{ modifiers: ["mod"], key: "/" }} variant="medium/bright" />
322-
</TooltipContent>
323-
</Tooltip>
324-
</TooltipProvider>
285+
<HelpAndAI />
325286
</div>
326287
{isFreeUser && (
327288
<FreePlanUsage
@@ -584,3 +545,90 @@ function SelectorDivider() {
584545
</svg>
585546
);
586547
}
548+
549+
function HelpAndAI() {
550+
const features = useFeatures();
551+
const kapa = useKapa();
552+
553+
useEffect(() => {
554+
if (!features.isManagedCloud || !kapa?.websiteId) return;
555+
556+
loadScriptIfNotExists(kapa.websiteId);
557+
558+
const kapaInterval = setInterval(() => {
559+
if (typeof window.Kapa === "function") {
560+
clearInterval(kapaInterval);
561+
window.Kapa("render");
562+
}
563+
}, 100);
564+
565+
// Clear interval on unmount to prevent memory leaks
566+
return () => {
567+
clearInterval(kapaInterval);
568+
if (typeof window.Kapa === "function") {
569+
window.Kapa("unmount");
570+
}
571+
};
572+
}, [features.isManagedCloud, kapa?.websiteId]);
573+
574+
return (
575+
<>
576+
<HelpAndFeedback />
577+
<TooltipProvider disableHoverableContent>
578+
<Tooltip>
579+
<TooltipTrigger asChild>
580+
<div>
581+
<Button
582+
variant="small-menu-item"
583+
data-action="ask-ai"
584+
shortcut={{ modifiers: ["mod"], key: "/", enabledOnInputElements: true }}
585+
hideShortcutKey
586+
data-modal-override-open-class-ask-ai="true"
587+
onClick={() => {
588+
if (typeof window.Kapa === "function") {
589+
window.Kapa("open");
590+
}
591+
}}
592+
>
593+
<AISparkleIcon className="size-5" />
594+
</Button>
595+
</div>
596+
</TooltipTrigger>
597+
<TooltipContent side="top" className="flex items-center gap-1 py-1.5 pl-2.5 pr-2 text-xs">
598+
Ask AI
599+
<ShortcutKey shortcut={{ modifiers: ["mod"], key: "/" }} variant="medium/bright" />
600+
</TooltipContent>
601+
</Tooltip>
602+
</TooltipProvider>
603+
</>
604+
);
605+
}
606+
607+
function loadScriptIfNotExists(websiteId: string) {
608+
const scriptSrc = "https://widget.kapa.ai/kapa-widget.bundle.js";
609+
610+
if (document.querySelector(`script[src="${scriptSrc}"]`)) {
611+
return;
612+
}
613+
614+
const script = document.createElement("script");
615+
script.async = true;
616+
script.src = scriptSrc;
617+
618+
const attributes = {
619+
"data-website-id": websiteId,
620+
"data-project-name": "Trigger.dev",
621+
"data-project-color": "#6366F1",
622+
"data-project-logo": "https://content.trigger.dev/trigger-logo-triangle.png",
623+
"data-render-on-load": "false",
624+
"data-button-hide": "true",
625+
"data-modal-disclaimer-bg-color": "#1A1B1F",
626+
"data-modal-disclaimer-text-color": "#878C99",
627+
};
628+
629+
Object.entries(attributes).forEach(([key, value]) => {
630+
script.setAttribute(key, value);
631+
});
632+
633+
document.head.appendChild(script);
634+
}

apps/webapp/app/root.tsx

Lines changed: 20 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
import type { LinksFunction, LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
22
import type { ShouldRevalidateFunction } from "@remix-run/react";
3-
import { Links, LiveReload, Meta, Outlet, Scripts, ScrollRestoration } from "@remix-run/react";
3+
import {
4+
Links,
5+
LiveReload,
6+
Meta,
7+
Outlet,
8+
Scripts,
9+
ScrollRestoration,
10+
useMatches,
11+
} from "@remix-run/react";
412
import { type UseDataFunctionReturn, typedjson, useTypedLoaderData } from "remix-typedjson";
513
import { ExternalScripts } from "remix-utils/external-scripts";
614
import { commitSession, getSession } from "~/models/message.server";
@@ -16,6 +24,7 @@ import { getUser } from "./services/session.server";
1624
import { appEnvTitleTag } from "./utils";
1725
import { type Handle } from "./utils/handle";
1826
import { useEffect } from "react";
27+
import { useTypedMatchesData } from "./hooks/useTypedMatchData";
1928

2029
declare global {
2130
interface Window {
@@ -42,6 +51,15 @@ export const meta: MetaFunction = ({ data }) => {
4251
];
4352
};
4453

54+
export function useKapa() {
55+
const matches = useMatches();
56+
const routeMatch = useTypedMatchesData<typeof loader>({
57+
id: "root",
58+
matches,
59+
});
60+
return routeMatch?.kapa;
61+
}
62+
4563
export const loader = async ({ request }: LoaderFunctionArgs) => {
4664
const session = await getSession(request.headers.get("cookie"));
4765
const toastMessage = session.get("toastMessage") as ToastMessage;
@@ -100,30 +118,9 @@ export function ErrorBoundary() {
100118
}
101119

102120
export default function App() {
103-
const { posthogProjectKey, kapa, features } = useTypedLoaderData<typeof loader>();
121+
const { posthogProjectKey, kapa } = useTypedLoaderData<typeof loader>();
104122
usePostHog(posthogProjectKey);
105123

106-
useEffect(() => {
107-
if (!features.isManagedCloud || !kapa.websiteId) return;
108-
109-
loadScriptIfNotExists(kapa.websiteId);
110-
111-
const kapaInterval = setInterval(() => {
112-
if (typeof window.Kapa === "function") {
113-
clearInterval(kapaInterval);
114-
window.Kapa("render");
115-
}
116-
}, 100);
117-
118-
// Clear interval on unmount to prevent memory leaks
119-
return () => {
120-
clearInterval(kapaInterval);
121-
if (typeof window.Kapa === "function") {
122-
window.Kapa("unmount");
123-
}
124-
};
125-
}, [features.isManagedCloud, kapa.websiteId]);
126-
127124
return (
128125
<>
129126
<html lang="en" className="h-full">
@@ -143,32 +140,3 @@ export default function App() {
143140
</>
144141
);
145142
}
146-
147-
function loadScriptIfNotExists(websiteId: string) {
148-
const scriptSrc = "https://widget.kapa.ai/kapa-widget.bundle.js";
149-
150-
if (document.querySelector(`script[src="${scriptSrc}"]`)) {
151-
return;
152-
}
153-
154-
const script = document.createElement("script");
155-
script.async = true;
156-
script.src = scriptSrc;
157-
158-
const attributes = {
159-
"data-website-id": websiteId,
160-
"data-project-name": "Trigger.dev",
161-
"data-project-color": "#6366F1",
162-
"data-project-logo": "https://content.trigger.dev/trigger-logo-triangle.png",
163-
"data-render-on-load": "false",
164-
"data-button-hide": "true",
165-
"data-modal-disclaimer-bg-color": "#1A1B1F",
166-
"data-modal-disclaimer-text-color": "#878C99",
167-
};
168-
169-
Object.entries(attributes).forEach(([key, value]) => {
170-
script.setAttribute(key, value);
171-
});
172-
173-
document.head.appendChild(script);
174-
}

0 commit comments

Comments
 (0)