Skip to content

[TOOL-4531] Dashboard: Add Token Asset creation wizard #7081

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
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
10 changes: 10 additions & 0 deletions apps/dashboard/src/@/actions/revalidate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"use server";

import { revalidatePath } from "next/cache";

export async function revalidatePathAction(
path: string,
type: "page" | "layout",
) {
revalidatePath(path, type);
}
71 changes: 71 additions & 0 deletions apps/dashboard/src/@/components/blocks/distribution-chart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { cn } from "@/lib/utils";

export type Segment = {
label: string;
percent: number;
color: string;
};

type DistributionBarChartProps = {
segments: Segment[];
title: string;
};
export function DistributionBarChart(props: DistributionBarChartProps) {
const totalPercentage = props.segments.reduce(
(sum, segment) => sum + segment.percent,
0,
);

const invalidTotalPercentage = totalPercentage !== 100;

return (
<div>
<div className="mb-2 flex items-center justify-between">
<h3 className="font-medium text-sm">{props.title}</h3>
<div
className={cn(
"font-medium text-muted-foreground text-sm",
invalidTotalPercentage && "text-red-500",
)}
>
Total: {totalPercentage}%
</div>
</div>

{/* Bar */}
<div className="flex h-3 overflow-hidden rounded-lg">
{props.segments.map((segment) => {
return (
<div
key={segment.label}
className="flex h-full items-center justify-center transition-all duration-200"
style={{
width: `${segment.percent}%`,
backgroundColor: segment.color,
}}
/>
);
})}
</div>

{/* Legends */}
<div className="mt-3 flex flex-col gap-1 lg:flex-row lg:gap-6">
{props.segments.map((segment) => {
return (
<div key={segment.label} className="flex items-center gap-1.5">
<div
className="size-3 rounded-full"
style={{
backgroundColor: segment.color,
}}
/>
<p className="text-sm">
{segment.label}: {segment.percent}%
</p>
</div>
);
})}
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import type { Meta, StoryObj } from "@storybook/react";
import { MultiStepStatus } from "./multi-step-status";

const meta = {
title: "Blocks/MultiStepStatus",
component: MultiStepStatus,
decorators: [
(Story) => (
<div className="container w-full max-w-md py-10">
<Story />
</div>
),
],
} satisfies Meta<typeof MultiStepStatus>;

export default meta;
type Story = StoryObj<typeof meta>;

const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

export const AllStates: Story = {
args: {
steps: [
{
status: "completed",
label: "Connect Wallet",
retryLabel: "Failed to connect wallet",
execute: async () => {
await sleep(1000);
},
},
{
status: "pending",
label: "Sign Message",
retryLabel: "Failed to sign message",
execute: async () => {
await sleep(1000);
},
},
{
status: "error",
label: "Approve Transaction",
retryLabel: "Transaction approval failed",
execute: async () => {
await sleep(1000);
},
},
{
status: "idle",
label: "Confirm Transaction",
retryLabel: "Transaction confirmation failed",
execute: async () => {
await sleep(1000);
},
},
{
status: "idle",
label: "Finalize",
retryLabel: "Finalization failed",
execute: async () => {
await sleep(1000);
},
},
],
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"use client";

import { Button } from "@/components/ui/button";
import {
AlertCircleIcon,
CircleCheckIcon,
CircleIcon,
RefreshCwIcon,
} from "lucide-react";
import { DynamicHeight } from "../../ui/DynamicHeight";
import { Spinner } from "../../ui/Spinner/Spinner";

export type MultiStepState = {
status: "idle" | "pending" | "completed" | "error";
retryLabel: string;
label: string;
execute: () => Promise<void>;
};

export function MultiStepStatus(props: {
steps: MultiStepState[];
}) {
return (
<DynamicHeight>
<div className="space-y-4">
{props.steps.map((step) => (
<div key={step.label} className="flex items-start space-x-3 ">
{step.status === "completed" ? (
<CircleCheckIcon className="mt-0.5 size-5 flex-shrink-0 text-green-500" />
) : step.status === "pending" ? (
<Spinner className="mt-0.5 size-5 flex-shrink-0 text-foreground" />
) : step.status === "error" ? (
<AlertCircleIcon className="mt-0.5 size-5 flex-shrink-0 text-red-500" />
) : (
<CircleIcon className="mt-0.5 size-5 flex-shrink-0 text-muted-foreground/70" />
)}
<div className="flex-1">
<p
className={`font-medium ${
step.status === "pending"
? "text-foreground"
: step.status === "completed"
? "text-green-500"
: step.status === "error"
? "text-red-500"
: "text-muted-foreground/70"
}`}
>
{step.label}
</p>

{step.status === "error" && (
<div className="mt-1 space-y-2">
<p className="mb-1 text-red-500 text-sm">{step.retryLabel}</p>
<Button
variant="destructive"
size="sm"
className="gap-2"
onClick={() => step.execute()}
>
<RefreshCwIcon className="size-4" />
Retry
</Button>
</div>
)}
</div>
</div>
))}
</div>
</DynamicHeight>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,10 @@ const getClaimConditionTypeFromPhase = (
if (!phase.snapshot) {
return "public";
}

if (phase.snapshot) {
if (
phase.price === "0" &&
typeof phase.snapshot !== "string" &&
phase.snapshot.length === 1 &&
phase.snapshot.some(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ function AddToProjectModalContent(props: {
teamId: params.teamId,
projectId: params.projectId,
chainId: props.chainId,
deploymentType: undefined,
contractType: undefined,
},
{
onSuccess: () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export function TokenDetailsCardUI(props: {
</h2>
<div className="flex flex-col gap-5 p-6 lg:flex-row">
<Stat
label="Total Supply"
label="Circulating Supply"
isPending={!tokenSupply}
value={
tokenSupply
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export function DeployedContractsPageHeader(props: {
}}
teamId={props.teamId}
projectId={props.projectId}
type="contract"
/>

<div className="container flex max-w-7xl flex-col gap-3 py-10 lg:flex-row lg:items-center lg:justify-between">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export function DeployViaCLIOrImportCard(props: {
return (
<div className="rounded-lg border bg-card p-4 lg:p-6">
<ImportModal
type="contract"
client={client}
isOpen={importModalOpen}
onClose={() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,13 @@ async function DeployedContractsPageAsync(props: {
teamId: props.teamId,
projectId: props.projectId,
authToken: props.authToken,
deploymentType: undefined,
});

return (
<ClientOnly ssr={<Loading />}>
<ContractTable
variant="contract"
contracts={deployedContracts}
pageSize={10}
teamId={props.teamId}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,28 @@ export type ProjectContract = {
chainId: string;
createdAt: string;
updatedAt: string;
deploymentType: string | null;
contractType: string | null;
};

export async function getProjectContracts(options: {
teamId: string;
projectId: string;
authToken: string;
deploymentType: string | undefined;
}) {
const res = await fetch(
const url = new URL(
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${options.teamId}/projects/${options.projectId}/contracts`,
{
headers: {
Authorization: `Bearer ${options.authToken}`,
},
},
);
if (options.deploymentType) {
url.searchParams.set("deploymentType", options.deploymentType);
}

const res = await fetch(url, {
headers: {
Authorization: `Bearer ${options.authToken}`,
},
});

if (!res.ok) {
const errorMessage = await res.text();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ export async function getSortedDeployedContracts(params: {
teamId: string;
projectId: string;
authToken: string;
deploymentType: string | undefined;
}) {
const contracts = await getProjectContracts({
teamId: params.teamId,
projectId: params.projectId,
authToken: params.authToken,
deploymentType: params.deploymentType,
});

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