Skip to content

Commit fe3f24e

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 <!-- start pr-codex --> --- ## PR-Codex overview This PR focuses on refactoring and enhancing the engine instance management in the application. It removes obsolete files, improves error handling, and introduces new utility functions for fetching engine instances and permissions. ### Detailed summary - Deleted multiple obsolete `.tsx` files related to engine instance management. - Improved error handling in engine import functionality. - Added `getEngineAccessPermission` and `getEngineInstances` utility functions. - Refactored components to utilize new utility functions for fetching engine data. - Enhanced the layout and error handling in the engine instance pages. - Updated components to use consistent instance data and permissions checks. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex -->
1 parent 9f55100 commit fe3f24e

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)