Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 0 additions & 39 deletions apps/dashboard/src/@3rdweb-sdk/react/hooks/useEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,45 +47,6 @@ const getEngineRequestHeaders = (token: string | null): HeadersInit => {
};
};

// TODO - add authToken in queryKey instead
export function useEngineInstances() {
const address = useActiveAccount()?.address;
return useQuery({
queryKey: engineKeys.instances(address || ""),
queryFn: async (): Promise<EngineInstance[]> => {
type Result = {
data?: {
instances: EngineInstance[];
};
};

const res = await apiServerProxy<Result>({
pathname: "/v1/engine",
method: "GET",
});

if (!res.ok) {
throw new Error(res.error);
}

const json = res.data;
const instances = json.data?.instances || [];

return instances.map((instance) => {
// Sanitize: Add trailing slash if not present.
const url = instance.url.endsWith("/")
? instance.url
: `${instance.url}/`;
return {
...instance,
url,
};
});
},
enabled: !!address,
});
}

// GET Requests
export type BackendWallet = {
address: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,13 @@ export const EngineImportPage = (props: {
await importMutation.mutateAsync(data);
toast.success("Engine imported successfully");
router.push(`/team/${props.teamSlug}/~/engine`);
} catch {
} catch (e) {
const message = e instanceof Error ? e.message : undefined;
toast.error(
"Error importing Engine. Please check if the details are correct.",
{
description: message,
},
);
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ import {
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { ToolTipLabel } from "@/components/ui/tooltip";
import { useDashboardRouter } from "@/lib/DashboardRouter";
import {
type DeleteCloudHostedInput,
type EditEngineInstanceInput,
type EngineInstance,
useEngineDeleteCloudHosted,
useEngineEditInstance,
type useEngineInstances,
useEngineRemoveFromDashboard,
} from "@3rdweb-sdk/react/hooks/useEngine";
import { FormControl, Radio, RadioGroup } from "@chakra-ui/react";
Expand All @@ -43,22 +43,17 @@ import { FormLabel } from "tw-components";

interface EngineInstancesTableProps {
instances: EngineInstance[];
isPending: boolean;
isFetched: boolean;
refetch: ReturnType<typeof useEngineInstances>["refetch"];
engineLinkPrefix: string;
}

export const EngineInstancesTable: React.FC<EngineInstancesTableProps> = ({
instances,
isPending,
isFetched,
refetch,
engineLinkPrefix,
}) => {
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
const [isRemoveModalOpen, setIsRemoveModalOpen] = useState(false);
const trackEvent = useTrack();
const router = useDashboardRouter();

const [instanceToUpdate, setInstanceToUpdate] = useState<
EngineInstance | undefined
Expand Down Expand Up @@ -137,8 +132,8 @@ export const EngineInstancesTable: React.FC<EngineInstancesTableProps> = ({
title="Your Engines"
data={instances}
columns={columns}
isFetched={isFetched}
isPending={isPending}
isFetched={true}
isPending={false}
onMenuClick={[
{
icon: <PencilIcon className="size-4" />,
Expand Down Expand Up @@ -177,7 +172,7 @@ export const EngineInstancesTable: React.FC<EngineInstancesTableProps> = ({
instance={instanceToUpdate}
open={isEditModalOpen}
onOpenChange={setIsEditModalOpen}
refetch={refetch}
refetch={() => router.refresh()}
/>
)}

Expand All @@ -186,7 +181,7 @@ export const EngineInstancesTable: React.FC<EngineInstancesTableProps> = ({
instance={instanceToUpdate}
onOpenChange={setIsRemoveModalOpen}
open={isRemoveModalOpen}
refetch={refetch}
refetch={() => router.refresh()}
/>
)}
</>
Expand All @@ -203,7 +198,7 @@ const EditModal = (props: {
const { onOpenChange, instance, open, refetch } = props;

const form = useForm<EditEngineInstanceInput>({
defaultValues: {
values: {
instanceId: instance.id,
name: instance.name,
url: instance.url,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
"use client";

import { GenericLoadingPage } from "@/components/blocks/skeletons/GenericLoadingPage";
import { UnorderedList } from "@/components/ui/List/List";
import { Button } from "@/components/ui/button";
import { useEngineInstances } from "@3rdweb-sdk/react/hooks/useEngine";
import type { EngineInstance } from "@3rdweb-sdk/react/hooks/useEngine";
import { useTrack } from "hooks/analytics/useTrack";
import {
CloudDownloadIcon,
Expand All @@ -18,83 +16,9 @@ import { EngineInstancesTable } from "./engine-instances-table";

export const EngineInstancesList = (props: {
team_slug: string;
instances: EngineInstance[];
}) => {
const engineLinkPrefix = `/team/${props.team_slug}/~/engine`;
const instancesQuery = useEngineInstances();
const instances = instancesQuery.data ?? [];
const trackEvent = useTrack();

if (instancesQuery.isPending) {
return <GenericLoadingPage />;
}

if (instances.length === 0) {
return (
<div className="mx-auto max-w-[600px]">
<Image alt="Engine hero image" src={emptyStateHeaderImage} />

<div className="h-6" />

<h1 className="font-bold text-2xl tracking-tighter md:text-3xl">
Your scalable web3 backend server
</h1>

<div className="h-3" />

<UnorderedList>
<li>Read, write, and deploy contracts at production scale</li>
<li>
Reliably parallelize and retry transactions with gas & nonce
management
</li>
<li>Securely manage backend wallets</li>
<li>Built-in support for account abstraction, relayers, and more</li>
</UnorderedList>

<div className="h-6" />

<div className="flex gap-3">
<CreateEngineLink
label="Get Started"
engineLinkPrefix={engineLinkPrefix}
/>

<Button
asChild
onClick={() => {
trackEvent({
category: "engine",
action: "try-demo",
label: "clicked-try-demo",
});
}}
variant="outline"
>
<Link href={`${engineLinkPrefix}/sandbox`} className="gap-2">
Try Demo
<RocketIcon className="size-4 text-muted-foreground" />
</Link>
</Button>
</div>

<div className="h-10" />

<div className="flex items-center justify-between gap-4 rounded-lg border border-border bg-card p-6">
<p className="font-semibold text-lg tracking-tight">
Already have an Engine Instance?
</p>

<ImportEngineLink
label="Import"
engineLinkPrefix={engineLinkPrefix}
/>
</div>

<div className="h-20" />
<LearnMoreCard />
</div>
);
}

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

<EngineInstancesTable
instances={instances}
isFetched={instancesQuery.isFetched}
isPending={instancesQuery.isPending}
refetch={instancesQuery.refetch}
instances={props.instances}
engineLinkPrefix={engineLinkPrefix}
/>

Expand All @@ -128,6 +49,74 @@ export const EngineInstancesList = (props: {
);
};

export function NoEngineInstancesPage(props: { team_slug: string }) {
const engineLinkPrefix = `/team/${props.team_slug}/~/engine`;
const trackEvent = useTrack();

return (
<div className="mx-auto max-w-[600px]">
<Image alt="Engine hero image" src={emptyStateHeaderImage} />

<div className="h-6" />

<h1 className="font-bold text-2xl tracking-tighter md:text-3xl">
Your scalable web3 backend server
</h1>

<div className="h-3" />

<UnorderedList>
<li>Read, write, and deploy contracts at production scale</li>
<li>
Reliably parallelize and retry transactions with gas & nonce
management
</li>
<li>Securely manage backend wallets</li>
<li>Built-in support for account abstraction, relayers, and more</li>
</UnorderedList>

<div className="h-6" />

<div className="flex gap-3">
<CreateEngineLink
label="Get Started"
engineLinkPrefix={engineLinkPrefix}
/>

<Button
asChild
onClick={() => {
trackEvent({
category: "engine",
action: "try-demo",
label: "clicked-try-demo",
});
}}
variant="outline"
>
<Link href={`${engineLinkPrefix}/sandbox`} className="gap-2">
Try Demo
<RocketIcon className="size-4 text-muted-foreground" />
</Link>
</Button>
</div>

<div className="h-10" />

<div className="flex items-center justify-between gap-4 rounded-lg border border-border bg-card p-6">
<p className="font-semibold text-lg tracking-tight">
Already have an Engine Instance?
</p>

<ImportEngineLink label="Import" engineLinkPrefix={engineLinkPrefix} />
</div>

<div className="h-20" />
<LearnMoreCard />
</div>
);
}

function LearnMoreCard() {
return (
<div className="relative rounded-lg border border-border p-4 hover:border-active-border">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,30 @@
import { EngineInstancesList } from "./overview/engine-list";
import { getAuthToken } from "../../../../../../api/lib/getAuthToken";
import { loginRedirect } from "../../../../../../login/loginRedirect";
import { getEngineInstances } from "../_utils/getEngineInstances";
import {
EngineInstancesList,
NoEngineInstancesPage,
} from "./overview/engine-list";

export default async function Page(props: {
params: Promise<{
team_slug: string;
}>;
}) {
const params = await props.params;
return <EngineInstancesList team_slug={params.team_slug} />;
const authToken = await getAuthToken();

if (!authToken) {
loginRedirect(`/team/${params.team_slug}/~/engine`);
}

const res = await getEngineInstances({ authToken });

if (!res.data || res.data.length === 0) {
return <NoEngineInstancesPage team_slug={params.team_slug} />;
}

return (
<EngineInstancesList team_slug={params.team_slug} instances={res.data} />
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { ArrowLeftIcon, CircleAlertIcon } from "lucide-react";
import Link from "next/link";

export function EngineErrorPage(props: {
children: React.ReactNode;
rootPath: string;
}) {
return (
<div className="flex grow flex-col">
<Link
href={props.rootPath}
className="flex items-center gap-2 text-muted-foreground hover:text-foreground"
>
<ArrowLeftIcon className="size-5" />
Back
</Link>

<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]">
<div className="flex flex-col items-center gap-4">
<CircleAlertIcon className="size-16 text-destructive-text" />
<div className="text-center text-muted-foreground">
{props.children}
</div>
</div>
</div>
</div>
);
}
Loading
Loading