Skip to content

Commit 7dc4b3d

Browse files
committed
[TOOL-3508] Dashboard: Engine general Page UI improvements, update engine instance header layout
1 parent f693ab8 commit 7dc4b3d

File tree

24 files changed

+1280
-831
lines changed

24 files changed

+1280
-831
lines changed

apps/dashboard/src/@/components/ui/input.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
1111
<input
1212
type={type}
1313
className={cn(
14-
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background selection:bg-foreground selection:text-background file:border-0 file:bg-transparent file:font-medium file:text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
14+
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background selection:bg-foreground/10 file:border-0 file:bg-transparent file:font-medium file:text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
1515
className,
1616
)}
1717
ref={ref}

apps/dashboard/src/@/components/ui/tracked-link.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { useTrack } from "hooks/analytics/useTrack";
44
import Link from "next/link";
55
import type React from "react";
6+
import { cn } from "../../lib/utils";
67

78
export type TrackedLinkProps = React.ComponentProps<typeof Link> & {
89
category: string;
@@ -24,3 +25,22 @@ export function TrackedLinkTW(props: TrackedLinkProps) {
2425
/>
2526
);
2627
}
28+
29+
export function TrackedUnderlineLink(props: TrackedLinkProps) {
30+
const trackEvent = useTrack();
31+
const { category, label, trackingProps, ...restProps } = props;
32+
33+
return (
34+
<Link
35+
{...restProps}
36+
onClick={(e) => {
37+
trackEvent({ category, action: "click", label, ...trackingProps });
38+
props.onClick?.(e);
39+
}}
40+
className={cn(
41+
"underline decoration-muted-foreground/50 decoration-dotted underline-offset-[5px] hover:text-foreground hover:decoration-foreground hover:decoration-solid",
42+
restProps.className,
43+
)}
44+
/>
45+
);
46+
}

apps/dashboard/src/@/styles/globals.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
--link-foreground: 221.21deg 83.19% 53.33%;
3232
--success-text: 142.09 70.56% 35.29%;
3333
--warning-text: 38 92% 40%;
34-
--destructive-text: 357.15deg 100% 68.72%;
34+
--destructive-text: 360 72% 60%;
3535

3636
/* Borders */
3737
--border: 0 0% 85%;

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

Lines changed: 49 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -230,101 +230,71 @@ export function useEngineUpdateDeployment() {
230230
});
231231
}
232232

