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
14 changes: 14 additions & 0 deletions apps/dashboard/src/@/analytics/report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -503,3 +503,17 @@ export function reportFundWalletSuccessful() {
export function reportFundWalletFailed(params: { errorMessage: string }) {
posthog.capture("fund wallet failed", params);
}

/**
* ### Why do we need to report this event?
* - To track the conversion rate of the users choosing to create a token from new flow instead of the old flow
*
* ### Who is responsible for this event?
* @MananTank
*/
export function reportTokenUpsellClicked(params: {
assetType: "nft" | "coin";
pageType: "explore" | "deploy-contract";
}) {
posthog.capture("token upsell clicked", params);
}
82 changes: 69 additions & 13 deletions apps/dashboard/src/@/components/blocks/dismissible-alert.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,39 @@
"use client";

import { XIcon } from "lucide-react";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { useLocalStorage } from "@/hooks/useLocalStorage";

export function DismissibleAlert(props: {
export function DismissibleAlert(
props: {
title: React.ReactNode;
header?: React.ReactNode;
className?: string;
description: React.ReactNode;
children?: React.ReactNode;
} & (
| {
preserveState: true;
localStorageId: string;
}
| {
preserveState: false;
}
),
) {
if (props.preserveState) {
return <DismissibleAlertWithLocalStorage {...props} />;
}

return <DismissibleAlertWithoutLocalStorage {...props} />;
}

function DismissibleAlertWithLocalStorage(props: {
title: React.ReactNode;
header?: React.ReactNode;
description: React.ReactNode;
children?: React.ReactNode;
localStorageId: string;
}) {
const [isVisible, setIsVisible] = useLocalStorage(
Expand All @@ -17,19 +44,48 @@ export function DismissibleAlert(props: {

if (!isVisible) return null;

return <AlertUI {...props} onClose={() => setIsVisible(false)} />;
}

function DismissibleAlertWithoutLocalStorage(props: {
title: React.ReactNode;
description: React.ReactNode;
children?: React.ReactNode;
}) {
const [isVisible, setIsVisible] = useState(true);

if (!isVisible) return null;

return <AlertUI {...props} onClose={() => setIsVisible(false)} />;
}

function AlertUI(props: {
title: React.ReactNode;
header?: React.ReactNode;
description: React.ReactNode;
children?: React.ReactNode;
className?: string;
onClose: () => void;
}) {
return (
<div className="relative rounded-lg border border-border bg-card p-4">
<Button
aria-label="Close alert"
className="absolute top-4 right-4 h-auto w-auto p-1 text-muted-foreground"
onClick={() => setIsVisible(false)}
variant="ghost"
>
<XIcon className="size-5" />
</Button>
<div>
<h2 className="mb-0.5 font-semibold">{props.title} </h2>
<div className="text-muted-foreground text-sm">{props.description}</div>
<div className={props.className}>
<div className="relative rounded-lg border border-border bg-card p-4 lg:p-6 overflow-hidden">
<Button
aria-label="Close alert"
className="absolute top-4 right-4 h-auto w-auto p-1 text-muted-foreground"
onClick={props.onClose}
variant="ghost"
>
<XIcon className="size-5" />
</Button>
<div>
{props.header}
<h2 className="mb-0.5 font-semibold">{props.title} </h2>
<div className="text-muted-foreground text-sm">
{props.description}
</div>
{props.children}
</div>
</div>
</div>
);
Expand Down
13 changes: 9 additions & 4 deletions apps/dashboard/src/app/(app)/(dashboard)/explore/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ContractRow } from "./components/contract-row";
import { DeployUpsellCard } from "./components/upsells/deploy-your-own";
import { PublishUpsellCard } from "./components/upsells/publish-submit";
import { EXPLORE_PAGE_DATA } from "./data";
import { TokensSection } from "./tokens-section";

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

<div className="container flex flex-col gap-4 py-10">
<div className="container max-w-7xl pt-8 pb-4">
<TokensSection />
</div>

<div className="container max-w-7xl flex flex-col gap-8 py-10">
<div className="flex flex-col gap-14">
{EXPLORE_PAGE_DATA.map((category, idx) => (
<Fragment key={category.id}>
Expand Down
111 changes: 111 additions & 0 deletions apps/dashboard/src/app/(app)/(dashboard)/explore/tokens-section.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
"use client";

import { ZapIcon } from "lucide-react";
import Link from "next/link";
import { reportTokenUpsellClicked } from "@/analytics/report";
import { GridPattern } from "@/components/ui/background-patterns";
import { Badge } from "@/components/ui/badge";
import { cn } from "@/lib/utils";

export function TokensSection() {
return (
<div className="bg-card p-4 lg:py-8 lg:px-6 border rounded-xl relative w-full overflow-hidden">
<GridPattern
width={30}
height={30}
strokeDasharray={"4 2"}
className="text-border dark:text-border/70 hidden lg:block translate-x-5"
style={{
maskImage:
"linear-gradient(to bottom left,white,transparent,transparent)",
}}
/>

<Badge
variant="outline"
className="text-sm bg-background h-auto px-3 py-1 gap-2 mb-4"
>
<div className="rounded-full bg-primary size-2" />
New
</Badge>

<h2 className="font-semibold text-2xl tracking-tight mb-1">
Launch Your Tokens Effortlessly
</h2>
<p className="text-muted-foreground mb-6 text-sm lg:text-base">
Deploy contract and configure all settings you need to launch your token
in one seamless flow
</p>

<div className="grid grid-cols-1 md:grid-cols-2 gap-4 max-w-4xl">
<CardLink
assetType="coin"
title="Launch Coin"
description="Launch an ERC-20 token to create a cryptocurrency"
href="/team/~/~project/tokens/create/token"
bullets={[
"Deploy Contract",
"Configure Price and Supply",
"Airdrop Tokens",
]}
/>

<CardLink
assetType="nft"
title="Launch NFT Collection"
description="Launch an ERC-721 or ERC-1155 NFT collection"
href="/team/~/~project/tokens/create/nft"
bullets={[
"Deploy Contract",
"Upload NFTs",
"Configure Price and Supply",
]}
/>
</div>
</div>
);
}

function CardLink(props: {
title: string;
description: string;
href: string;
assetType: "nft" | "coin";
bullets: string[];
}) {
return (
<div
className={cn(
"relative flex flex-col rounded-lg border p-4 cursor-pointer hover:border-active-border bg-background",
)}
>
<div className="mb-3 flex">
<div className="flex items-center justify-center rounded-full border p-2 bg-card">
<ZapIcon className="size-4 text-muted-foreground" />
</div>
</div>

<h3 className="mb-0.5 font-semibold text-lg tracking-tight">
<Link
className="before:absolute before:inset-0"
href={props.href}
onClick={() => {
reportTokenUpsellClicked({
assetType: props.assetType,
pageType: "explore",
});
}}
>
{props.title}
</Link>
</h3>
<p className="text-muted-foreground text-sm">{props.description}</p>

<ul className="mt-4 space-y-1 text-sm list-disc list-inside text-muted-foreground">
{props.bullets.map((bullet) => (
<li key={bullet}>{bullet}</li>
))}
</ul>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { ZERO_FEE_VERSIONS } from "@/constants/fee-config";
import { serverThirdwebClient } from "@/constants/thirdweb-client.server";
import { PublishedContractBreadcrumbs } from "../[publisher]/[contract_id]/components/breadcrumbs.client";
import { DeployContractHeader } from "./contract-header";
import { TokenBanner } from "./token-banner";
import { DeployFormForUri } from "./uri-based-deploy";

type PublishBasedDeployProps = {
Expand Down Expand Up @@ -95,6 +96,10 @@ export async function DeployFormForPublishInfo(props: PublishBasedDeployProps) {
),
]);

const isTWPublisher =
contractMetadata?.publisher ===
"0xdd99b75f095d0c4d5112aCe938e4e6ed962fb024";

return (
<div>
<div className="border-border border-b border-dashed">
Expand All @@ -111,6 +116,23 @@ export async function DeployFormForPublishInfo(props: PublishBasedDeployProps) {
/>
</div>

{isTWPublisher &&
(contractMetadata.name === "DropERC20" ||
contractMetadata.name === "TokenERC20") && (
<TokenBanner type="erc20" />
)}
{isTWPublisher &&
(contractMetadata.name === "DropERC721" ||
contractMetadata.name === "TokenERC721") && (
<TokenBanner type="erc721" />
)}

{isTWPublisher &&
(contractMetadata.name === "DropERC1155" ||
contractMetadata.name === "TokenERC1155") && (
<TokenBanner type="erc1155" />
)}

<div className="container max-w-5xl py-8">
<DeployFormForUri
contractMetadata={contractMetadata}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"use client";

import { ArrowUpRightIcon } from "lucide-react";
import Link from "next/link";
import { reportTokenUpsellClicked } from "@/analytics/report";
import { DismissibleAlert } from "@/components/blocks/dismissible-alert";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";

export function TokenBanner(props: { type: "erc20" | "erc721" | "erc1155" }) {
const title =
props.type === "erc20"
? "Launch Your Token Effortlessly"
: "Launch Your NFT Collection Effortlessly";
const description =
props.type === "erc20"
? "Deploy contract, set price and supply, airdrop tokens all in one seamless flow"
: props.type === "erc721"
? "Deploy contract, upload NFTs, set price all in one seamless flow"
: "Deploy contract, upload NFTs, set prices and supply all in one seamless flow";
const href =
props.type === "erc20"
? "/team/~/~project/tokens/create/token"
: "/team/~/~project/tokens/create/nft";

return (
<DismissibleAlert
header={
<Badge
variant="outline"
className="bg-background gap-1.5 py-1 px-2.5 h-auto mb-3"
>
<div className="rounded-full bg-primary size-1.5" />
New
</Badge>
}
title={title}
className="container max-w-5xl mt-8"
preserveState={false}
description={description}
>
<Button
variant="default"
size="sm"
className="gap-2 rounded-full mt-4 px-6"
asChild
>
<Link
href={href}
target="_blank"
onClick={() => {
reportTokenUpsellClicked({
assetType: props.type === "erc20" ? "coin" : "nft",
pageType: "deploy-contract",
});
}}
>
Try Now <ArrowUpRightIcon className="size-3.5" />
</Link>
</Button>
</DismissibleAlert>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export default async function Page(props: {
<FreePlanUpsellBannerUI highlightPlan="growth" teamSlug={team.slug} />
) : (
<DismissibleAlert
preserveState={true}
description="Engines, contracts, project settings, and more are now managed within projects. Open or create a project to access them."
localStorageId={`${team.id}-engines-alert`}
title="Looking for Engines?"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { AppFooter } from "@/components/footers/app-footer";
import { DotsBackgroundPattern } from "@/components/ui/background-patterns";
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
import { TeamHeader } from "../../components/TeamHeader/team-header";
import { createTeamLink, TeamSelectorCard } from "./components/team-selector";
import { createTeamLink, TeamSelectorCard } from "../_components/team-selector";

export default async function Page(props: {
params: Promise<{
Expand Down
Loading
Loading