Skip to content

Commit aad83fb

Browse files
committed
server-side layout
1 parent 42c10a9 commit aad83fb

File tree

3 files changed

+142
-124
lines changed

3 files changed

+142
-124
lines changed

apps/dashboard/app/(main)/websites/[id]/_components/tabs/tracking-setup-tab.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -397,7 +397,9 @@ function OptionToggle({
397397
onClick={onToggle}
398398
type="button"
399399
>
400-
<Switch checked={isEnabled} className="shrink-0" />
400+
<span aria-hidden="true" className="pointer-events-none shrink-0">
401+
<Switch checked={isEnabled} tabIndex={-1} />
402+
</span>
401403
<div className="min-w-0 flex-1">
402404
<span className="font-medium text-sm">{option.title}</span>
403405
<p className="text-muted-foreground text-xs">{option.description}</p>
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
"use client";
2+
3+
import type { InferSelectModel, websites } from "@databuddy/db";
4+
import { useQueryClient } from "@tanstack/react-query";
5+
import { useAtom } from "jotai";
6+
import { usePathname } from "next/navigation";
7+
import { toast } from "sonner";
8+
import { WebsiteErrorState } from "@/components/website-error-state";
9+
import { isAnalyticsRefreshingAtom } from "@/stores/jotai/filterAtoms";
10+
import { AnalyticsToolbar } from "./analytics-toolbar";
11+
import { WebsiteTrackingSetupTab } from "./tabs/tracking-setup-tab";
12+
13+
type Website = InferSelectModel<typeof websites>;
14+
15+
const NO_TOOLBAR_ROUTES = [
16+
"/assistant",
17+
"/map",
18+
"/flags",
19+
"/databunny",
20+
"/settings",
21+
"/users",
22+
"/agent",
23+
"/pulse",
24+
];
25+
26+
interface WebsiteLayoutClientProps {
27+
children: React.ReactNode;
28+
websiteId: string;
29+
initialWebsite: Website | null;
30+
initialTrackingSetup: boolean;
31+
}
32+
33+
export function WebsiteLayoutClient({
34+
children,
35+
websiteId,
36+
initialWebsite,
37+
initialTrackingSetup,
38+
}: WebsiteLayoutClientProps) {
39+
const pathname = usePathname();
40+
const queryClient = useQueryClient();
41+
const [isRefreshing, setIsRefreshing] = useAtom(isAnalyticsRefreshingAtom);
42+
43+
const isDemoRoute = pathname?.startsWith("/demo/");
44+
const hideToolbar = NO_TOOLBAR_ROUTES.some((route) =>
45+
pathname.includes(route)
46+
);
47+
48+
if (!initialWebsite) {
49+
return <WebsiteErrorState error={{ data: { code: "NOT_FOUND" } }} />;
50+
}
51+
52+
const isTrackingSetup = isDemoRoute ? true : initialTrackingSetup;
53+
const showTrackingSetup = !(isDemoRoute || isTrackingSetup);
54+
55+
const handleRefresh = async () => {
56+
setIsRefreshing(true);
57+
try {
58+
await Promise.all([
59+
queryClient.invalidateQueries({ queryKey: ["websites", websiteId] }),
60+
queryClient.invalidateQueries({
61+
queryKey: ["websites", "isTrackingSetup", websiteId],
62+
}),
63+
queryClient.invalidateQueries({
64+
queryKey: ["dynamic-query", websiteId],
65+
}),
66+
queryClient.invalidateQueries({
67+
queryKey: ["batch-dynamic-query", websiteId],
68+
}),
69+
]);
70+
toast.success("Data refreshed");
71+
} catch {
72+
toast.error("Failed to refresh data");
73+
} finally {
74+
setIsRefreshing(false);
75+
}
76+
};
77+
78+
const renderContent = () => {
79+
if (hideToolbar) {
80+
return children;
81+
}
82+
83+
if (showTrackingSetup) {
84+
return (
85+
<div className="p-4">
86+
<WebsiteTrackingSetupTab websiteId={websiteId} />
87+
</div>
88+
);
89+
}
90+
91+
return children;
92+
};
93+
94+
return (
95+
<div className="flex h-full flex-col overflow-hidden">
96+
{!hideToolbar && (
97+
<div className="sticky top-0 right-0 left-0 z-50 shrink-0 overscroll-contain bg-background md:top-0 md:left-84">
98+
<AnalyticsToolbar
99+
isDisabled={!(isDemoRoute || isTrackingSetup)}
100+
isLoading={false}
101+
isRefreshing={isRefreshing}
102+
onRefreshAction={handleRefresh}
103+
websiteId={websiteId}
104+
/>
105+
</div>
106+
)}
107+
108+
<div
109+
className={
110+
hideToolbar
111+
? "min-h-0 flex-1"
112+
: "min-h-0 flex-1 overflow-y-auto overscroll-contain"
113+
}
114+
>
115+
{renderContent()}
116+
</div>
117+
</div>
118+
);
119+
}
Lines changed: 20 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -1,133 +1,30 @@
1-
"use client";
2-
3-
import { useQueryClient } from "@tanstack/react-query";
4-
import { useAtom } from "jotai";
5-
import { useParams, usePathname } from "next/navigation";
6-
import { toast } from "sonner";
7-
import { WebsiteErrorState } from "@/components/website-error-state";
8-
import { useTrackingSetup } from "@/hooks/use-tracking-setup";
9-
import { useWebsite } from "@/hooks/use-websites";
10-
import { isAnalyticsRefreshingAtom } from "@/stores/jotai/filterAtoms";
11-
import { AnalyticsToolbar } from "./_components/analytics-toolbar";
12-
import { WebsiteTrackingSetupTab } from "./_components/tabs/tracking-setup-tab";
13-
14-
const NO_TOOLBAR_ROUTES = [
15-
"/assistant",
16-
"/map",
17-
"/flags",
18-
"/databunny",
19-
"/settings",
20-
"/users",
21-
"/agent",
22-
"/pulse",
23-
];
1+
import { getServerRPCClient } from "@/lib/orpc-server";
2+
import { WebsiteLayoutClient } from "./_components/website-layout-client";
243

254
interface WebsiteLayoutProps {
265
children: React.ReactNode;
6+
params: Promise<{ id: string }>;
277
}
288

29-
export default function WebsiteLayout({ children }: WebsiteLayoutProps) {
30-
const { id } = useParams();
31-
const websiteId = id as string;
32-
const pathname = usePathname();
33-
const queryClient = useQueryClient();
34-
const [isRefreshing, setIsRefreshing] = useAtom(isAnalyticsRefreshingAtom);
35-
36-
const isDemoRoute = pathname?.startsWith("/demo/");
37-
const hideToolbar = NO_TOOLBAR_ROUTES.some((route) =>
38-
pathname.includes(route)
39-
);
40-
41-
const {
42-
data: websiteData,
43-
isLoading: isWebsiteLoading,
44-
isError: isWebsiteError,
45-
error: websiteError,
46-
} = useWebsite(websiteId);
47-
48-
const { isTrackingSetup, isTrackingSetupLoading } =
49-
useTrackingSetup(websiteId);
50-
51-
if (!id) {
52-
return <WebsiteErrorState error={{ data: { code: "NOT_FOUND" } }} />;
53-
}
54-
55-
if (!isWebsiteLoading && isWebsiteError) {
56-
return <WebsiteErrorState error={websiteError} websiteId={websiteId} />;
57-
}
9+
export default async function WebsiteLayout({
10+
children,
11+
params,
12+
}: WebsiteLayoutProps) {
13+
const { id: websiteId } = await params;
14+
const rpc = await getServerRPCClient();
5815

59-
const isToolbarLoading =
60-
isWebsiteLoading ||
61-
(!isDemoRoute && (isTrackingSetupLoading || isTrackingSetup === null));
62-
63-
const isToolbarDisabled =
64-
!isDemoRoute && (!isTrackingSetup || isToolbarLoading);
65-
66-
const showTrackingSetup =
67-
!(isDemoRoute || isTrackingSetupLoading) &&
68-
websiteData &&
69-
isTrackingSetup === false;
70-
71-
const handleRefresh = async () => {
72-
setIsRefreshing(true);
73-
try {
74-
await Promise.all([
75-
queryClient.invalidateQueries({ queryKey: ["websites", id] }),
76-
queryClient.invalidateQueries({
77-
queryKey: ["websites", "isTrackingSetup", id],
78-
}),
79-
queryClient.invalidateQueries({ queryKey: ["dynamic-query", id] }),
80-
queryClient.invalidateQueries({
81-
queryKey: ["batch-dynamic-query", id],
82-
}),
83-
]);
84-
toast.success("Data refreshed");
85-
} catch {
86-
toast.error("Failed to refresh data");
87-
} finally {
88-
setIsRefreshing(false);
89-
}
90-
};
91-
92-
const renderContent = () => {
93-
if (hideToolbar) {
94-
return children;
95-
}
96-
97-
if (showTrackingSetup) {
98-
return (
99-
<div className="p-4">
100-
<WebsiteTrackingSetupTab websiteId={websiteId} />
101-
</div>
102-
);
103-
}
104-
105-
return children;
106-
};
16+
const [website, trackingData] = await Promise.all([
17+
rpc.websites.getById({ id: websiteId }).catch(() => null),
18+
rpc.websites.isTrackingSetup({ websiteId }).catch(() => null),
19+
]);
10720

10821
return (
109-
<div className="flex h-full flex-col overflow-hidden">
110-
{!hideToolbar && (
111-
<div className="sticky top-0 right-0 left-0 z-50 shrink-0 overscroll-contain bg-background md:top-0 md:left-84">
112-
<AnalyticsToolbar
113-
isDisabled={isToolbarDisabled}
114-
isLoading={isToolbarLoading}
115-
isRefreshing={isRefreshing}
116-
onRefreshAction={handleRefresh}
117-
websiteId={websiteId}
118-
/>
119-
</div>
120-
)}
121-
122-
<div
123-
className={
124-
hideToolbar
125-
? "min-h-0 flex-1"
126-
: "min-h-0 flex-1 overflow-y-auto overscroll-contain"
127-
}
128-
>
129-
{renderContent()}
130-
</div>
131-
</div>
22+
<WebsiteLayoutClient
23+
initialTrackingSetup={trackingData?.tracking_setup ?? false}
24+
initialWebsite={website}
25+
websiteId={websiteId}
26+
>
27+
{children}
28+
</WebsiteLayoutClient>
13229
);
13330
}

0 commit comments

Comments
 (0)