Skip to content

Commit 7f8c64d

Browse files
committed
[TOOL-4531] Dashboard: Add Token Asset creation wizard
1 parent 0eff648 commit 7f8c64d

File tree

26 files changed

+2032
-31
lines changed

26 files changed

+2032
-31
lines changed
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { cn } from "@/lib/utils";
2+
3+
export type Segment = {
4+
label: string;
5+
percent: number;
6+
color: string;
7+
};
8+
9+
type DistributionBarChatProps = {
10+
segments: Segment[];
11+
title: string;
12+
};
13+
14+
export function DistributionBarChart(props: DistributionBarChatProps) {
15+
const totalPercentage = props.segments.reduce(
16+
(sum, segment) => sum + segment.percent,
17+
0,
18+
);
19+
20+
const invalidTotalPercentage = totalPercentage !== 100;
21+
22+
return (
23+
<div>
24+
<div className="mb-2 flex items-center justify-between">
25+
<h3 className="font-medium text-sm">{props.title}</h3>
26+
<div
27+
className={cn(
28+
"font-medium text-muted-foreground text-sm",
29+
invalidTotalPercentage && "text-red-500",
30+
)}
31+
>
32+
Total: {totalPercentage}%
33+
</div>
34+
</div>
35+
36+
{/* Bar */}
37+
<div className="flex h-3 overflow-hidden rounded-lg">
38+
{props.segments.map((segment) => {
39+
return (
40+
<div
41+
key={segment.label}
42+
className="flex h-full items-center justify-center transition-all duration-200"
43+
style={{
44+
width: `${segment.percent}%`,
45+
backgroundColor: segment.color,
46+
}}
47+
/>
48+
);
49+
})}
50+
</div>
51+
52+
{/* Legends */}
53+
<div className="mt-3 flex flex-col gap-1 lg:flex-row lg:gap-6">
54+
{props.segments.map((segment) => {
55+
return (
56+
<div key={segment.label} className="flex items-center gap-1.5">
57+
<div
58+
className="size-3 rounded-full"
59+
style={{
60+
backgroundColor: segment.color,
61+
}}
62+
/>
63+
<p className="text-sm">
64+
{segment.label}: {segment.percent}%
65+
</p>
66+
</div>
67+
);
68+
})}
69+
</div>
70+
</div>
71+
);
72+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import type { Meta, StoryObj } from "@storybook/react";
2+
import { MultiStepStatus } from "./multi-step-status";
3+
4+
const meta = {
5+
title: "Blocks/MultiStepStatus",
6+
component: MultiStepStatus,
7+
decorators: [
8+
(Story) => (
9+
<div className="container w-full max-w-md py-10">
10+
<Story />
11+
</div>
12+
),
13+
],
14+
} satisfies Meta<typeof MultiStepStatus>;
15+
16+
export default meta;
17+
type Story = StoryObj<typeof meta>;
18+
19+
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
20+
21+
export const AllStates: Story = {
22+
args: {
23+
steps: [
24+
{
25+
status: "completed",
26+
label: "Connect Wallet",
27+
retryLabel: "Failed to connect wallet",
28+
execute: async () => {
29+
await sleep(1000);
30+
},
31+
},
32+
{
33+
status: "pending",
34+
label: "Sign Message",
35+
retryLabel: "Failed to sign message",
36+
execute: async () => {
37+
await sleep(1000);
38+
},
39+
},
40+
{
41+
status: "error",
42+
label: "Approve Transaction",
43+
retryLabel: "Transaction approval failed",
44+
execute: async () => {
45+
await sleep(1000);
46+
},
47+
},
48+
{
49+
status: "idle",
50+
label: "Confirm Transaction",
51+
retryLabel: "Transaction confirmation failed",
52+
execute: async () => {
53+
await sleep(1000);
54+
},
55+
},
56+
{
57+
status: "idle",
58+
label: "Finalize",
59+
retryLabel: "Finalization failed",
60+
execute: async () => {
61+
await sleep(1000);
62+
},
63+
},
64+
],
65+
},
66+
};
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
"use client";
2+
3+
import { Button } from "@/components/ui/button";
4+
import {
5+
AlertCircleIcon,
6+
CircleCheckIcon,
7+
CircleIcon,
8+
RefreshCwIcon,
9+
} from "lucide-react";
10+
import { DynamicHeight } from "../../ui/DynamicHeight";
11+
import { Spinner } from "../../ui/Spinner/Spinner";
12+
13+
export type MultiStepState = {
14+
status: "idle" | "pending" | "completed" | "error";
15+
retryLabel: string;
16+
label: string;
17+
execute: () => Promise<void>;
18+
};
19+
20+
export function MultiStepStatus(props: {
21+
steps: MultiStepState[];
22+
}) {
23+
return (
24+
<DynamicHeight>
25+
<div className="space-y-4">
26+
{props.steps.map((step) => (
27+
<div
28+
key={step.label}
29+
className="flex items-start space-x-3 [&_*]:transition-colors [&_*]:duration-300"
30+
>
31+
{step.status === "completed" ? (
32+
<CircleCheckIcon className="mt-0.5 size-5 flex-shrink-0 text-green-500" />
33+
) : step.status === "pending" ? (
34+
<Spinner className="mt-0.5 size-5 flex-shrink-0 animate-spin text-foreground" />
35+
) : step.status === "error" ? (
36+
<AlertCircleIcon className="mt-0.5 size-5 flex-shrink-0 text-red-500" />
37+
) : (
38+
<CircleIcon className="mt-0.5 size-5 flex-shrink-0 text-muted-foreground/70" />
39+
)}
40+
<div className="flex-1">
41+
<p
42+
className={`font-medium ${
43+
step.status === "pending"
44+
? "text-foreground"
45+
: step.status === "completed"
46+
? "text-green-500"
47+
: step.status === "error"
48+
? "text-red-500"
49+
: "text-muted-foreground/70"
50+
}`}
51+
>
52+
{step.label}
53+
</p>
54+
55+
{step.status === "error" && (
56+
<div className="mt-1 space-y-2">
57+
<p className="mb-1 text-red-500 text-sm">{step.retryLabel}</p>
58+
<Button
59+
variant="destructive"
60+
size="sm"
61+
className="gap-2"
62+
onClick={() => step.execute()}
63+
>
64+
<RefreshCwIcon className="size-4" />
65+
Retry
66+
</Button>
67+
</div>
68+
)}
69+
</div>
70+
</div>
71+
))}
72+
</div>
73+
</DynamicHeight>
74+
);
75+
}

apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_layout/primary-dashboard-button.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,8 @@ function AddToProjectModalContent(props: {
147147
teamId: params.teamId,
148148
projectId: params.projectId,
149149
chainId: props.chainId,
150+
deploymentType: undefined,
151+
contractType: undefined,
150152
},
151153
{
152154
onSuccess: () => {

apps/dashboard/src/app/(app)/account/contracts/_components/DeployedContractsPage.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,13 @@ async function DeployedContractsPageAsync(props: {
4444
teamId: props.teamId,
4545
projectId: props.projectId,
4646
authToken: props.authToken,
47+
deploymentType: undefined,
4748
});
4849

4950
return (
5051
<ClientOnly ssr={<Loading />}>
5152
<ContractTable
53+
variant="contract"
5254
contracts={deployedContracts}
5355
pageSize={10}
5456
teamId={props.teamId}

apps/dashboard/src/app/(app)/account/contracts/_components/getProjectContracts.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,28 @@ export type ProjectContract = {
77
chainId: string;
88
createdAt: string;
99
updatedAt: string;
10+
deploymentType: string | null;
11+
contractType: string | null;
1012
};
1113

1214
export async function getProjectContracts(options: {
1315
teamId: string;
1416
projectId: string;
1517
authToken: string;
18+
deploymentType: string | undefined;
1619
}) {
17-
const res = await fetch(
20+
const url = new URL(
1821
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${options.teamId}/projects/${options.projectId}/contracts`,
19-
{
20-
headers: {
21-
Authorization: `Bearer ${options.authToken}`,
22-
},
23-
},
2422
);
23+
if (options.deploymentType) {
24+
url.searchParams.set("deploymentType", options.deploymentType);
25+
}
26+
27+
const res = await fetch(url, {
28+
headers: {
29+
Authorization: `Bearer ${options.authToken}`,
30+
},
31+
});
2532

2633
if (!res.ok) {
2734
const errorMessage = await res.text();

apps/dashboard/src/app/(app)/account/contracts/_components/getSortedDeployedContracts.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@ export async function getSortedDeployedContracts(params: {
99
teamId: string;
1010
projectId: string;
1111
authToken: string;
12+
deploymentType: string | undefined;
1213
}) {
1314
const contracts = await getProjectContracts({
1415
teamId: params.teamId,
1516
projectId: params.projectId,
1617
authToken: params.authToken,
18+
deploymentType: params.deploymentType,
1719
});
1820

1921
const chainIds = Array.from(new Set(contracts.map((c) => c.chainId)));

0 commit comments

Comments
 (0)