233-
export function useEngineRemoveFromDashboard() {
234-
const address = useActiveAccount()?.address;
235-
const queryClient = useQueryClient();
236-
237-
return useMutation({
238-
mutationFn: async (instanceId: string) => {
239-
invariant(instanceId, "instance is required");
240-
241-
const res = await apiServerProxy({
242-
pathname: `/v1/engine/${instanceId}`,
243-
method: "DELETE",
244-
});
245-
246-
if (!res.ok) {
247-
throw new Error(res.error);
248-
}
249-
},
233+
export type RemoveEngineFromDashboardIParams = {
234+
instanceId: string;
235+
};
250236

251-
onSuccess: () => {
252-
return queryClient.invalidateQueries({
253-
queryKey: engineKeys.instances(address || ""),
254-
});
255-
},
237+
export async function removeEngineFromDashboard({
238+
instanceId,
239+
}: RemoveEngineFromDashboardIParams) {
240+
const res = await apiServerProxy({
241+
pathname: `/v1/engine/${instanceId}`,
242+
method: "DELETE",
256243
});
244+
245+
if (!res.ok) {
246+
throw new Error(res.error);
247+
}
257248
}
258249

259-
export interface DeleteCloudHostedInput {
250+
export type DeleteCloudHostedEngineParams = {
260251
deploymentId: string;
261252
reason: "USING_SELF_HOSTED" | "TOO_EXPENSIVE" | "MISSING_FEATURES" | "OTHER";
262253
feedback: string;
263-
}
264-
265-
export function useEngineDeleteCloudHosted() {
266-
const address = useActiveAccount()?.address;
267-
const queryClient = useQueryClient();
268-
269-
return useMutation({
270-
mutationFn: async ({
271-
deploymentId,
272-
reason,
273-
feedback,
274-
}: DeleteCloudHostedInput) => {
275-
const res = await apiServerProxy({
276-
pathname: `/v2/engine/deployments/${deploymentId}/infrastructure/delete`,
277-
method: "POST",
278-
headers: {
279-
"Content-Type": "application/json",
280-
},
281-
body: JSON.stringify({ reason, feedback }),
282-
});
283-
284-
if (!res.ok) {
285-
throw new Error(res.error);
286-
}
287-
},
254+
};
288255

289-
onSuccess: () => {
290-
return queryClient.invalidateQueries({
291-
queryKey: engineKeys.instances(address || ""),
292-
});
293-
},
256+
export async function deleteCloudHostedEngine({
257+
deploymentId,
258+
reason,
259+
feedback,
260+
}: DeleteCloudHostedEngineParams) {
261+
const res = await apiServerProxy({
262+
pathname: `/v2/engine/deployments/${deploymentId}/infrastructure/delete`,
263+
method: "POST",
264+
headers: {
265+
"Content-Type": "application/json",
266+
},
267+
body: JSON.stringify({ reason, feedback }),
294268
});
269+
270+
if (!res.ok) {
271+
throw new Error(res.error);
272+
}
295273
}
296274

297-
export interface EditEngineInstanceInput {
275+
export type EditEngineInstanceParams = {
298276
instanceId: string;
299277
name: string;
300278
url: string;
301-
}
302-
303-
export function useEngineEditInstance() {
304-
const address = useActiveAccount()?.address;
305-
const queryClient = useQueryClient();
306-
307-
return useMutation({
308-
mutationFn: async ({ instanceId, name, url }: EditEngineInstanceInput) => {
309-
const res = await apiServerProxy({
310-
pathname: `/v1/engine/${instanceId}`,
311-
method: "PUT",
312-
headers: {
313-
"Content-Type": "application/json",
314-
},
315-
body: JSON.stringify({ name, url }),
316-
});
279+
};
317280

318-
if (!res.ok) {
319-
throw new Error(res.error);
320-
}
321-
},
322-
onSuccess: () => {
323-
return queryClient.invalidateQueries({
324-
queryKey: engineKeys.instances(address || ""),
325-
});
326-
},
281+
export async function editEngineInstance({
282+
instanceId,
283+
name,
284+
url,
285+
}: EditEngineInstanceParams) {
286+
const res = await apiServerProxy({
287+
pathname: `/v1/engine/${instanceId}`,
288+
method: "PUT",
289+
headers: {
290+
"Content-Type": "application/json",
291+
},
292+
body: JSON.stringify({ name, url }),
327293
});
294+
295+
if (!res.ok) {
296+
throw new Error(res.error);
297+
}
328298
}
329299

330300
export type Transaction = {

apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/components/EcosystemSlugLayout.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { SidebarLayout } from "@/components/blocks/SidebarLayout";
2-
import {} from "@/constants/cookie";
32
import { redirect } from "next/navigation";
43
import { getAuthToken } from "../../../../../../../../api/lib/getAuthToken";
54
import { fetchEcosystem } from "../../../utils/fetchEcosystem";

apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/page.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { Button } from "@/components/ui/button";
2-
import {} from "@/constants/cookie";
32
import { ArrowRightIcon, ExternalLinkIcon } from "lucide-react";
43
import Image from "next/image";
54
import Link from "next/link";
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
"use client";
2+
3+
import { Button } from "@/components/ui/button";
4+
import {
5+
ArrowRightIcon,
6+
DownloadIcon,
7+
ExternalLinkIcon,
8+
PlusIcon,
9+
} from "lucide-react";
10+
import Link from "next/link";
11+
import { useTrack } from "../../../../../../../hooks/analytics/useTrack";
12+
13+
export function CreateEngineLink(props: {
14+
label: string;
15+
engineLinkPrefix: string;
16+
}) {
17+
const trackEvent = useTrack();
18+
19+
return (
20+
<Button
21+
asChild
22+
variant="default"
23+
size="sm"
24+
onClick={() => {
25+
trackEvent({
26+
category: "engine",
27+
action: "click",
28+
label: "add-engine-instance",
29+
});
30+
}}
31+
>
32+
<Link href={`${props.engineLinkPrefix}/create`} className="gap-2">
33+
<PlusIcon className="size-3" />
34+
{props.label}
35+
</Link>
36+
</Button>
37+
);
38+
}
39+
40+
export function ImportEngineLink(props: {
41+
label: string;
42+
engineLinkPrefix: string;
43+
}) {
44+
const trackEvent = useTrack();
45+
46+
return (
47+
<Button
48+
asChild
49+
variant="outline"
50+
size="sm"
51+
className="gap-2 bg-card"
52+
onClick={() => {
53+
trackEvent({
54+
category: "engine",
55+
action: "import",
56+
});
57+
}}
58+
>
59+
<Link href={`${props.engineLinkPrefix}/import`}>
60+
<DownloadIcon className="size-3" />
61+
{props.label}
62+
</Link>
63+
</Button>
64+
);
65+
}
66+
67+
export function EngineInfoCard(props: { team_slug: string }) {
68+
const engineLinkPrefix = `/team/${props.team_slug}/~/engine`;
69+
const trackEvent = useTrack();
70+
71+
return (
72+
<div className=" rounded-lg border border-border bg-card">
73+
<div className="p-6">
74+
<h1 className="font-semibold text-xl tracking-tight">
75+
Your scalable web3 backend server
76+
</h1>
77+
78+
<div className="h-2" />
79+
80+
<ul className="list-disc space-y-2 pl-3 text-muted-foreground text-sm">
81+
<li>Read, write, and deploy contracts at production scale</li>
82+
<li>
83+
Reliably parallelize and retry transactions with gas & nonce
84+
management
85+
</li>
86+
<li>Securely manage backend wallets</li>
87+
<li>Built-in support for account abstraction, relayers, and more</li>
88+
</ul>
89+
</div>
90+
91+
<div className="flex justify-end gap-3 border-border border-t p-4 lg:px-6">
92+
<Button asChild variant="outline" size="sm">
93+
<Link
94+
href="https://portal.thirdweb.com/engine"
95+
className="gap-2"
96+
target="_blank"
97+
>
98+
Learn More
99+
<ExternalLinkIcon className="size-3 text-muted-foreground" />
100+
</Link>
101+
</Button>
102+
103+
<Button
104+
size="sm"
105+
asChild
106+
onClick={() => {
107+
trackEvent({
108+
category: "engine",
109+
action: "try-demo",
110+
label: "clicked-try-demo",
111+
});
112+
}}
113+
variant="outline"
114+
>
115+
<Link href={`${engineLinkPrefix}/sandbox`} className="gap-2">
116+
Try Demo Engine
117+
<ArrowRightIcon className="size-3 text-muted-foreground" />
118+
</Link>
119+
</Button>
120+
</div>
121+
</div>
122+
);
123+
}

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@ import { EngineTierCard } from "./tier-card";
22

33
export default function Page() {
44
return (
5-
<div>
6-
<h1 className="mb-2 font-semibold text-2xl tracking-tight">
5+
<div className="pb-20">
6+
<h1 className="mb-1 font-semibold text-2xl tracking-tight">
77
Choose an Engine deployment
88
</h1>
99

10-
<p className="mb-7 text-muted-foreground">
11-
Host Engine on thirdweb with no setup or maintenance required.
10+
<p className="mb-8 text-muted-foreground text-sm">
11+
Host Engine on thirdweb with no setup or maintenance required
1212
</p>
1313

14-
<div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
14+
<div className="grid grid-cols-1 gap-4 lg:grid-cols-3">
1515
<EngineTierCard tier="STARTER" />
1616
<EngineTierCard tier="PREMIUM" isPrimaryCta />
1717
<EngineTierCard tier="ENTERPRISE" previousTier="Premium Engine" />

0 commit comments

Comments
 (0)