Skip to content

Commit acd6d23

Browse files
committed
Dashboard: Add Marketplace in project
1 parent cf401be commit acd6d23

File tree

12 files changed

+903
-64
lines changed

12 files changed

+903
-64
lines changed

apps/dashboard/src/@/analytics/report.ts

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -269,22 +269,6 @@ export function reportAssetBuyFailed(properties: {
269269

270270
// Assets Landing Page ----------------------------
271271

272-
/**
273-
* ### Why do we need to report this event?
274-
* - To track number of asset creation started from the assets page
275-
* - To track which asset types are being created the most
276-
*
277-
* ### Who is responsible for this event?
278-
* @MananTank
279-
*/
280-
export function reportAssetCreationStarted(properties: {
281-
assetType: "nft" | "coin";
282-
}) {
283-
posthog.capture("asset creation started", {
284-
assetType: properties.assetType,
285-
});
286-
}
287-
288272
/**
289273
* ### Why do we need to report this event?
290274
* - To track number of assets imported successfully from the assets page
@@ -386,9 +370,35 @@ export function reportAssetCreationFailed(
386370
});
387371
}
388372

373+
/**
374+
* ### Why do we need to report this event?
375+
* - To track number of successful asset creations
376+
* - To track which asset types are being created the most
377+
*
378+
* ### Who is responsible for this event?
379+
* @MananTank
380+
*/
381+
export function reportMarketCreationSuccessful() {
382+
posthog.capture("market creation successful");
383+
}
384+
385+
/**
386+
* ### Why do we need to report this event?
387+
* - To track number of failed marketplace creations
388+
* - To track the errors that users encounter when trying to create a marketplace
389+
*
390+
* ### Who is responsible for this event?
391+
* @MananTank
392+
*/
393+
export function reportMarketCreationFailed(properties: { error: string }) {
394+
posthog.capture("market creation failed", {
395+
error: properties.error,
396+
});
397+
}
398+
389399
type UpsellParams = {
390400
content: "storage-limit";
391-
campaign: "create-coin" | "create-nft";
401+
campaign: "create-coin" | "create-nft" | "create-marketplace";
392402
sku: Exclude<ProductSKU, null>;
393403
};
394404

apps/dashboard/src/@/components/contract-components/tables/contract-table.tsx

Lines changed: 26 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export function ContractTable(props: {
4848
teamId: string;
4949
projectId: string;
5050
client: ThirdwebClient;
51-
variant: "asset" | "contract";
51+
variant: "token" | "contract" | "marketplace";
5252
teamSlug: string;
5353
projectSlug: string;
5454
}) {
@@ -76,7 +76,7 @@ export function ContractTableUI(props: {
7676
pageSize: number;
7777
removeContractFromProject: (contractId: string) => Promise<void>;
7878
client: ThirdwebClient;
79-
variant: "asset" | "contract";
79+
variant: "token" | "contract" | "marketplace";
8080
teamSlug: string;
8181
projectSlug: string;
8282
}) {
@@ -143,7 +143,7 @@ export function ContractTableUI(props: {
143143
<TableHeader className="z-0">
144144
<TableRow>
145145
<TableHead>Name</TableHead>
146-
<TableHead>Type</TableHead>
146+
{props.variant !== "marketplace" && <TableHead>Type</TableHead>}
147147
<TableHead className="tracking-normal">
148148
<NetworkFilterCell
149149
chainId={
@@ -163,7 +163,7 @@ export function ContractTableUI(props: {
163163
<TableHead>Contract Address</TableHead>
164164
)}
165165

166-
{props.variant === "asset" && <TableHead> Token Page</TableHead>}
166+
{props.variant === "token" && <TableHead> Token Page</TableHead>}
167167

168168
<TableHead>Actions</TableHead>
169169
</TableRow>
@@ -188,23 +188,25 @@ export function ContractTableUI(props: {
188188
/>
189189
</TableCell>
190190

191-
<TableCell>
192-
{contract.contractType &&
193-
props.variant === "asset" &&
194-
contractTypeToAssetTypeRecord[contract.contractType] ? (
195-
<ContractTypeCellUI
196-
name={
197-
contractTypeToAssetTypeRecord[contract.contractType]
198-
}
199-
/>
200-
) : (
201-
<ContractTypeCell
202-
chainId={contract.chainId}
203-
client={props.client}
204-
contractAddress={contract.contractAddress}
205-
/>
206-
)}
207-
</TableCell>
191+
{props.variant !== "marketplace" && (
192+
<TableCell>
193+
{contract.contractType &&
194+
props.variant === "token" &&
195+
contractTypeToAssetTypeRecord[contract.contractType] ? (
196+
<ContractTypeCellUI
197+
name={
198+
contractTypeToAssetTypeRecord[contract.contractType]
199+
}
200+
/>
201+
) : (
202+
<ContractTypeCell
203+
chainId={contract.chainId}
204+
client={props.client}
205+
contractAddress={contract.contractAddress}
206+
/>
207+
)}
208+
</TableCell>
209+
)}
208210

209211
<TableCell>
210212
<ChainNameCell
@@ -224,7 +226,7 @@ export function ContractTableUI(props: {
224226
</TableCell>
225227
)}
226228

227-
{props.variant === "asset" && (
229+
{props.variant === "token" && (
228230
<TableCell>
229231
<Button asChild size="sm" variant="ghost">
230232
<Link
@@ -261,11 +263,8 @@ export function ContractTableUI(props: {
261263
{contracts.length === 0 && (
262264
<div className="flex h-[350px] items-center justify-center text-muted-foreground">
263265
<div className="text-center">
264-
{props.variant === "asset" ? (
265-
<p className="mb-3">No tokens found</p>
266-
) : (
267-
<p className="mb-3">No contracts found</p>
268-
)}
266+
<p className="mb-3">No {props.variant}s</p>
267+
269268
{props.variant === "contract" && (
270269
<Button asChild className="bg-background" variant="outline">
271270
<Link

apps/dashboard/src/@/components/contracts/import-contract/modal.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ type ImportModalProps = {
4040
projectSlug: string;
4141
teamSlug: string;
4242
client: ThirdwebClient;
43-
type: "contract" | "asset";
43+
type: "contract" | "token" | "marketplace";
4444
onSuccess?: () => void;
4545
};
4646

@@ -60,7 +60,7 @@ export const ImportModal: React.FC<ImportModalProps> = (props) => {
6060
>
6161
<DialogHeader className="p-6">
6262
<DialogTitle className="font-semibold text-2xl tracking-tight">
63-
Import {props.type === "contract" ? "Contract" : "Token"}
63+
Import {props.type}
6464
</DialogTitle>
6565
<DialogDescription>
6666
Import a deployed contract in your project
@@ -103,7 +103,7 @@ function ImportForm(props: {
103103
teamSlug: string;
104104
projectSlug: string;
105105
client: ThirdwebClient;
106-
type: "contract" | "asset";
106+
type: "contract" | "token" | "marketplace";
107107
onSuccess?: () => void;
108108
}) {
109109
const router = useDashboardRouter();
@@ -197,7 +197,7 @@ function ImportForm(props: {
197197
<FormItem>
198198
<FormLabel>Contract Address</FormLabel>
199199
<FormControl>
200-
<Input placeholder="0x..." {...field} />
200+
<Input placeholder="0x..." {...field} className="bg-card" />
201201
</FormControl>
202202
<FormMessage />
203203
</FormItem>
@@ -211,6 +211,8 @@ function ImportForm(props: {
211211
chainId={form.watch("chainId")}
212212
client={props.client}
213213
disableChainId
214+
disableDeprecated
215+
className="bg-card"
214216
onChange={(v) => form.setValue("chainId", v)}
215217
side="top"
216218
/>

apps/dashboard/src/@/hooks/project-contracts.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,13 @@ export function useAddContractToProject() {
1010
projectId: string;
1111
contractAddress: string;
1212
chainId: string;
13-
deploymentType: "asset" | undefined;
14-
contractType: "DropERC20" | "DropERC721" | "DropERC1155" | undefined;
13+
deploymentType: "asset" | "marketplace" | undefined;
14+
contractType:
15+
| "DropERC20"
16+
| "DropERC721"
17+
| "DropERC1155"
18+
| "MarketplaceV3"
19+
| undefined;
1520
}) => {
1621
const res = await apiServerProxy({
1722
body: JSON.stringify({

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectSidebarLayout.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
LockIcon,
1010
RssIcon,
1111
SettingsIcon,
12+
StoreIcon,
1213
WalletIcon,
1314
} from "lucide-react";
1415
import { FullWidthSidebarLayout } from "@/components/blocks/full-width-sidebar-layout";
@@ -92,6 +93,15 @@ export function ProjectSidebarLayout(props: {
9293
</span>
9394
),
9495
},
96+
{
97+
href: `${layoutPath}/marketplace`,
98+
icon: StoreIcon,
99+
label: (
100+
<span className="flex items-center gap-2">
101+
Marketplace <Badge>New</Badge>
102+
</span>
103+
),
104+
},
95105
],
96106
},
97107
{
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
"use client";
2+
3+
import { ArrowDownToLineIcon, StoreIcon } from "lucide-react";
4+
import Link from "next/link";
5+
import { useState } from "react";
6+
import type { ThirdwebClient } from "thirdweb";
7+
import {
8+
reportAssetImportStarted,
9+
reportAssetImportSuccessful,
10+
} from "@/analytics/report";
11+
import { ImportModal } from "@/components/contracts/import-contract/modal";
12+
import { cn } from "@/lib/utils";
13+
14+
export function Cards(props: {
15+
teamSlug: string;
16+
projectSlug: string;
17+
client: ThirdwebClient;
18+
teamId: string;
19+
projectId: string;
20+
}) {
21+
const [importModalOpen, setImportModalOpen] = useState(false);
22+
23+
return (
24+
<div className="grid grid-cols-1 gap-4 lg:grid-cols-2">
25+
<ImportModal
26+
client={props.client}
27+
isOpen={importModalOpen}
28+
onClose={() => {
29+
setImportModalOpen(false);
30+
}}
31+
onSuccess={() => {
32+
reportAssetImportSuccessful();
33+
}}
34+
projectId={props.projectId}
35+
projectSlug={props.projectSlug}
36+
teamId={props.teamId}
37+
teamSlug={props.teamSlug}
38+
type="marketplace"
39+
/>
40+
41+
<CardLink
42+
description="Launch your own marketplace"
43+
href={`/team/${props.teamSlug}/${props.projectSlug}/marketplace/create`}
44+
icon={StoreIcon}
45+
title="Create Marketplace"
46+
/>
47+
48+
<CardLink
49+
description="Import marketplace contract to your project"
50+
href={undefined}
51+
icon={ArrowDownToLineIcon}
52+
onClick={() => {
53+
reportAssetImportStarted();
54+
setImportModalOpen(true);
55+
}}
56+
title="Import Marketplace"
57+
/>
58+
</div>
59+
);
60+
}
61+
62+
function CardLink(props: {
63+
title: string;
64+
description: string;
65+
href: string | undefined;
66+
onClick?: () => void;
67+
icon: React.FC<{ className?: string }>;
68+
}) {
69+
const { onClick } = props;
70+
const isClickable = !!onClick || !!props.href;
71+
72+
return (
73+
// biome-ignore lint/a11y/noStaticElementInteractions: FIXME
74+
<div
75+
className={cn(
76+
"relative flex flex-col rounded-lg border bg-card p-4",
77+
isClickable && "cursor-pointer hover:border-active-border ",
78+
)}
79+
onClick={onClick}
80+
onKeyDown={(e) => {
81+
if (e.key === "Enter" || e.key === " ") {
82+
onClick?.();
83+
}
84+
}}
85+
role={onClick ? "button" : undefined}
86+
tabIndex={onClick ? 0 : undefined}
87+
>
88+
<div className="mb-4 flex">
89+
<div className="flex items-center justify-center rounded-full border p-2.5">
90+
<props.icon className="size-5 text-muted-foreground" />
91+
</div>
92+
</div>
93+
94+
<h3 className="mb-0.5 font-semibold text-lg tracking-tight">
95+
{props.href ? (
96+
<Link className="before:absolute before:inset-0" href={props.href}>
97+
{props.title}
98+
</Link>
99+
) : (
100+
<span>{props.title}</span>
101+
)}
102+
</h3>
103+
<p className="text-muted-foreground text-sm">{props.description}</p>
104+
</div>
105+
);
106+
}

0 commit comments

Comments
 (0)