Skip to content

Commit d812cc3

Browse files
committed
[MNY-92] Dashboard: Add tokens section in explore (#7812)
<!-- ## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes" If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000): ## Notes for the reviewer Anything important to call out? Be sure to also clarify these in your comments. ## How to test Unit tests, playground, etc. --> <!-- start pr-codex --> --- ## PR-Codex overview This PR focuses on improving the project selection and token creation flow within the dashboard application. It includes the addition of new components, updates to existing ones, and enhancements to user notifications and alerts. ### Detailed summary - Updated import paths for `project-selector` and `team-selector`. - Added `TokenBanner` component for displaying token creation options. - Enhanced `DismissibleAlert` to support conditional rendering based on visibility state. - Introduced `GenericProjectSelector` for improved project selection. - Modified `ExplorePage` layout and styling for better presentation. - Added `TokensSection` component with links for launching tokens and NFTs. - Updated `reportTokenUpsellClicked` function for event tracking. - Improved UI elements for better user experience and responsiveness. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Introduced a "Tokens" section on the Explore page, showcasing options for creating ERC-20 tokens (Coins) and NFT Collections. * Added a promotional token banner on deploy pages for select published token contracts. * Launched a new project selection page within the team context, streamlining project navigation and selection. * Added a generic project selector component to enhance project selection flexibility. * Added tracking for clicks on token creation cards and token upsell banner interactions to improve analytics. * **Improvements** * Enhanced layout and styling of the Explore page for better readability and organization. * Updated project and team selector components for more flexible descriptions and improved navigation. * Updated team page alert to preserve dismissal state across sessions. * **Chores** * Refined import paths for improved code organization and maintainability. * Refactored dismissible alert component to support optional state persistence. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 7883127 commit d812cc3

File tree

15 files changed

+401
-25
lines changed

15 files changed

+401
-25
lines changed

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,3 +503,17 @@ export function reportFundWalletSuccessful() {
503503
export function reportFundWalletFailed(params: { errorMessage: string }) {
504504
posthog.capture("fund wallet failed", params);
505505
}
506+
507+
/**
508+
* ### Why do we need to report this event?
509+
* - To track the conversion rate of the users choosing to create a token from new flow instead of the old flow
510+
*
511+
* ### Who is responsible for this event?
512+
* @MananTank
513+
*/
514+
export function reportTokenUpsellClicked(params: {
515+
assetType: "nft" | "coin";
516+
pageType: "explore" | "deploy-contract";
517+
}) {
518+
posthog.capture("token upsell clicked", params);
519+
}

apps/dashboard/src/@/components/blocks/dismissible-alert.tsx

Lines changed: 69 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,39 @@
11
"use client";
22

33
import { XIcon } from "lucide-react";
4+
import { useState } from "react";
45
import { Button } from "@/components/ui/button";
56
import { useLocalStorage } from "@/hooks/useLocalStorage";
67

7-
export function DismissibleAlert(props: {
8+
export function DismissibleAlert(
9+
props: {
10+
title: React.ReactNode;
11+
header?: React.ReactNode;
12+
className?: string;
13+
description: React.ReactNode;
14+
children?: React.ReactNode;
15+
} & (
16+
| {
17+
preserveState: true;
18+
localStorageId: string;
19+
}
20+
| {
21+
preserveState: false;
22+
}
23+
),
24+
) {
25+
if (props.preserveState) {
26+
return <DismissibleAlertWithLocalStorage {...props} />;
27+
}
28+
29+
return <DismissibleAlertWithoutLocalStorage {...props} />;
30+
}
31+
32+
function DismissibleAlertWithLocalStorage(props: {
833
title: React.ReactNode;
34+
header?: React.ReactNode;
935
description: React.ReactNode;
36+
children?: React.ReactNode;
1037
localStorageId: string;
1138
}) {
1239
const [isVisible, setIsVisible] = useLocalStorage(
@@ -17,19 +44,48 @@ export function DismissibleAlert(props: {
1744

1845
if (!isVisible) return null;
1946

47+
return <AlertUI {...props} onClose={() => setIsVisible(false)} />;
48+
}
49+
50+
function DismissibleAlertWithoutLocalStorage(props: {
51+
title: React.ReactNode;
52+
description: React.ReactNode;
53+
children?: React.ReactNode;
54+
}) {
55+
const [isVisible, setIsVisible] = useState(true);
56+
57+
if (!isVisible) return null;
58+
59+
return <AlertUI {...props} onClose={() => setIsVisible(false)} />;
60+
}
61+
62+
function AlertUI(props: {
63+
title: React.ReactNode;
64+
header?: React.ReactNode;
65+
description: React.ReactNode;
66+
children?: React.ReactNode;
67+
className?: string;
68+
onClose: () => void;
69+
}) {
2070
return (
21-
<div className="relative rounded-lg border border-border bg-card p-4">
22-
<Button
23-
aria-label="Close alert"
24-
className="absolute top-4 right-4 h-auto w-auto p-1 text-muted-foreground"
25-
onClick={() => setIsVisible(false)}
26-
variant="ghost"
27-
>
28-
<XIcon className="size-5" />
29-
</Button>
30-
<div>
31-
<h2 className="mb-0.5 font-semibold">{props.title} </h2>
32-
<div className="text-muted-foreground text-sm">{props.description}</div>
71+
<div className={props.className}>
72+
<div className="relative rounded-lg border border-border bg-card p-4 lg:p-6 overflow-hidden">
73+
<Button
74+
aria-label="Close alert"
75+
className="absolute top-4 right-4 h-auto w-auto p-1 text-muted-foreground"
76+
onClick={props.onClose}
77+
variant="ghost"
78+
>
79+
<XIcon className="size-5" />
80+
</Button>
81+
<div>
82+
{props.header}
83+
<h2 className="mb-0.5 font-semibold">{props.title} </h2>
84+
<div className="text-muted-foreground text-sm">
85+
{props.description}
86+
</div>
87+
{props.children}
88+
</div>
3389
</div>
3490
</div>
3591
);

apps/dashboard/src/app/(app)/(dashboard)/explore/page.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { ContractRow } from "./components/contract-row";
44
import { DeployUpsellCard } from "./components/upsells/deploy-your-own";
55
import { PublishUpsellCard } from "./components/upsells/publish-submit";
66
import { EXPLORE_PAGE_DATA } from "./data";
7+
import { TokensSection } from "./tokens-section";
78

89
const title = "List of smart contracts for EVM Developers";
910
const description =
@@ -22,19 +23,23 @@ export default async function ExplorePage() {
2223
return (
2324
<div className="flex flex-col">
2425
<div className="border-b py-10">
25-
<div className="container">
26-
<h1 className="mb-2 font-bold text-3xl lg:text-5xl tracking-tighter">
26+
<div className="container max-w-7xl">
27+
<h1 className="mb-2 font-bold text-4xl lg:text-5xl tracking-tight">
2728
Explore
2829
</h1>
29-
<p className="max-w-screen-sm text-base text-muted-foreground">
30+
<p className="max-w-screen-sm text-sm lg:text-base text-muted-foreground">
3031
The best place for web3 developers to explore smart contracts from
3132
world-class web3 protocols & engineers all deployable with one
3233
click.
3334
</p>
3435
</div>
3536
</div>
3637

37-
<div className="container flex flex-col gap-4 py-10">
38+
<div className="container max-w-7xl pt-8 pb-4">
39+
<TokensSection />
40+
</div>
41+
42+
<div className="container max-w-7xl flex flex-col gap-8 py-10">
3843
<div className="flex flex-col gap-14">
3944
{EXPLORE_PAGE_DATA.map((category, idx) => (
4045
<Fragment key={category.id}>
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
"use client";
2+
3+
import { ZapIcon } from "lucide-react";
4+
import Link from "next/link";
5+
import { reportTokenUpsellClicked } from "@/analytics/report";
6+
import { GridPattern } from "@/components/ui/background-patterns";
7+
import { Badge } from "@/components/ui/badge";
8+
import { cn } from "@/lib/utils";
9+
10+
export function TokensSection() {
11+
return (
12+
<div className="bg-card p-4 lg:py-8 lg:px-6 border rounded-xl relative w-full overflow-hidden">
13+
<GridPattern
14+
width={30}
15+
height={30}
16+
strokeDasharray={"4 2"}
17+
className="text-border dark:text-border/70 hidden lg:block translate-x-5"
18+
style={{
19+
maskImage:
20+
"linear-gradient(to bottom left,white,transparent,transparent)",
21+
}}
22+
/>
23+
24+
<Badge
25+
variant="outline"
26+
className="text-sm bg-background h-auto px-3 py-1 gap-2 mb-4"
27+
>
28+
<div className="rounded-full bg-primary size-2" />
29+
New
30+
</Badge>
31+
32+
<h2 className="font-semibold text-2xl tracking-tight mb-1">
33+
Launch Your Tokens Effortlessly
34+
</h2>
35+
<p className="text-muted-foreground mb-6 text-sm lg:text-base">
36+
Deploy contract and configure all settings you need to launch your token
37+
in one seamless flow
38+
</p>
39+
40+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 max-w-4xl">
41+
<CardLink
42+
assetType="coin"
43+
title="Launch Coin"
44+
description="Launch an ERC-20 token to create a cryptocurrency"
45+
href="/team/~/~project/tokens/create/token"
46+
bullets={[
47+
"Deploy Contract",
48+
"Configure Price and Supply",
49+
"Airdrop Tokens",
50+
]}
51+
/>
52+
53+
<CardLink
54+
assetType="nft"
55+
title="Launch NFT Collection"
56+
description="Launch an ERC-721 or ERC-1155 NFT collection"
57+
href="/team/~/~project/tokens/create/nft"
58+
bullets={[
59+
"Deploy Contract",
60+
"Upload NFTs",
61+
"Configure Price and Supply",
62+
]}
63+
/>
64+
</div>
65+
</div>
66+
);
67+
}
68+
69+
function CardLink(props: {
70+
title: string;
71+
description: string;
72+
href: string;
73+
assetType: "nft" | "coin";
74+
bullets: string[];
75+
}) {
76+
return (
77+
<div
78+
className={cn(
79+
"relative flex flex-col rounded-lg border p-4 cursor-pointer hover:border-active-border bg-background",
80+
)}
81+
>
82+
<div className="mb-3 flex">
83+
<div className="flex items-center justify-center rounded-full border p-2 bg-card">
84+
<ZapIcon className="size-4 text-muted-foreground" />
85+
</div>
86+
</div>
87+
88+
<h3 className="mb-0.5 font-semibold text-lg tracking-tight">
89+
<Link
90+
className="before:absolute before:inset-0"
91+
href={props.href}
92+
onClick={() => {
93+
reportTokenUpsellClicked({
94+
assetType: props.assetType,
95+
pageType: "explore",
96+
});
97+
}}
98+
>
99+
{props.title}
100+
</Link>
101+
</h3>
102+
<p className="text-muted-foreground text-sm">{props.description}</p>
103+
104+
<ul className="mt-4 space-y-1 text-sm list-disc list-inside text-muted-foreground">
105+
{props.bullets.map((bullet) => (
106+
<li key={bullet}>{bullet}</li>
107+
))}
108+
</ul>
109+
</div>
110+
);
111+
}

apps/dashboard/src/app/(app)/(dashboard)/published-contract/components/publish-based-deploy.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { ZERO_FEE_VERSIONS } from "@/constants/fee-config";
1010
import { serverThirdwebClient } from "@/constants/thirdweb-client.server";
1111
import { PublishedContractBreadcrumbs } from "../[publisher]/[contract_id]/components/breadcrumbs.client";
1212
import { DeployContractHeader } from "./contract-header";
13+
import { TokenBanner } from "./token-banner";
1314
import { DeployFormForUri } from "./uri-based-deploy";
1415

1516
type PublishBasedDeployProps = {
@@ -95,6 +96,10 @@ export async function DeployFormForPublishInfo(props: PublishBasedDeployProps) {
9596
),
9697
]);
9798

99+
const isTWPublisher =
100+
contractMetadata?.publisher ===
101+
"0xdd99b75f095d0c4d5112aCe938e4e6ed962fb024";
102+
98103
return (
99104
<div>
100105
<div className="border-border border-b border-dashed">
@@ -111,6 +116,23 @@ export async function DeployFormForPublishInfo(props: PublishBasedDeployProps) {
111116
/>
112117
</div>
113118

119+
{isTWPublisher &&
120+
(contractMetadata.name === "DropERC20" ||
121+
contractMetadata.name === "TokenERC20") && (
122+
<TokenBanner type="erc20" />
123+
)}
124+
{isTWPublisher &&
125+
(contractMetadata.name === "DropERC721" ||
126+
contractMetadata.name === "TokenERC721") && (
127+
<TokenBanner type="erc721" />
128+
)}
129+
130+
{isTWPublisher &&
131+
(contractMetadata.name === "DropERC1155" ||
132+
contractMetadata.name === "TokenERC1155") && (
133+
<TokenBanner type="erc1155" />
134+
)}
135+
114136
<div className="container max-w-5xl py-8">
115137
<DeployFormForUri
116138
contractMetadata={contractMetadata}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
"use client";
2+
3+
import { ArrowUpRightIcon } from "lucide-react";
4+
import Link from "next/link";
5+
import { reportTokenUpsellClicked } from "@/analytics/report";
6+
import { DismissibleAlert } from "@/components/blocks/dismissible-alert";
7+
import { Badge } from "@/components/ui/badge";
8+
import { Button } from "@/components/ui/button";
9+
10+
export function TokenBanner(props: { type: "erc20" | "erc721" | "erc1155" }) {
11+
const title =
12+
props.type === "erc20"
13+
? "Launch Your Token Effortlessly"
14+
: "Launch Your NFT Collection Effortlessly";
15+
const description =
16+
props.type === "erc20"
17+
? "Deploy contract, set price and supply, airdrop tokens all in one seamless flow"
18+
: props.type === "erc721"
19+
? "Deploy contract, upload NFTs, set price all in one seamless flow"
20+
: "Deploy contract, upload NFTs, set prices and supply all in one seamless flow";
21+
const href =
22+
props.type === "erc20"
23+
? "/team/~/~project/tokens/create/token"
24+
: "/team/~/~project/tokens/create/nft";
25+
26+
return (
27+
<DismissibleAlert
28+
header={
29+
<Badge
30+
variant="outline"
31+
className="bg-background gap-1.5 py-1 px-2.5 h-auto mb-3"
32+
>
33+
<div className="rounded-full bg-primary size-1.5" />
34+
New
35+
</Badge>
36+
}
37+
title={title}
38+
className="container max-w-5xl mt-8"
39+
preserveState={false}
40+
description={description}
41+
>
42+
<Button
43+
variant="default"
44+
size="sm"
45+
className="gap-2 rounded-full mt-4 px-6"
46+
asChild
47+
>
48+
<Link
49+
href={href}
50+
target="_blank"
51+
onClick={() => {
52+
reportTokenUpsellClicked({
53+
assetType: props.type === "erc20" ? "coin" : "nft",
54+
pageType: "deploy-contract",
55+
});
56+
}}
57+
>
58+
Try Now <ArrowUpRightIcon className="size-3.5" />
59+
</Link>
60+
</Button>
61+
</DismissibleAlert>
62+
);
63+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ export default async function Page(props: {
6767
<FreePlanUpsellBannerUI highlightPlan="growth" teamSlug={team.slug} />
6868
) : (
6969
<DismissibleAlert
70+
preserveState={true}
7071
description="Engines, contracts, project settings, and more are now managed within projects. Open or create a project to access them."
7172
localStorageId={`${team.id}-engines-alert`}
7273
title="Looking for Engines?"

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { AppFooter } from "@/components/footers/app-footer";
55
import { DotsBackgroundPattern } from "@/components/ui/background-patterns";
66
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
77
import { TeamHeader } from "../../components/TeamHeader/team-header";
8-
import { createTeamLink, TeamSelectorCard } from "./components/team-selector";
8+
import { createTeamLink, TeamSelectorCard } from "../_components/team-selector";
99

1010
export default async function Page(props: {
1111
params: Promise<{

0 commit comments

Comments
 (0)