Skip to content

Commit 53fdb2b

Browse files
committed
[TOOL-3110] Dashboard: Engine pages cleanup: Fetch engine info on server + Use layout (#5968)
Engine pages were in a weird "pages router" type of state - Its now properly migrated to app router * Common skeleton is moved to layout * engine instances are fetched on server
1 parent 73894d2 commit 53fdb2b

35 files changed

+491
-888
lines changed

apps/dashboard/src/@3rdweb-sdk/react/hooks/useEngine.ts

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -47,45 +47,6 @@ const getEngineRequestHeaders = (token: string | null): HeadersInit => {
4747
};
4848
};
4949

50-
// TODO - add authToken in queryKey instead
51-
export function useEngineInstances() {
52-
const address = useActiveAccount()?.address;
53-
return useQuery({
54-
queryKey: engineKeys.instances(address || ""),
55-
queryFn: async (): Promise<EngineInstance[]> => {
56-
type Result = {
57-
data?: {
58-
instances: EngineInstance[];
59-
};
60-
};
61-
62-
const res = await apiServerProxy<Result>({
63-
pathname: "/v1/engine",
64-
method: "GET",
65-
});
66-
67-
if (!res.ok) {
68-
throw new Error(res.error);
69-
}
70-
71-
const json = res.data;
72-
const instances = json.data?.instances || [];
73-
74-
return instances.map((instance) => {
75-
// Sanitize: Add trailing slash if not present.
76-
const url = instance.url.endsWith("/")
77-
? instance.url
78-
: `${instance.url}/`;
79-
return {
80-
...instance,
81-
url,
82-
};
83-
});
84-
},
85-
enabled: !!address,
86-
});
87-
}
88-
8950
// GET Requests
9051
export type BackendWallet = {
9152
address: string;

apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(general)/import/EngineImportPage.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,13 @@ export const EngineImportPage = (props: {
6464
await importMutation.mutateAsync(data);
6565
toast.success("Engine imported successfully");
6666
router.push(`/team/${props.teamSlug}/~/engine`);
67-
} catch {
67+
} catch (e) {
68+
const message = e instanceof Error ? e.message : undefined;
6869
toast.error(
6970
"Error importing Engine. Please check if the details are correct.",
71+
{
72+
description: message,
73+
},
7074
);
7175
}
7276
};

apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(general)/overview/engine-instances-table.tsx

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ import {
1313
import { Input } from "@/components/ui/input";
1414
import { Textarea } from "@/components/ui/textarea";
1515
import { ToolTipLabel } from "@/components/ui/tooltip";
16+
import { useDashboardRouter } from "@/lib/DashboardRouter";
1617
import {
1718
type DeleteCloudHostedInput,
1819
type EditEngineInstanceInput,
1920
type EngineInstance,
2021
useEngineDeleteCloudHosted,
2122
useEngineEditInstance,
22-
type useEngineInstances,
2323
useEngineRemoveFromDashboard,
2424
} from "@3rdweb-sdk/react/hooks/useEngine";
2525
import { FormControl, Radio, RadioGroup } from "@chakra-ui/react";
@@ -43,22 +43,17 @@ import { FormLabel } from "tw-components";
4343

4444
interface EngineInstancesTableProps {
4545
instances: EngineInstance[];
46-
isPending: boolean;
47-
isFetched: boolean;
48-
refetch: ReturnType<typeof useEngineInstances>["refetch"];
4946
engineLinkPrefix: string;
5047
}
5148

5249
export const EngineInstancesTable: React.FC<EngineInstancesTableProps> = ({
5350
instances,
54-
isPending,
55-
isFetched,
56-
refetch,
5751
engineLinkPrefix,
5852
}) => {
5953
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
6054
const [isRemoveModalOpen, setIsRemoveModalOpen] = useState(false);
6155
const trackEvent = useTrack();
56+
const router = useDashboardRouter();
6257

6358
const [instanceToUpdate, setInstanceToUpdate] = useState<
6459
EngineInstance | undefined
@@ -137,8 +132,8 @@ export const EngineInstancesTable: React.FC<EngineInstancesTableProps> = ({
137132
title="Your Engines"
138133
data={instances}
139134
columns={columns}
140-
isFetched={isFetched}
141-
isPending={isPending}
135+
isFetched={true}
136+
isPending={false}
142137
onMenuClick={[
143138
{
144139
icon: <PencilIcon className="size-4" />,
@@ -177,7 +172,7 @@ export const EngineInstancesTable: React.FC<EngineInstancesTableProps> = ({
177172
instance={instanceToUpdate}
178173
open={isEditModalOpen}
179174
onOpenChange={setIsEditModalOpen}
180-
refetch={refetch}
175+
refetch={() => router.refresh()}
181176
/>
182177
)}
183178

@@ -186,7 +181,7 @@ export const EngineInstancesTable: React.FC<EngineInstancesTableProps> = ({
186181
instance={instanceToUpdate}
187182
onOpenChange={setIsRemoveModalOpen}
188183
open={isRemoveModalOpen}
189-
refetch={refetch}
184+
refetch={() => router.refresh()}
190185
/>
191186
)}
192187
</>
@@ -203,7 +198,7 @@ const EditModal = (props: {
203198
const { onOpenChange, instance, open, refetch } = props;
204199

205200
const form = useForm<EditEngineInstanceInput>({
206-
defaultValues: {
201+
values: {
207202
instanceId: instance.id,
208203
name: instance.name,
209204
url: instance.url,

apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(general)/overview/engine-list.tsx

Lines changed: 71 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
"use client";
2-
3-
import { GenericLoadingPage } from "@/components/blocks/skeletons/GenericLoadingPage";
42
import { UnorderedList } from "@/components/ui/List/List";
53
import { Button } from "@/components/ui/button";
6-
import { useEngineInstances } from "@3rdweb-sdk/react/hooks/useEngine";
4+
import type { EngineInstance } from "@3rdweb-sdk/react/hooks/useEngine";
75
import { useTrack } from "hooks/analytics/useTrack";
86
import {
97
CloudDownloadIcon,
@@ -18,83 +16,9 @@ import { EngineInstancesTable } from "./engine-instances-table";
1816

1917
export const EngineInstancesList = (props: {
2018
team_slug: string;
19+
instances: EngineInstance[];
2120
}) => {
2221
const engineLinkPrefix = `/team/${props.team_slug}/~/engine`;
23-
const instancesQuery = useEngineInstances();
24-
const instances = instancesQuery.data ?? [];
25-
const trackEvent = useTrack();
26-
27-
if (instancesQuery.isPending) {
28-
return <GenericLoadingPage />;
29-
}
30-
31-
if (instances.length === 0) {
32-
return (
33-
<div className="mx-auto max-w-[600px]">
34-
<Image alt="Engine hero image" src={emptyStateHeaderImage} />
35-
36-
<div className="h-6" />
37-
38-
<h1 className="font-bold text-2xl tracking-tighter md:text-3xl">
39-
Your scalable web3 backend server
40-
</h1>
41-
42-
<div className="h-3" />
43-
44-
<UnorderedList>
45-
<li>Read, write, and deploy contracts at production scale</li>
46-
<li>
47-
Reliably parallelize and retry transactions with gas & nonce
48-
management
49-
</li>
50-
<li>Securely manage backend wallets</li>
51-
<li>Built-in support for account abstraction, relayers, and more</li>
52-
</UnorderedList>
53-
54-
<div className="h-6" />
55-
56-
<div className="flex gap-3">
57-
<CreateEngineLink
58-
label="Get Started"
59-
engineLinkPrefix={engineLinkPrefix}
60-
/>
61-
62-
<Button
63-
asChild
64-
onClick={() => {
65-
trackEvent({
66-
category: "engine",
67-
action: "try-demo",
68-
label: "clicked-try-demo",
69-
});
70-
}}
71-
variant="outline"
72-
>
73-
<Link href={`${engineLinkPrefix}/sandbox`} className="gap-2">
74-
Try Demo
75-
<RocketIcon className="size-4 text-muted-foreground" />
76-
</Link>
77-
</Button>
78-
</div>
79-
80-
<div className="h-10" />
81-
82-
<div className="flex items-center justify-between gap-4 rounded-lg border border-border bg-card p-6">
83-
<p className="font-semibold text-lg tracking-tight">
84-
Already have an Engine Instance?
85-
</p>
86-
87-
<ImportEngineLink
88-
label="Import"
89-
engineLinkPrefix={engineLinkPrefix}
90-
/>
91-
</div>
92-
93-
<div className="h-20" />
94-
<LearnMoreCard />
95-
</div>
96-
);
97-
}
9822

9923
return (
10024
<div>
@@ -115,10 +39,7 @@ export const EngineInstancesList = (props: {
11539
<div className="h-6" />
11640

11741
<EngineInstancesTable
118-
instances={instances}
119-
isFetched={instancesQuery.isFetched}
120-
isPending={instancesQuery.isPending}
121-
refetch={instancesQuery.refetch}
42+
instances={props.instances}
12243
engineLinkPrefix={engineLinkPrefix}
12344
/>
12445

@@ -128,6 +49,74 @@ export const EngineInstancesList = (props: {
12849
);
12950
};
13051

52+
export function NoEngineInstancesPage(props: { team_slug: string }) {
53+
const engineLinkPrefix = `/team/${props.team_slug}/~/engine`;
54+
const trackEvent = useTrack();
55+
56+
return (
57+
<div className="mx-auto max-w-[600px]">
58+
<Image alt="Engine hero image" src={emptyStateHeaderImage} />
59+
60+
<div className="h-6" />
61+
62+
<h1 className="font-bold text-2xl tracking-tighter md:text-3xl">
63+
Your scalable web3 backend server
64+
</h1>
65+
66+
<div className="h-3" />
67+
68+
<UnorderedList>
69+
<li>Read, write, and deploy contracts at production scale</li>
70+
<li>
71+
Reliably parallelize and retry transactions with gas & nonce
72+
management
73+
</li>
74+
<li>Securely manage backend wallets</li>
75+
<li>Built-in support for account abstraction, relayers, and more</li>
76+
</UnorderedList>
77+
78+
<div className="h-6" />
79+
80+
<div className="flex gap-3">
81+
<CreateEngineLink
82+
label="Get Started"
83+
engineLinkPrefix={engineLinkPrefix}
84+
/>
85+
86+
<Button
87+
asChild
88+
onClick={() => {
89+
trackEvent({
90+
category: "engine",
91+
action: "try-demo",
92+
label: "clicked-try-demo",
93+
});
94+
}}
95+
variant="outline"
96+
>
97+
<Link href={`${engineLinkPrefix}/sandbox`} className="gap-2">
98+
Try Demo
99+
<RocketIcon className="size-4 text-muted-foreground" />
100+
</Link>
101+
</Button>
102+
</div>
103+
104+
<div className="h-10" />
105+
106+
<div className="flex items-center justify-between gap-4 rounded-lg border border-border bg-card p-6">
107+
<p className="font-semibold text-lg tracking-tight">
108+
Already have an Engine Instance?
109+
</p>
110+
111+
<ImportEngineLink label="Import" engineLinkPrefix={engineLinkPrefix} />
112+
</div>
113+
114+
<div className="h-20" />
115+
<LearnMoreCard />
116+
</div>
117+
);
118+
}
119+
131120
function LearnMoreCard() {
132121
return (
133122
<div className="relative rounded-lg border border-border p-4 hover:border-active-border">
Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,30 @@
1-
import { EngineInstancesList } from "./overview/engine-list";
1+
import { getAuthToken } from "../../../../../../api/lib/getAuthToken";
2+
import { loginRedirect } from "../../../../../../login/loginRedirect";
3+
import { getEngineInstances } from "../_utils/getEngineInstances";
4+
import {
5+
EngineInstancesList,
6+
NoEngineInstancesPage,
7+
} from "./overview/engine-list";
28

39
export default async function Page(props: {
410
params: Promise<{
511
team_slug: string;
612
}>;
713
}) {
814
const params = await props.params;
9-
return <EngineInstancesList team_slug={params.team_slug} />;
15+
const authToken = await getAuthToken();
16+
17+
if (!authToken) {
18+
loginRedirect(`/team/${params.team_slug}/~/engine`);
19+
}
20+
21+
const res = await getEngineInstances({ authToken });
22+
23+
if (!res.data || res.data.length === 0) {
24+
return <NoEngineInstancesPage team_slug={params.team_slug} />;
25+
}
26+
27+
return (
28+
<EngineInstancesList team_slug={params.team_slug} instances={res.data} />
29+
);
1030
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { ArrowLeftIcon, CircleAlertIcon } from "lucide-react";
2+
import Link from "next/link";
3+
4+
export function EngineErrorPage(props: {
5+
children: React.ReactNode;
6+
rootPath: string;
7+
}) {
8+
return (
9+
<div className="flex grow flex-col">
10+
<Link
11+
href={props.rootPath}
12+
className="flex items-center gap-2 text-muted-foreground hover:text-foreground"
13+
>
14+
<ArrowLeftIcon className="size-5" />
15+
Back
16+
</Link>
17+
18+
<div className="mt-5 flex min-h-[300px] grow flex-col items-center justify-center rounded-lg border border-border px-4 lg:min-h-[400px]">
19+
<div className="flex flex-col items-center gap-4">
20+
<CircleAlertIcon className="size-16 text-destructive-text" />
21+
<div className="text-center text-muted-foreground">
22+
{props.children}
23+
</div>
24+
</div>
25+
</div>
26+
</div>
27+
);
28+
}

0 commit comments

Comments
 (0)