diff --git a/apps/dashboard/src/@/icons/TokenIcon.tsx b/apps/dashboard/src/@/icons/TokenIcon.tsx new file mode 100644 index 00000000000..80e1b31579e --- /dev/null +++ b/apps/dashboard/src/@/icons/TokenIcon.tsx @@ -0,0 +1,21 @@ +export function TokenIcon(props: { className?: string }) { + return ( + + + + ); +} diff --git a/apps/dashboard/src/@/icons/WalletProductIcon.tsx b/apps/dashboard/src/@/icons/WalletProductIcon.tsx new file mode 100644 index 00000000000..606e273d679 --- /dev/null +++ b/apps/dashboard/src/@/icons/WalletProductIcon.tsx @@ -0,0 +1,21 @@ +export function WalletProductIcon(props: { className?: string }) { + return ( + + + + ); +} diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/TeamSidebarLayout.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/TeamSidebarLayout.tsx index e83543e8844..0db6cdcab3b 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/TeamSidebarLayout.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/TeamSidebarLayout.tsx @@ -1,5 +1,6 @@ "use client"; import { + AtomIcon, BookTextIcon, BoxIcon, ChartNoAxesColumnIcon, @@ -51,7 +52,7 @@ export function TeamSidebarLayout(props: { }, ], subMenu: { - icon: WalletCardsIcon, + icon: AtomIcon, label: "Ecosystems", }, }, diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectSidebarLayout.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectSidebarLayout.tsx index e9dd315e7c1..361a4e41bd3 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectSidebarLayout.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectSidebarLayout.tsx @@ -4,12 +4,10 @@ import { BellIcon, BookTextIcon, BoxIcon, - CoinsIcon, HomeIcon, LockIcon, RssIcon, SettingsIcon, - WalletIcon, } from "lucide-react"; import { FullWidthSidebarLayout } from "@/components/blocks/full-width-sidebar-layout"; import { Badge } from "@/components/ui/badge"; @@ -17,6 +15,8 @@ import { ContractIcon } from "@/icons/ContractIcon"; import { InsightIcon } from "@/icons/InsightIcon"; import { PayIcon } from "@/icons/PayIcon"; import { SmartAccountIcon } from "@/icons/SmartAccountIcon"; +import { TokenIcon } from "@/icons/TokenIcon"; +import { WalletProductIcon } from "@/icons/WalletProductIcon"; export function ProjectSidebarLayout(props: { layoutPath: string; @@ -48,7 +48,7 @@ export function ProjectSidebarLayout(props: { links: [ { href: `${layoutPath}/wallets`, - icon: WalletIcon, + icon: WalletProductIcon, label: "Wallets", }, { @@ -85,7 +85,7 @@ export function ProjectSidebarLayout(props: { }, { href: `${layoutPath}/tokens`, - icon: CoinsIcon, + icon: TokenIcon, label: ( Tokens New diff --git a/apps/playground-web/next.config.mjs b/apps/playground-web/next.config.mjs index 93d918ee3ff..cb03b64c470 100644 --- a/apps/playground-web/next.config.mjs +++ b/apps/playground-web/next.config.mjs @@ -113,17 +113,37 @@ const nextConfig = { }, { source: "/wallets/account-abstraction/sponsor", - destination: "/wallets/account-abstraction/eip-4337", + destination: "/account-abstraction/eip-4337", permanent: false, }, { source: "/wallets/account-abstraction/7702", - destination: "/wallets/account-abstraction/eip-7702", + destination: "/account-abstraction/eip-7702", permanent: false, }, { source: "/wallets/account-abstraction/5792", - destination: "/wallets/account-abstraction/eip-5792", + destination: "/account-abstraction/eip-5792", + permanent: false, + }, + { + source: "/wallets/account-abstraction/native-aa", + destination: "/account-abstraction/native-aa", + permanent: false, + }, + { + source: "/wallets/headless/token-components", + destination: "/tokens/token-components", + permanent: false, + }, + { + source: "/wallets/headless/nft-components", + destination: "/tokens/nft-components", + permanent: false, + }, + { + source: "/wallets/in-app-wallet/ecosystem", + destination: "/wallets/ecosystem-wallet", permanent: false, }, ]; diff --git a/apps/playground-web/package.json b/apps/playground-web/package.json index 4e490615e85..1332c0a73da 100644 --- a/apps/playground-web/package.json +++ b/apps/playground-web/package.json @@ -3,6 +3,8 @@ "@abstract-foundation/agw-react": "^1.6.4", "@hookform/resolvers": "^3.9.1", "@radix-ui/react-checkbox": "^1.3.2", + "@radix-ui/react-collapsible": "^1.1.11", + "@radix-ui/react-dialog": "1.1.14", "@radix-ui/react-label": "^2.1.7", "@radix-ui/react-popover": "^1.1.14", "@radix-ui/react-progress": "^1.1.7", diff --git a/apps/playground-web/public/og/background-1.jpg b/apps/playground-web/public/og/background-1.jpg new file mode 100644 index 00000000000..7a215873a1a Binary files /dev/null and b/apps/playground-web/public/og/background-1.jpg differ diff --git a/apps/playground-web/public/og/icons/contract.svg b/apps/playground-web/public/og/icons/contract.svg new file mode 100644 index 00000000000..2b6c3057a5b --- /dev/null +++ b/apps/playground-web/public/og/icons/contract.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/playground-web/public/og/icons/insight.svg b/apps/playground-web/public/og/icons/insight.svg new file mode 100644 index 00000000000..5e51c6a0e23 --- /dev/null +++ b/apps/playground-web/public/og/icons/insight.svg @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/playground-web/public/og/icons/payments.svg b/apps/playground-web/public/og/icons/payments.svg new file mode 100644 index 00000000000..88b862568aa --- /dev/null +++ b/apps/playground-web/public/og/icons/payments.svg @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/playground-web/public/og/icons/transactions.svg b/apps/playground-web/public/og/icons/transactions.svg new file mode 100644 index 00000000000..685b8b5ce74 --- /dev/null +++ b/apps/playground-web/public/og/icons/transactions.svg @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/playground-web/public/og/icons/wallets.svg b/apps/playground-web/public/og/icons/wallets.svg new file mode 100644 index 00000000000..e455d48b136 --- /dev/null +++ b/apps/playground-web/public/og/icons/wallets.svg @@ -0,0 +1,206 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/playground-web/src/app/AppSidebar.tsx b/apps/playground-web/src/app/AppSidebar.tsx index a0f4b4a8440..6309198efbe 100644 --- a/apps/playground-web/src/app/AppSidebar.tsx +++ b/apps/playground-web/src/app/AppSidebar.tsx @@ -1,45 +1,38 @@ -import Image from "next/image"; -import Link from "next/link"; -import thirdwebIconSrc from "@/../public/thirdweb.svg"; -import { Sidebar, type SidebarLink } from "@/components/ui/sidebar"; -import { ScrollShadow } from "../components/ui/ScrollShadow/ScrollShadow"; -import { otherLinks } from "./otherLinks"; +"use client"; +import { BookTextIcon, GithubIcon } from "lucide-react"; +import { SidebarProvider } from "@/components/ui/sidebar"; +import { FullWidthSidebarLayout } from "../components/blocks/full-width-sidebar-layout"; +import { DashboardIcon } from "../icons/DashboardIcon"; +import { sidebarLinks } from "./navLinks"; -export function AppSidebar(props: { links: SidebarLink[] }) { +export function AppSidebarLayout(props: { children: React.ReactNode }) { return ( -
-
-
- thirdweb - - Playground - -
-
- -
- - - -
- -
- {otherLinks.map((link) => { - return ( - - {link.name} - - ); - })} -
-
+ + + {props.children} + + ); } diff --git a/apps/playground-web/src/app/MobileHeader.tsx b/apps/playground-web/src/app/MobileHeader.tsx deleted file mode 100644 index 703ee092205..00000000000 --- a/apps/playground-web/src/app/MobileHeader.tsx +++ /dev/null @@ -1,93 +0,0 @@ -"use client"; - -import { MenuIcon, XIcon } from "lucide-react"; -import Image from "next/image"; -import Link from "next/link"; -import { useEffect, useState } from "react"; -import thirdwebIconSrc from "@/../public/thirdweb.svg"; -import { Button } from "../components/ui/button"; -import { ScrollShadow } from "../components/ui/ScrollShadow/ScrollShadow"; -import { Sidebar, type SidebarLink } from "../components/ui/sidebar"; -import { otherLinks } from "./otherLinks"; - -export function MobileHeader(props: { links: SidebarLink[] }) { - const [isOpen, setIsOpen] = useState(false); - - useEffect(() => { - if (isOpen) { - document.body.style.overflow = "hidden"; - } else { - document.body.style.overflow = "auto"; - } - - return () => { - document.body.style.overflow = "auto"; - }; - }, [isOpen]); - - return ( - <> -
- - - - Playground - - - -
- - {isOpen && ( -
{ - if (e.target instanceof HTMLElement && e.target.closest("a")) { - setIsOpen(false); - } - }} - > -
- - - -
- -
- {otherLinks.map((link) => { - return ( - - {link.name} - - ); - })} -
-
- )} - - ); -} diff --git a/apps/playground-web/src/app/wallets/account-abstraction/eip-4337/page.tsx b/apps/playground-web/src/app/account-abstraction/eip-4337/page.tsx similarity index 79% rename from apps/playground-web/src/app/wallets/account-abstraction/eip-4337/page.tsx rename to apps/playground-web/src/app/account-abstraction/eip-4337/page.tsx index 2474d99f46a..8239abd33af 100644 --- a/apps/playground-web/src/app/wallets/account-abstraction/eip-4337/page.tsx +++ b/apps/playground-web/src/app/account-abstraction/eip-4337/page.tsx @@ -1,27 +1,33 @@ -import type { Metadata } from "next"; +import { ShieldIcon } from "lucide-react"; import ThirdwebProvider from "@/components/thirdweb-provider"; -import { metadataBase } from "@/lib/constants"; -import { ConnectSmartAccountCustomPreview } from "../../../../components/account-abstraction/connect-smart-account"; -import { SponsoredTxPreview } from "../../../../components/account-abstraction/sponsored-tx"; -import { PageLayout } from "../../../../components/blocks/APIHeader"; -import { CodeExample } from "../../../../components/code/code-example"; -import { SponsoredInAppTxPreview } from "../../../../components/in-app-wallet/sponsored-tx"; +import { createMetadata } from "@/lib/metadata"; +import { ConnectSmartAccountCustomPreview } from "../../../components/account-abstraction/connect-smart-account"; +import { SponsoredTxPreview } from "../../../components/account-abstraction/sponsored-tx"; +import { PageLayout } from "../../../components/blocks/APIHeader"; +import { CodeExample } from "../../../components/code/code-example"; +import { SponsoredInAppTxPreview } from "../../../components/in-app-wallet/sponsored-tx"; -export const metadata: Metadata = { - description: "Turn any EOA into a smart contract wallet with EIP-4337.", - metadataBase, - title: "EIP-4337 Smart Contract Wallets | thirdweb Connect", -}; +const title = "Account Abstraction EIP-4337"; +const description = + "Enable account abstraction with EIP-4337. Unlock gasless transactions, session keys, paymaster support, and smart wallet programmability"; + +export const metadata = createMetadata({ + description, + title, + image: { + icon: "wallets", + title, + }, +}); export default function Page() { return ( Turn any EOA into a smart contract wallet with EIP-4337. - } + icon={ShieldIcon} + title={title} + description={description} docsLink="https://portal.thirdweb.com/wallets/sponsor-gas?utm_source=playground" - title="EIP-4337 Smart Contract Wallets" >
diff --git a/apps/playground-web/src/app/wallets/account-abstraction/eip-5792/page.tsx b/apps/playground-web/src/app/account-abstraction/eip-5792/page.tsx similarity index 70% rename from apps/playground-web/src/app/wallets/account-abstraction/eip-5792/page.tsx rename to apps/playground-web/src/app/account-abstraction/eip-5792/page.tsx index b3549777ce1..f4195249532 100644 --- a/apps/playground-web/src/app/wallets/account-abstraction/eip-5792/page.tsx +++ b/apps/playground-web/src/app/account-abstraction/eip-5792/page.tsx @@ -1,30 +1,32 @@ -import type { Metadata } from "next"; +import { ShieldIcon } from "lucide-react"; import ThirdwebProvider from "@/components/thirdweb-provider"; -import { metadataBase } from "@/lib/constants"; -import { Eip5792GetCapabilitiesPreview } from "../../../../components/account-abstraction/5792-get-capabilities"; -import { Eip5792SendCallsPreview } from "../../../../components/account-abstraction/5792-send-calls"; -import { PageLayout } from "../../../../components/blocks/APIHeader"; -import { CodeExample } from "../../../../components/code/code-example"; +import { Eip5792GetCapabilitiesPreview } from "../../../components/account-abstraction/5792-get-capabilities"; +import { Eip5792SendCallsPreview } from "../../../components/account-abstraction/5792-send-calls"; +import { PageLayout } from "../../../components/blocks/APIHeader"; +import { CodeExample } from "../../../components/code/code-example"; +import { createMetadata } from "../../../lib/metadata"; -export const metadata: Metadata = { - description: - "EIP-5792 capabilities allow you to view the capabilities of the connected wallet", - metadataBase, - title: "EIP-5792 Wallet Capabilities | thirdweb Connect", -}; +const title = "Account Abstraction EIP-5792"; +const description = + "EIP-5792 brings a standard for abstracted actions—combine transaction batching, bundling, and paymasters with a unified smart account interface"; + +export const metadata = createMetadata({ + description, + title, + image: { + icon: "wallets", + title, + }, +}); export default function Page() { return ( - EIP-5792 capabilities allow you to view the capabilities of the - connected wallet. - - } + icon={ShieldIcon} + title={title} + description={description} docsLink="https://portal.thirdweb.com/wallets/sponsor-gas?utm_source=playground" - title="EIP-5792 Wallet Capabilities" >
diff --git a/apps/playground-web/src/app/wallets/account-abstraction/eip-7702/page.tsx b/apps/playground-web/src/app/account-abstraction/eip-7702/page.tsx similarity index 60% rename from apps/playground-web/src/app/wallets/account-abstraction/eip-7702/page.tsx rename to apps/playground-web/src/app/account-abstraction/eip-7702/page.tsx index 21c07b7a75e..3c85d6e6874 100644 --- a/apps/playground-web/src/app/wallets/account-abstraction/eip-7702/page.tsx +++ b/apps/playground-web/src/app/account-abstraction/eip-7702/page.tsx @@ -1,29 +1,33 @@ -import type { Metadata } from "next"; +import { ShieldIcon } from "lucide-react"; import ThirdwebProvider from "@/components/thirdweb-provider"; -import { metadataBase } from "@/lib/constants"; -import { Eip7702SmartAccountPreview } from "../../../../components/account-abstraction/7702-smart-account"; -import { PageLayout } from "../../../../components/blocks/APIHeader"; -import { CodeExample } from "../../../../components/code/code-example"; +import { Eip7702SmartAccountPreview } from "../../../components/account-abstraction/7702-smart-account"; +import { PageLayout } from "../../../components/blocks/APIHeader"; +import { CodeExample } from "../../../components/code/code-example"; +import { createMetadata } from "../../../lib/metadata"; -export const metadata: Metadata = { - description: - "EIP-7702 smart accounts allow you to turn your EOA into a smart account with no code changes", - metadataBase, - title: "EIP-7702 Smart Accounts | thirdweb Connect", -}; +const title = "Account Abstraction EIP-7702"; +const description = + "Enable account abstraction with EIP-7702. Unlock gasless transactions, session keys, paymaster support, and smart wallet programmability"; +const ogDescription = + "Use EIP-7702 to upgrade EOAs into temporary smart accounts. Bring native account abstraction to any wallet, enabling gasless transactions and advanced logic."; + +export const metadata = createMetadata({ + description: ogDescription, + title, + image: { + icon: "wallets", + title, + }, +}); export default function Page() { return ( - EIP-7702 smart accounts allow you to turn your EOA into a smart - account with no code changes. - - } + icon={ShieldIcon} + description={description} docsLink="https://portal.thirdweb.com/wallets/sponsor-gas?utm_source=playground" - title="EIP-7702 Smart Accounts" + title={title} > diff --git a/apps/playground-web/src/app/wallets/account-abstraction/native-aa/page.tsx b/apps/playground-web/src/app/account-abstraction/native-aa/page.tsx similarity index 62% rename from apps/playground-web/src/app/wallets/account-abstraction/native-aa/page.tsx rename to apps/playground-web/src/app/account-abstraction/native-aa/page.tsx index 17cb4e4106f..04c2def05db 100644 --- a/apps/playground-web/src/app/wallets/account-abstraction/native-aa/page.tsx +++ b/apps/playground-web/src/app/account-abstraction/native-aa/page.tsx @@ -1,29 +1,31 @@ -import type { Metadata } from "next"; +import { ShieldIcon } from "lucide-react"; import ThirdwebProvider from "@/components/thirdweb-provider"; -import { metadataBase } from "@/lib/constants"; -import { SponsoredTxZksyncPreview } from "../../../../components/account-abstraction/sponsored-tx-zksync"; -import { PageLayout } from "../../../../components/blocks/APIHeader"; -import { CodeExample } from "../../../../components/code/code-example"; +import { createMetadata } from "@/lib/metadata"; +import { SponsoredTxZksyncPreview } from "../../../components/account-abstraction/sponsored-tx-zksync"; +import { PageLayout } from "../../../components/blocks/APIHeader"; +import { CodeExample } from "../../../components/code/code-example"; -export const metadata: Metadata = { - description: - "On zkSync chains, you can take advantage of native account abstraction with no code changes", - metadataBase, - title: "Native Account Abstraction", -}; +const title = "Native Account Abstraction Through zkSync"; +const description = + "Leverage native account abstraction on zkSync. Use smart accounts out of the box for gasless transactions, batching, and flexible signing logic"; + +export const metadata = createMetadata({ + description, + title, + image: { + icon: "wallets", + title, + }, +}); export default function Page() { return ( - On zkSync chains, you can take advantage of native account - abstraction with no code changes. - - } + icon={ShieldIcon} + title={title} + description={description} docsLink="https://portal.thirdweb.com/wallets/sponsor-gas?utm_source=playground" - title="Native Account Abstraction" > diff --git a/apps/playground-web/src/app/api/og/inter/700.ttf b/apps/playground-web/src/app/api/og/inter/700.ttf new file mode 100644 index 00000000000..8e82c70d108 Binary files /dev/null and b/apps/playground-web/src/app/api/og/inter/700.ttf differ diff --git a/apps/playground-web/src/app/api/og/route.tsx b/apps/playground-web/src/app/api/og/route.tsx new file mode 100644 index 00000000000..0ba52752883 --- /dev/null +++ b/apps/playground-web/src/app/api/og/route.tsx @@ -0,0 +1,108 @@ +/* eslint-disable @next/next/no-img-element */ +import { ImageResponse } from "next/og"; +import { getBaseUrl } from "@/lib/env"; + +export const runtime = "edge"; + +const BASE_URL = getBaseUrl(); + +const width = 1200; +const height = 630; + +const iconSize = 400; + +const inter600 = fetch(new URL("./inter/700.ttf", import.meta.url)).then( + (res) => res.arrayBuffer(), +); + +export async function GET(request: Request) { + const { searchParams } = new URL(request.url); + + const icon = searchParams.get("icon"); + const title = searchParams.get("title"); + + if (!icon || !title) { + return new Response("Failed to generate the image", { + status: 500, + }); + } + + const iconUrl = `${BASE_URL}/og/icons/${icon}.svg`; + + return new ImageResponse( + // ImageResponse JSX element +
+ + + {/* Left */} +
+
+ {title} +
+
+ + +
, + // ImageResponse options + { + fonts: [ + { + data: await inter600, + name: "Inter", + style: "normal", + weight: 600, + }, + ], + height: height, + width: width, + }, + ); +} diff --git a/apps/playground-web/src/app/contracts/events/page.tsx b/apps/playground-web/src/app/contracts/events/page.tsx new file mode 100644 index 00000000000..9d4fc116471 --- /dev/null +++ b/apps/playground-web/src/app/contracts/events/page.tsx @@ -0,0 +1,71 @@ +import { RssIcon } from "lucide-react"; +import { WatchEventPreview } from "@/components/blockchain-api/watch-event-preview"; +import { CodeExample } from "@/components/code/code-example"; +import ThirdwebProvider from "@/components/thirdweb-provider"; +import { PageLayout } from "../../../components/blocks/APIHeader"; +import { createMetadata } from "../../../lib/metadata"; + +const title = "Listen Contract Events"; +const description = + "Subscribe to any contract event with auto-polling hooks and type-safe event handlers. Supports all common standards out of the box"; + +const ogDescription = + "Listen to blockchain contract events using auto-polling hooks and type-safe functions. Supports ERC20, ERC721, and other common Web3 standards with minimal setup."; + +export const metadata = createMetadata({ + title, + description: ogDescription, + image: { + icon: "contract", + title, + }, +}); + +export default function Page() { + return ( + + + + + + ); +} + +function WatchEvent() { + return ( + { + const { from, to, value } = item.args; + console.log("{from}...{value} USDC...{to}"); + }); +} +`} + lang="tsx" + preview={} + /> + ); +} diff --git a/apps/playground-web/src/app/contracts/extensions/page.tsx b/apps/playground-web/src/app/contracts/extensions/page.tsx new file mode 100644 index 00000000000..f4e416eaa5b --- /dev/null +++ b/apps/playground-web/src/app/contracts/extensions/page.tsx @@ -0,0 +1,117 @@ +import { BlocksIcon } from "lucide-react"; +import { ReadContractExtensionPreview } from "@/components/blockchain-api/read-contract-extension"; +import { WriteContractExtensionPreview } from "@/components/blockchain-api/write-contract-extension"; +import { CodeExample } from "@/components/code/code-example"; +import ThirdwebProvider from "@/components/thirdweb-provider"; +import { PageLayout } from "../../../components/blocks/APIHeader"; +import { createMetadata } from "../../../lib/metadata"; + +const title = "Pre-Built Extensions"; +const description = + "High-level ready and write functions with built-in pre/post-processing for common standards."; + +const ogDescription = + "Simplify smart contract reads and writes with prebuilt extensions. Handle approvals, formatting, and standards with built-in pre/post-processing."; + +export const metadata = createMetadata({ + title, + description: ogDescription, + image: { + icon: "contract", + title, + }, +}); + +export default function Page() { + return ( + + +
+ + +
+
+
+ ); +} + +function ReadContractExtension() { + return ( + + ); +} +`} + header={{ + description: + "Extensions let you do more with less code. High level functions with simple API that do pre and post processing for all common standards.", + title: "Prebuilt read extensions", + }} + lang="tsx" + preview={} + /> + ); +} + +function WriteContractExtension() { + return ( + + claimTo({ + contract: twCoinContract, + to: account.address, + quantity: "10", + }) + } + > + Claim + +} +`} + header={{ + description: + "Extensions let you do more with less code. High level functions with simple API that do pre and post processing for all common standards.", + title: "Prebuilt write extensions", + }} + lang="tsx" + preview={} + /> + ); +} diff --git a/apps/playground-web/src/app/contracts/read/page.tsx b/apps/playground-web/src/app/contracts/read/page.tsx new file mode 100644 index 00000000000..5696501581f --- /dev/null +++ b/apps/playground-web/src/app/contracts/read/page.tsx @@ -0,0 +1,73 @@ +import { ScanTextIcon } from "lucide-react"; +import { ReadContractRawPreview } from "@/components/blockchain-api/read-contract-raw"; +import { PageLayout } from "@/components/blocks/APIHeader"; +import { CodeExample } from "@/components/code/code-example"; +import ThirdwebProvider from "@/components/thirdweb-provider"; +import { createMetadata } from "@/lib/metadata"; + +const title = "Read Contract Data"; +const description = + "Read data from any contract on EVM with Type safe functions and hooks without needing contract ABIs"; +const ogDescription = + "Query smart contracts and wallet data using type-safe functions and React hooks—no ABI required. Read blockchain data with full type safety"; + +export const metadata = createMetadata({ + title, + description: ogDescription, + image: { + icon: "contract", + title, + }, +}); + +export default function Page() { + return ( + + +
+ +
+
+
+ ); +} + +function ReadContractRaw() { + return ( + + ); +} +`} + lang="tsx" + preview={} + /> + ); +} diff --git a/apps/playground-web/src/app/contracts/write/page.tsx b/apps/playground-web/src/app/contracts/write/page.tsx new file mode 100644 index 00000000000..0197d463ef7 --- /dev/null +++ b/apps/playground-web/src/app/contracts/write/page.tsx @@ -0,0 +1,76 @@ +import { PencilIcon } from "lucide-react"; +import { WriteContractRawPreview } from "@/components/blockchain-api/write-contract-raw"; +import { CodeExample } from "@/components/code/code-example"; +import ThirdwebProvider from "@/components/thirdweb-provider"; +import { PageLayout } from "../../../components/blocks/APIHeader"; +import { createMetadata } from "../../../lib/metadata"; + +const title = "Write Contract"; +const description = + "Send transactions from the connected wallet using type-safe functions and hooks for contract calls or raw transactions"; + +const ogDescription = + "Send blockchain transactions with the connected wallet using type-safe functions and React hooks for contract calls or raw transactions"; + +export const metadata = createMetadata({ + title, + description: ogDescription, + image: { + icon: "contract", + title, + }, +}); + +export default function Page() { + return ( + + +
+ +
+
+
+ ); +} + +function WriteContractRaw() { + return ( + + prepareContractCall({ + contract: tw_coin, + method: + "function transfer(address to, uint256 value) returns (bool)", + params: [ + "0x...", + toUnits("5", 18), + ], + }) + } + > + Send + +} +`} + lang="tsx" + preview={} + /> + ); +} diff --git a/apps/playground-web/src/app/globals.css b/apps/playground-web/src/app/globals.css index b980b525598..0dd345e81e7 100644 --- a/apps/playground-web/src/app/globals.css +++ b/apps/playground-web/src/app/globals.css @@ -108,6 +108,11 @@ } } +.shiki, +.shiki span { + background-color: transparent !important; +} + .dark .shiki, .dark .shiki span { color: var(--shiki-dark) !important; @@ -117,11 +122,6 @@ text-decoration: var(--shiki-dark-text-decoration) !important; } -.shiki, -.shiki span { - background-color: transparent !important; -} - /* Fix colors on auto-filled inputs */ input:-webkit-autofill, input:-webkit-autofill:hover, @@ -149,3 +149,8 @@ input:-webkit-autofill:active { scrollbar-width: none; /* Firefox */ } } + +* { + scrollbar-width: thin; + scrollbar-color: hsl(var(--muted)) transparent; +} diff --git a/apps/playground-web/src/app/insight/[blueprint_slug]/page.tsx b/apps/playground-web/src/app/insight/[blueprint_slug]/page.tsx index 03483618d08..bd953b23266 100644 --- a/apps/playground-web/src/app/insight/[blueprint_slug]/page.tsx +++ b/apps/playground-web/src/app/insight/[blueprint_slug]/page.tsx @@ -54,7 +54,7 @@ export default async function Page(props: { return (
-

+

Simple & customizable endpoints for querying rich blockchain data - } + description={description} docsLink="https://portal.thirdweb.com/insight?utm_source=playground" - title="Insight" + title={title} > {props.children} diff --git a/apps/playground-web/src/app/layout.tsx b/apps/playground-web/src/app/layout.tsx index aafb518d396..5aa3c9d04ac 100644 --- a/apps/playground-web/src/app/layout.tsx +++ b/apps/playground-web/src/app/layout.tsx @@ -2,12 +2,11 @@ import type { Metadata } from "next"; import { Fira_Code, Inter } from "next/font/google"; import { metadataBase } from "@/lib/constants"; import { cn } from "@/lib/utils"; -import { AppSidebar } from "./AppSidebar"; +import { AppSidebarLayout } from "./AppSidebar"; import { Providers } from "./providers"; import "./globals.css"; +import { ThemeProvider } from "next-themes"; import NextTopLoader from "nextjs-toploader"; -import { MobileHeader } from "./MobileHeader"; -import { getSidebarLinks } from "./navLinks"; const sansFont = Inter({ subsets: ["latin"], @@ -32,7 +31,6 @@ export default async function RootLayout({ }: { children: React.ReactNode; }) { - const sidebarLinks = getSidebarLinks(); return ( - - -
- -
-
+ + +
+ {children} -
+
-
+ ); diff --git a/apps/playground-web/src/app/login/_sdk_.ts b/apps/playground-web/src/app/login/_sdk_.ts deleted file mode 100644 index f65628f772c..00000000000 --- a/apps/playground-web/src/app/login/_sdk_.ts +++ /dev/null @@ -1,95 +0,0 @@ -// TODO: clean all of this up a heck of a lot! - -const LOGIN_URL = "https://login.thirdweb.com"; -const CLIENT_ID = "demo"; - -export async function triggerLogin() { - const { codeChallenge, codeVerifier } = await generateCodeChallenge(); - const state = generateState(); - storeCodeVerifier(codeVerifier); - storeState(state); - const redirectUri = window.location.href; - window.location.href = `${LOGIN_URL}/authorize?client_id=${CLIENT_ID}&redirect_uri=${redirectUri}&response_type=code&state=${state}&code_challenge=${codeChallenge}&code_challenge_method=S256`; -} - -// do code exchange -export async function handleLogin(code: string) { - const codeVerifier = getCodeVerifier(); - const state = getState(); - if (!codeVerifier || !state) { - console.error("No code verifier or state found"); - return; - } - // clear the code verifier and state -> we don't need them anymore - clearCodeVerifier(); - clearState(); - const redirectUri = window.location.href; - - fetch(`${LOGIN_URL}/api/token`, { - body: JSON.stringify({ - client_id: CLIENT_ID, - code, - code_verifier: codeVerifier, - grant_type: "authorization_code", - redirect_uri: redirectUri, - state, - }), - headers: { - "Content-Type": "application/json", - }, - method: "POST", - }) - .then((res) => res.json()) - .then((data) => { - console.log("data", data); - }) - .catch((err) => { - console.error("error", err); - }); -} - -// oauth2 PKCE code challenge generator (code challenge has to be url safe) -async function generateCodeChallenge() { - const codeVerifier = base64UrlEncode( - window.crypto.getRandomValues(new Uint8Array(64)), - ); - const codeChallenge = await window.crypto.subtle.digest( - "SHA-256", - new TextEncoder().encode(codeVerifier), - ); - return { - codeChallenge: base64UrlEncode(codeChallenge), - codeVerifier: codeVerifier, - }; -} - -function generateState() { - const random = window.crypto.getRandomValues(new Uint8Array(12)); - return base64UrlEncode(random); -} - -function base64UrlEncode(array: Uint8Array | ArrayBuffer) { - return btoa(String.fromCharCode(...new Uint8Array(array))) - .replace(/=/g, "") - .replace(/\+/g, "-") - .replace(/\//g, "_"); -} - -function storeCodeVerifier(codeVerifier: string) { - localStorage.setItem("codeVerifier", codeVerifier); -} -function getCodeVerifier() { - return localStorage.getItem("codeVerifier"); -} -function clearCodeVerifier() { - localStorage.removeItem("codeVerifier"); -} -function storeState(state: string) { - localStorage.setItem("state", state); -} -function getState() { - return localStorage.getItem("state"); -} -function clearState() { - localStorage.removeItem("state"); -} diff --git a/apps/playground-web/src/app/login/layout.tsx b/apps/playground-web/src/app/login/layout.tsx deleted file mode 100644 index fe0c8102ecf..00000000000 --- a/apps/playground-web/src/app/login/layout.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { Suspense } from "react"; - -export default function LoginLayout({ - children, -}: { - children: React.ReactNode; -}) { - return Loading...
}>{children}; -} diff --git a/apps/playground-web/src/app/login/page.tsx b/apps/playground-web/src/app/login/page.tsx deleted file mode 100644 index b2f021c1085..00000000000 --- a/apps/playground-web/src/app/login/page.tsx +++ /dev/null @@ -1,32 +0,0 @@ -"use client"; - -import { useSearchParams } from "next/navigation"; -import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { handleLogin, triggerLogin } from "./_sdk_"; - -export default function LoginPage() { - const searchParams = useSearchParams(); - const code = searchParams.get("code"); - if (code) { - handleLogin(code); - } - return ( -
- - - Login Test - - - - - -
- ); -} diff --git a/apps/playground-web/src/app/navLinks.ts b/apps/playground-web/src/app/navLinks.ts index 522db5a5b80..04f898cd37e 100644 --- a/apps/playground-web/src/app/navLinks.ts +++ b/apps/playground-web/src/app/navLinks.ts @@ -1,163 +1,225 @@ -import type { SidebarLink } from "../components/ui/sidebar"; +"use client"; + +import { ArrowLeftRightIcon } from "lucide-react"; +import type { ShadcnSidebarLink } from "@/components/blocks/full-width-sidebar-layout"; +import { ContractIcon } from "../icons/ContractIcon"; +import { InsightIcon } from "../icons/InsightIcon"; +import { PayIcon } from "../icons/PayIcon"; +import { SmartAccountIcon } from "../icons/SmartAccountIcon"; +import { TokenIcon } from "../icons/TokenIcon"; +import { WalletProductIcon } from "../icons/WalletProductIcon"; import { insightBlueprints } from "./insight/insightBlueprints"; -const staticSidebarLinks: SidebarLink[] = [ - { - isCollapsible: false, - links: [ - { - href: "/wallets/sign-in/button", - name: "ConnectButton", - }, - { - href: "/wallets/sign-in/embed", - name: "ConnectEmbed", - }, - { - href: "/wallets/sign-in/headless", - name: "Headless Connect", - }, - { - href: "/wallets/in-app-wallet", - name: "In-App Wallets", - }, - { - href: "/wallets/in-app-wallet/ecosystem", - name: "Ecosystem Wallets", - }, - { - href: "/wallets/account-abstraction/eip-4337", - name: "EIP-4337", - }, - { - href: "/wallets/account-abstraction/eip-7702", - name: "EIP-7702", - }, - { - href: "/wallets/account-abstraction/eip-5792", - name: "EIP-5792", - }, - { - href: "/wallets/account-abstraction/native-aa", - name: "Native AA (zkSync)", - }, - { - href: "/wallets/auth", - name: "Auth", - }, - { - href: "/wallets/social", - name: "Social", - }, - { - href: "/wallets/blockchain-api", - name: "Blockchain API", - }, - { - expanded: false, - links: [ - { - href: "/wallets/headless/account-components", - name: "Account", - }, - { - href: "/wallets/headless/nft-components", - name: "NFT", - }, - { - href: "/wallets/headless/token-components", - name: "Token", - }, - { - href: "/wallets/headless/chain-components", - name: "Chain", - }, - { - href: "/wallets/headless/wallet-components", - name: "Wallet", - }, - ], - name: "Headless Components", +const wallets: ShadcnSidebarLink = { + subMenu: { + label: "Wallets", + icon: WalletProductIcon, + }, + links: [ + { + href: "/wallets/sign-in/button", + label: "Connect Button", + }, + { + href: "/wallets/sign-in/embed", + label: "Connect Embed", + }, + { + href: "/wallets/sign-in/headless", + label: "Headless Connect", + }, + { + href: "/wallets/in-app-wallet", + label: "In-App Wallets", + }, + { + href: "/wallets/ecosystem-wallet", + label: "Ecosystem Wallets", + }, + { + href: "/wallets/auth", + label: "Authentication (SIWE)", + }, + { + href: "/wallets/social", + label: "Social Profiles", + }, + + { + subMenu: { + label: "Headless Components", }, - ], - name: "Wallets", + links: [ + { + href: "/wallets/headless/account-components", + label: "Account Components", + }, + { + href: "/wallets/headless/chain-components", + label: "Chain Components", + }, + { + href: "/wallets/headless/wallet-components", + label: "Wallet Components", + }, + ], + }, + ], +}; + +const contracts: ShadcnSidebarLink = { + subMenu: { + label: "Contracts", + icon: ContractIcon, }, -]; + links: [ + { + href: "/contracts/read", + label: "Read Contract", + }, + { + href: "/contracts/write", + label: "Write Contract", + }, + { + href: "/contracts/extensions", + label: "Pre-built Extensions", + }, + { + href: "/contracts/events", + label: "Listen Contract Events", + }, + ], +}; + +const tokens: ShadcnSidebarLink = { + subMenu: { + label: "Tokens", + icon: TokenIcon, + }, + links: [ + { + href: "/tokens/token-components", + label: "Token Components", + }, + { + href: "/tokens/nft-components", + label: "NFT Components", + }, + ], +}; + +const accountAbstractions: ShadcnSidebarLink = { + subMenu: { + label: "Account Abstraction", + icon: SmartAccountIcon, + }, + links: [ + { + href: "/account-abstraction/eip-4337", + label: "EIP-4337", + }, + { + href: "/account-abstraction/eip-7702", + label: "EIP-7702", + }, + { + href: "/account-abstraction/eip-5792", + label: "EIP-5792", + }, + { + href: "/account-abstraction/native-aa", + label: "Native AA (zkSync)", + }, + ], +}; -const universalBridgeSidebarLinks: SidebarLink = { - expanded: false, - isCollapsible: false, +const payments: ShadcnSidebarLink = { + subMenu: { + label: "Payments", + icon: PayIcon, + }, links: [ { href: "/payments/ui-components", - name: "UI Component", + label: "UI Components", }, { href: "/payments/fund-wallet", - name: "Buy Crypto", + label: "Buy Crypto", }, { href: "/payments/commerce", - name: "Checkout", + label: "Checkout", }, { href: "/payments/transactions", - name: "Transactions", + label: "Onchain Transaction", }, { href: "/payments/backend", - name: "Backend API", + label: "Payments API", }, ], - name: "Payments", }; -const engineSidebarLinks: SidebarLink = { - expanded: false, - isCollapsible: false, +const transactions: ShadcnSidebarLink = { + subMenu: { + label: "Transactions", + icon: ArrowLeftRightIcon, + }, links: [ { href: "/transactions/airdrop-tokens", - name: "Airdrop", + label: "Airdrop Tokens", }, { href: "/transactions/mint-tokens", - name: "Mint NFTs", + label: "Mint NFTs", }, { href: "/transactions/webhooks", - name: "Webhooks", + label: "Webhooks", }, ], - name: "Transactions", }; -export function getSidebarLinks() { - const insightLinks: SidebarLink[] = insightBlueprints.map((blueprint) => { - return { - expanded: false, - links: blueprint.paths.map((pathInfo) => { - return { - crossedOut: pathInfo.deprecated, - href: `/insight/${blueprint.id}?path=${pathInfo.path}`, - name: pathInfo.name, - }; - }), - name: blueprint.name, - }; - }); +const insightLinks: ShadcnSidebarLink[] = insightBlueprints.map((blueprint) => { + return { + links: blueprint.paths.map((pathInfo) => { + return { + href: `/insight/${blueprint.id}?path=${pathInfo.path}`, + label: pathInfo.name, + exactMatch: true, + }; + }), + subMenu: { + label: blueprint.name, + }, + }; +}); - const sidebarLinks: SidebarLink[] = [ - ...staticSidebarLinks, - universalBridgeSidebarLinks, - engineSidebarLinks, +const insight: ShadcnSidebarLink = { + links: [ { - expanded: false, - isCollapsible: false, - links: insightLinks, - name: "Insight", + href: "/insight", + label: "Overview", + exactMatch: true, }, - ]; + ...insightLinks, + ], + subMenu: { + label: "Insight", + icon: InsightIcon, + }, +}; - return sidebarLinks; -} +export const sidebarLinks: ShadcnSidebarLink[] = [ + wallets, + transactions, + contracts, + payments, + tokens, + insight, + accountAbstractions, +]; diff --git a/apps/playground-web/src/app/opengraph-image.jpg b/apps/playground-web/src/app/opengraph-image.jpg new file mode 100644 index 00000000000..e921ca372b4 Binary files /dev/null and b/apps/playground-web/src/app/opengraph-image.jpg differ diff --git a/apps/playground-web/src/app/otherLinks.ts b/apps/playground-web/src/app/otherLinks.ts deleted file mode 100644 index fded9feab67..00000000000 --- a/apps/playground-web/src/app/otherLinks.ts +++ /dev/null @@ -1,14 +0,0 @@ -export const otherLinks: { name: string; href: string }[] = [ - { - href: "https://thirdweb.com/team?utm_source=playground", - name: "Dashboard", - }, - { - href: "https://portal.thirdweb.com?utm_source=playground", - name: "Docs", - }, - { - href: "https://github.com/thirdweb-dev", - name: "Github", - }, -]; diff --git a/apps/playground-web/src/app/page.tsx b/apps/playground-web/src/app/page.tsx index 41e53f42c88..451c955bd55 100644 --- a/apps/playground-web/src/app/page.tsx +++ b/apps/playground-web/src/app/page.tsx @@ -1,5 +1,294 @@ -import { redirect } from "next/navigation"; +import { + ArrowLeftRightIcon, + BlocksIcon, + BoxIcon, + BracesIcon, + CreditCardIcon, + GlobeIcon, + LockIcon, + PanelTopIcon, + PencilIcon, + PlaneIcon, + RectangleHorizontalIcon, + RssIcon, + ScanTextIcon, + ShieldIcon, + ShoppingBagIcon, + SquircleDashedIcon, + StampIcon, + UserIcon, +} from "lucide-react"; +import Link from "next/link"; +import { InsightIcon } from "../icons/InsightIcon"; +import { ThirdwebIcon } from "../icons/ThirdwebMiniLogo"; export default function Page() { - redirect("/wallets/sign-in/button"); + return ( +
+ {/* Hero Section */} +
+
+
+
+ +
+
+ +

+ thirdweb Playground +

+

+ Interactive UI components and endpoints to test, tweak, and ship + faster with thirdweb. +

+
+
+ +
+ {/* Wallets Section */} +
+ +
+ + + + + + +
+
+ + {/* Transactions Section */} +
+ +
+ + + +
+
+ + {/* Contracts Section */} +
+ +
+ + + + +
+
+ + {/* Payments Section */} +
+ +
+ + + + + +
+
+ + {/* Insight Section */} +
+ +
+ + + + + + +
+
+ + {/* Account Abstraction Section */} +
+ +
+ + + + +
+
+
+
+ ); +} + +function SectionTitle(props: { label: string }) { + return ( +

+ {props.label} +

+ ); +} + +function FeatureCard(props: { + title: string; + description: string; + icon: React.FC<{ className?: string }>; + href: string; // todo make this required +}) { + return ( +
+
+
+ +
+
+

+ + {props.title} + +

+

{props.description}

+
+ ); } diff --git a/apps/playground-web/src/app/payments/backend/layout.tsx b/apps/playground-web/src/app/payments/backend/layout.tsx index acebc58595b..efd08171f3b 100644 --- a/apps/playground-web/src/app/payments/backend/layout.tsx +++ b/apps/playground-web/src/app/payments/backend/layout.tsx @@ -1,15 +1,32 @@ +import { BracesIcon } from "lucide-react"; import type React from "react"; -import { PageHeader } from "../../../components/blocks/APIHeader"; +import { PageHeader } from "@/components/blocks/APIHeader"; +import { createMetadata } from "@/lib/metadata"; + +const title = "Payments API"; +const description = + "Create customizable components or backend flows with an HTTP API to onramp, swap, and bridge to and from different cryptocurrencies"; +const ogDescription = + "Bridge, swap, and onramp any currency with thirdweb’s Payments REST API. Use simple HTTP requests to integrate from your backend."; + +export const metadata = createMetadata({ + description: ogDescription, + title, + image: { + icon: "payments", + title, + }, +}); export default function Layout(props: { children: React.ReactNode }) { return (
HTTP API to bridge, swap and onramp to and from any currency - } + description={description} docsLink="https://portal.thirdweb.com/payments?utm_source=playground" - title="Payments API" + icon={BracesIcon} + title={title} + containerClassName="max-w-[1400px]" /> {props.children} diff --git a/apps/playground-web/src/app/payments/backend/page.tsx b/apps/playground-web/src/app/payments/backend/page.tsx index 6a8d1e4f6c0..91685528891 100644 --- a/apps/playground-web/src/app/payments/backend/page.tsx +++ b/apps/playground-web/src/app/payments/backend/page.tsx @@ -1,3 +1,4 @@ +import { ArrowUpRightIcon } from "lucide-react"; import Link from "next/link"; import { Button } from "@/components/ui/button"; import { Table, TableBody, TableCell, TableRow } from "@/components/ui/table"; @@ -7,10 +8,10 @@ export default async function Page() { try { const paths = await getBridgePaths(); return ( -
+
-

+

thirdweb Payments REST API

@@ -20,8 +21,13 @@ export default async function Page() {

-
diff --git a/apps/playground-web/src/app/payments/backend/reference/page.tsx b/apps/playground-web/src/app/payments/backend/reference/page.tsx index 44e2057f2a9..1f0753622ce 100644 --- a/apps/playground-web/src/app/payments/backend/reference/page.tsx +++ b/apps/playground-web/src/app/payments/backend/reference/page.tsx @@ -36,7 +36,7 @@ export default async function Page(props: { const title = pathMetadata.summary || ""; return ( -
+
{title && ( diff --git a/apps/playground-web/src/app/payments/commerce/page.tsx b/apps/playground-web/src/app/payments/commerce/page.tsx index 6b99c528123..2f1fc22d8bb 100644 --- a/apps/playground-web/src/app/payments/commerce/page.tsx +++ b/apps/playground-web/src/app/payments/commerce/page.tsx @@ -1,28 +1,33 @@ -import type { Metadata } from "next"; +import { CreditCardIcon } from "lucide-react"; import { PageLayout } from "@/components/blocks/APIHeader"; import { CodeExample } from "@/components/code/code-example"; import { BuyMerchPreview } from "@/components/pay/direct-payment"; import ThirdwebProvider from "@/components/thirdweb-provider"; -import { metadataBase } from "@/lib/constants"; +import { createMetadata } from "@/lib/metadata"; -export const metadata: Metadata = { - description: - "The easiest way for users to transact in your app. Onramp users in clicks and generate revenue for each user transaction. Integrate for free.", - metadataBase, - title: "Integrate Fiat & Cross-Chain Crypto Payments | thirdweb Payments", -}; +const title = "Checkout Component"; +const description = + "Enable purchase of any service or goods with fiat or cryptocurrency and setup notifications on every sale to ship goods, activate services, and more"; +const ogDescription = + "Accept fiat or crypto payments on any chain—direct to your wallet. Instant checkout, webhook support, and full control over post-sale actions."; + +export const metadata = createMetadata({ + description: ogDescription, + title, + image: { + icon: "payments", + title, + }, +}); export default function Page() { return ( - Let your users pay for any service with fiat or crypto on any chain. - - } + icon={CreditCardIcon} + title={title} + description={description} docsLink="https://portal.thirdweb.com/payments?utm_source=playground" - title="Commerce payments with fiat or crypto" > diff --git a/apps/playground-web/src/app/payments/fund-wallet/page.tsx b/apps/playground-web/src/app/payments/fund-wallet/page.tsx index 6d09963ea87..c16f446a36d 100644 --- a/apps/playground-web/src/app/payments/fund-wallet/page.tsx +++ b/apps/playground-web/src/app/payments/fund-wallet/page.tsx @@ -1,29 +1,33 @@ -import type { Metadata } from "next"; +import { ShoppingBagIcon } from "lucide-react"; import { PageLayout } from "@/components/blocks/APIHeader"; import { CodeExample } from "@/components/code/code-example"; import ThirdwebProvider from "@/components/thirdweb-provider"; import { StyledBuyWidgetPreview } from "@/components/universal-bridge/buy"; -import { metadataBase } from "@/lib/constants"; +import { createMetadata } from "@/lib/metadata"; -export const metadata: Metadata = { - description: - "The easiest way for users to fund their wallets. Onramp users in clicks and generate revenue for each user transaction. Integrate for free.", - metadataBase, - title: "Buy Crypto | thirdweb Payments", -}; +const title = "Buy Crypto Component"; +const description = + "Embeddable component for users to purchase any cryptocurrency for top-ups and more with fiat or crypto-to-crypto swaps"; +const ogDescription = + "Configure a component to buy cryptocurrency with specified amounts, customization, and more. This interactive playground shows how to customize the component."; + +export const metadata = createMetadata({ + description: ogDescription, + title, + image: { + icon: "payments", + title, + }, +}); export default function Page() { return ( - Onramp users with credit card & cross-chain crypto payments — - and generate revenue for each user transaction. - - } + icon={ShoppingBagIcon} + title={title} + description={description} docsLink="https://portal.thirdweb.com/wallets/sponsor-gas?utm_source=playground" - title="The easiest way for users to fund their wallets" > diff --git a/apps/playground-web/src/app/payments/opengraph-image.png b/apps/playground-web/src/app/payments/opengraph-image.png deleted file mode 100644 index 7a71cef0b4a..00000000000 Binary files a/apps/playground-web/src/app/payments/opengraph-image.png and /dev/null differ diff --git a/apps/playground-web/src/app/payments/transactions/page.tsx b/apps/playground-web/src/app/payments/transactions/page.tsx index e95dd0ef130..5c63c63eda7 100644 --- a/apps/playground-web/src/app/payments/transactions/page.tsx +++ b/apps/playground-web/src/app/payments/transactions/page.tsx @@ -1,4 +1,4 @@ -import type { Metadata } from "next"; +import { ArrowLeftRightIcon } from "lucide-react"; import { PageLayout } from "@/components/blocks/APIHeader"; import { CodeExample } from "@/components/code/code-example"; import { @@ -6,28 +6,32 @@ import { PayTransactionPreview, } from "@/components/pay/transaction-button"; import ThirdwebProvider from "@/components/thirdweb-provider"; -import { metadataBase } from "@/lib/constants"; +import { createMetadata } from "@/lib/metadata"; -export const metadata: Metadata = { - description: - "The easiest way for users to transact in your app. Onramp users in clicks and generate revenue for each user transaction. Integrate for free.", - metadataBase, - title: "Integrate Fiat & Cross-Chain Crypto Payments | thirdweb Pay", -}; +const title = "Onchain Transaction Component"; +const description = + "Enable seamless onchain transactions for any contract with fiat or crypto with amounts calculated and automatic execution after funds are confirmed."; +const ogDescription = + "Power onchain transactions with fiat or crypto payments. Automatically calculate costs and run the transaction post onramp or token swap."; + +export const metadata = createMetadata({ + description: ogDescription, + title, + image: { + icon: "payments", + title, + }, +}); export default function Page() { return ( - Let your users pay for onchain transactions with fiat or crypto on - any chain. - - } + description={description} docsLink="https://portal.thirdweb.com/wallets/sponsor-gas?utm_source=playground" - title="Onchain transactions with fiat or crypto" + title={title} > diff --git a/apps/playground-web/src/app/payments/ui-components/page.tsx b/apps/playground-web/src/app/payments/ui-components/page.tsx index 042b99cb31f..7420766a344 100644 --- a/apps/playground-web/src/app/payments/ui-components/page.tsx +++ b/apps/playground-web/src/app/payments/ui-components/page.tsx @@ -1,15 +1,23 @@ -import type { Metadata } from "next"; +import { BoxIcon } from "lucide-react"; +import { PageLayout } from "@/components/blocks/APIHeader"; import ThirdwebProvider from "@/components/thirdweb-provider"; -import { metadataBase } from "@/lib/constants"; -import { PageLayout } from "../../../components/blocks/APIHeader"; +import { createMetadata } from "@/lib/metadata"; import PayEmbedPlayground from "../embed/page"; -export const metadata: Metadata = { - description: - "The easiest way for users to transact in your app. Onramp users, pay with any token and generate revenue for each user transaction. Integrate for free.", - metadataBase, - title: "Integrate Fiat & Cross-Chain Crypto Payments | thirdweb Payments", -}; +const title = "Crypto Payments UI Components"; +const description = + "Onramp, swap, & bridge over 1,000+ tokens to enable seamless crypto payments, checkouts, and transactions"; +const ogDescription = + "Onramp, swap, and bridge cryptocurrency with easy to implement components for purchasing crypto, checking out physical or digital goods and services, and executing onchain transactions."; + +export const metadata = createMetadata({ + description: ogDescription, + title, + image: { + icon: "payments", + title, + }, +}); export default function Page(props: { searchParams: Promise<{ tab: string }>; @@ -17,14 +25,10 @@ export default function Page(props: { return ( - Onramp users with credit card & cross-chain crypto payments — - and generate revenue for each user transaction. - - } + icon={BoxIcon} + description={description} docsLink="https://portal.thirdweb.com/payments?utm_source=playground" - title="Payments UI component" + title={title} > diff --git a/apps/playground-web/src/app/token-selector-demo/page.tsx b/apps/playground-web/src/app/token-selector-demo/page.tsx deleted file mode 100644 index dbeceb2e44d..00000000000 --- a/apps/playground-web/src/app/token-selector-demo/page.tsx +++ /dev/null @@ -1,87 +0,0 @@ -"use client"; - -import { useState } from "react"; -import { arbitrum, base, ethereum } from "thirdweb/chains"; -import { PageLayout } from "@/components/blocks/APIHeader"; -import ThirdwebProvider from "@/components/thirdweb-provider"; -import { TokenSelector } from "@/components/ui/TokenSelector"; -import { THIRDWEB_CLIENT } from "@/lib/client"; -import type { TokenMetadata } from "@/lib/types"; - -export default function TokenSelectorDemo() { - const [selectedToken, setSelectedToken] = useState< - { chainId: number; address: string } | undefined - >(undefined); - - const [selectedChain, setSelectedChain] = useState(ethereum.id); - - const chains = [ - { id: ethereum.id, name: "Ethereum" }, - { id: base.id, name: "Base" }, - { id: arbitrum.id, name: "Arbitrum" }, - ]; - - return ( - - -
-
-

Select a Chain

- -
- -
-

Select a Token

-
- { - setSelectedToken({ - address: token.address, - chainId: token.chainId, - }); - }} - placeholder="Select a token" - selectedToken={selectedToken} - /> -
-
- - {selectedToken && ( -
-

Selected Token

-
-

- Chain ID: {selectedToken.chainId} -

-

- Address: {selectedToken.address} -

-
-
- )} -
-
-
- ); -} diff --git a/apps/playground-web/src/app/wallets/headless/nft-components/page.tsx b/apps/playground-web/src/app/tokens/nft-components/page.tsx similarity index 53% rename from apps/playground-web/src/app/wallets/headless/nft-components/page.tsx rename to apps/playground-web/src/app/tokens/nft-components/page.tsx index 99145089022..ebd5fb7b42c 100644 --- a/apps/playground-web/src/app/wallets/headless/nft-components/page.tsx +++ b/apps/playground-web/src/app/tokens/nft-components/page.tsx @@ -1,4 +1,4 @@ -import type { Metadata } from "next"; +import { ImageIcon } from "lucide-react"; import { PageLayout } from "@/components/blocks/APIHeader"; import { NftCardExample, @@ -7,24 +7,29 @@ import { NftNameExample, } from "@/components/headless-ui/nft-examples"; import ThirdwebProvider from "@/components/thirdweb-provider"; -import { metadataBase } from "@/lib/constants"; +import { createMetadata } from "@/lib/metadata"; -export const metadata: Metadata = { - description: - "Elevate your NFT marketplace with our React headless UI components, engineered for seamless digital asset transactions. These customizable, zero-styling components simplify NFT interactions while giving developers complete freedom to craft their perfect user interface.", - metadataBase, - title: "NFT Components", -}; +const title = "NFT Components"; +const description = + "Headless UI components for rendering NFT Media and metadata"; + +export const metadata = createMetadata({ + title, + description, + image: { + icon: "wallets", + title, + }, +}); export default function Page() { return ( Headless UI components for rendering NFT Media and metadata - } + icon={ImageIcon} + description={description} docsLink="https://portal.thirdweb.com/react/v5/components/onchain#nfts?utm_source=playground" - title="NFT Components" + title={title} >
diff --git a/apps/playground-web/src/app/wallets/headless/token-components/page.tsx b/apps/playground-web/src/app/tokens/token-components/page.tsx similarity index 58% rename from apps/playground-web/src/app/wallets/headless/token-components/page.tsx rename to apps/playground-web/src/app/tokens/token-components/page.tsx index 0ef6505090e..118ca4c7fec 100644 --- a/apps/playground-web/src/app/wallets/headless/token-components/page.tsx +++ b/apps/playground-web/src/app/tokens/token-components/page.tsx @@ -1,4 +1,4 @@ -import type { Metadata } from "next"; +import { DollarSignIcon } from "lucide-react"; import { PageLayout } from "@/components/blocks/APIHeader"; import { TokenImageBasic, @@ -6,27 +6,30 @@ import { TokenSymbolBasic, } from "@/components/headless-ui/token-examples"; import ThirdwebProvider from "@/components/thirdweb-provider"; -import { metadataBase } from "@/lib/constants"; +import { createMetadata } from "@/lib/metadata"; -export const metadata: Metadata = { - description: - "Headless UI components for rendering token image, name, and symbol", - metadataBase, - title: "Token Components", -}; +const title = "Token Components"; +const description = + "Headless UI components for rendering token image, name, and symbol"; + +export const metadata = createMetadata({ + title, + description, + image: { + icon: "wallets", + title, + }, +}); export default function Page() { return ( - Headless UI components for rendering token image, name, and symbol - - } + icon={DollarSignIcon} + description={description} docsLink="https://portal.thirdweb.com/react/v5/components/onchain#tokens?utm_source=playground" - title="Token Components" + title={title} > diff --git a/apps/playground-web/src/app/transactions/airdrop-tokens/page.tsx b/apps/playground-web/src/app/transactions/airdrop-tokens/page.tsx index 247b1c75f04..7230d65bde8 100644 --- a/apps/playground-web/src/app/transactions/airdrop-tokens/page.tsx +++ b/apps/playground-web/src/app/transactions/airdrop-tokens/page.tsx @@ -1,20 +1,33 @@ +import { PlaneIcon } from "lucide-react"; import { EngineAirdropPreview } from "@/app/transactions/airdrop-tokens/_components/airdrop-preview"; +import { PageLayout } from "@/components/blocks/APIHeader"; import ThirdwebProvider from "@/components/thirdweb-provider"; -import { PageLayout } from "../../../components/blocks/APIHeader"; +import { createMetadata } from "@/lib/metadata"; import { AirdropCode } from "./_components/airdrop-code"; +const title = "Airdrop Tokens"; +const description = + "Airdrop ERC-20, ERC-721, or ERC-1155 tokens at scale to multiple addresses. Support gas sponsorship and receive real-time status updates for each transaction."; +const ogDescription = + "Airdrop any collection of ERC20, ERC721, or ERC1155 tokens with a few lines of code. Try the flow, inspect the smart contract, and copy code directly into your app."; + +export const metadata = createMetadata({ + description: ogDescription, + title, + image: { + icon: "transactions", + title, + }, +}); + export default function Page() { return ( - Engine makes it effortless for any developer to airdrop tokens at - scale. You sponsor the gas so your users only need a wallet address! - - } + icon={PlaneIcon} + description={description} docsLink="https://thirdweb-engine.apidocumentation.com/reference#tag/erc20/POST/contract/{chain}/{contractAddress}/erc20/mint-batch-to?utm_source=playground" - title="Airdrop" + title={title} >
diff --git a/apps/playground-web/src/app/transactions/mint-tokens/page.tsx b/apps/playground-web/src/app/transactions/mint-tokens/page.tsx index 009291364db..febda3cab4b 100644 --- a/apps/playground-web/src/app/transactions/mint-tokens/page.tsx +++ b/apps/playground-web/src/app/transactions/mint-tokens/page.tsx @@ -1,20 +1,33 @@ +import { StampIcon } from "lucide-react"; import { EngineMintPreview } from "@/app/transactions/mint-tokens/_components/mint-preview"; +import { PageLayout } from "@/components/blocks/APIHeader"; import ThirdwebProvider from "@/components/thirdweb-provider"; -import { PageLayout } from "../../../components/blocks/APIHeader"; +import { createMetadata } from "@/lib/metadata"; import { MintCode } from "./_components/mint-code"; +const title = "Mint NFTs"; +const description = + "Enable users to mint new tokens into any smart contract. Gas fees are sponsored, so users only need to provide a wallet address"; +const ogDescription = + "Interactive demo for gasless token minting. Mint tokens to any contract with just a wallet address. Sponsor gas and streamline UX."; + +export const metadata = createMetadata({ + description: ogDescription, + title, + image: { + icon: "transactions", + title, + }, +}); + export default function Page() { return ( - Allow your users to mint new tokens into any given contract. You - sponsor the gas so your users only need a wallet address! - - } + icon={StampIcon} + description={description} docsLink="https://thirdweb-engine.apidocumentation.com/reference#tag/erc1155/POST/contract/{chain}/{contractAddress}/erc1155/mint-to?utm_source=playground" - title="Mint Dynamic NFTs" + title={title} >
diff --git a/apps/playground-web/src/app/transactions/webhooks/page.tsx b/apps/playground-web/src/app/transactions/webhooks/page.tsx index 66c458f59e7..02ad2d7a32e 100644 --- a/apps/playground-web/src/app/transactions/webhooks/page.tsx +++ b/apps/playground-web/src/app/transactions/webhooks/page.tsx @@ -1,19 +1,32 @@ +import { RssIcon } from "lucide-react"; import { EngineWebhooksPreview } from "@/app/transactions/webhooks/_components/webhooks-preview"; +import { PageLayout } from "@/components/blocks/APIHeader"; import ThirdwebProvider from "@/components/thirdweb-provider"; -import { PageLayout } from "../../../components/blocks/APIHeader"; +import { createMetadata } from "@/lib/metadata"; + +const title = "Transaction Webhooks"; +const description = + "Configure webhooks to notify your backend of transaction events or activity from your server wallet"; +const ogDescription = + "Set up webhooks using Transactions to receive real-time notifications for transactions and wallet events. Test the flow and see example payloads in action."; + +export const metadata = createMetadata({ + description: ogDescription, + title, + image: { + icon: "transactions", + title, + }, +}); export default function Page() { return ( - Configure webhooks in Engine to notify your backend server of - transaction or backend wallet events. - - } + icon={RssIcon} + description={description} docsLink="https://portal.thirdweb.com/engine/v2/features/webhooks?utm_source=playground" - title="Webhooks" + title={title} > diff --git a/apps/playground-web/src/app/wallets/account-abstraction/connect/page.tsx b/apps/playground-web/src/app/wallets/account-abstraction/connect/page.tsx deleted file mode 100644 index afd5eb2301b..00000000000 --- a/apps/playground-web/src/app/wallets/account-abstraction/connect/page.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import type { Metadata } from "next"; -import ThirdwebProvider from "@/components/thirdweb-provider"; -import { metadataBase } from "@/lib/constants"; -import { - ConnectSmartAccountCustomPreview, - ConnectSmartAccountPreview, -} from "../../../../components/account-abstraction/connect-smart-account"; -import { PageLayout } from "../../../../components/blocks/APIHeader"; -import { CodeExample } from "../../../../components/code/code-example"; - -export const metadata: Metadata = { - description: - "Let users sign up with their email, phone number, social media accounts or directly with a wallet. Seamlessly integrate account abstraction and SIWE auth.", - metadataBase, - title: "Account Abstraction | thirdweb Connect", -}; - -export default function Page() { - return ( - - - Let users connect to their smart accounts with any wallet and unlock - gas sponsorship, batched transactions, session keys and full wallet - programmability. - - } - docsLink="https://portal.thirdweb.com/wallets/sponsor-gas?utm_source=playground" - title="Connect smart accounts" - > - - - - ); -} - -function ConnectSmartAccount() { - return ( - <> - - -); -};`} - header={{ - description: - "Use the prebuilt UI components to connect to smart accounts", - title: "Using prebuilt UI component", - }} - lang="tsx" - preview={} - /> - -
- - -); -};`} - header={{ - description: "Build your own UI to connect to smart accounts", - title: "Build custom UI", - }} - lang="tsx" - preview={} - /> - - ); -} diff --git a/apps/playground-web/src/app/wallets/auth/page.tsx b/apps/playground-web/src/app/wallets/auth/page.tsx index 07b57daf2c1..42f7ebb3a47 100644 --- a/apps/playground-web/src/app/wallets/auth/page.tsx +++ b/apps/playground-web/src/app/wallets/auth/page.tsx @@ -1,33 +1,34 @@ -import type { Metadata } from "next"; +import { LockIcon } from "lucide-react"; import { BasicAuthPreview } from "@/components/auth/basic-auth"; import { GatedContentPreview } from "@/components/auth/gated-content"; import { SmartAccountAuthPreview } from "@/components/auth/smart-account-auth"; import { CodeExample } from "@/components/code/code-example"; import ThirdwebProvider from "@/components/thirdweb-provider"; -import { metadataBase } from "@/lib/constants"; import { BasicAuthHookPreview } from "../../../components/auth/basic-auth-hook"; import { PageLayout } from "../../../components/blocks/APIHeader"; +import { createMetadata } from "../../../lib/metadata"; -export const metadata: Metadata = { - description: - "Authenticate users to your backend using only their wallet. This is a secure and easy way to authenticate users without requiring them to create an additional account.", - metadataBase, - title: "Auth | thirdweb Connect", -}; +const title = "Authentication (SIWE)"; +const description = + "Add secure wallet authentication to your app using SIWE and JWT. Authenticate users without passwords via Sign-In with Ethereum and backend token validation"; + +export const metadata = createMetadata({ + description, + title, + image: { + icon: "wallets", + title, + }, +}); export default function Page() { return ( - Authenticate users to your backend using only their wallet. This is - a secure and easy way to authenticate users without requiring them - to create an additional account. - - } + icon={LockIcon} + title={title} + description={description} docsLink="https://portal.thirdweb.com/typescript/v5/auth?utm_source=playground" - title="Auth" >
diff --git a/apps/playground-web/src/app/wallets/blockchain-api/page.tsx b/apps/playground-web/src/app/wallets/blockchain-api/page.tsx deleted file mode 100644 index 5fca12d0f02..00000000000 --- a/apps/playground-web/src/app/wallets/blockchain-api/page.tsx +++ /dev/null @@ -1,241 +0,0 @@ -import type { Metadata } from "next"; -import { ReadContractExtensionPreview } from "@/components/blockchain-api/read-contract-extension"; -import { ReadContractRawPreview } from "@/components/blockchain-api/read-contract-raw"; -import { WatchEventPreview } from "@/components/blockchain-api/watch-event-preview"; -import { WriteContractExtensionPreview } from "@/components/blockchain-api/write-contract-extension"; -import { WriteContractRawPreview } from "@/components/blockchain-api/write-contract-raw"; -import { CodeExample } from "@/components/code/code-example"; -import ThirdwebProvider from "@/components/thirdweb-provider"; -import { metadataBase } from "@/lib/constants"; -import { PageLayout } from "../../../components/blocks/APIHeader"; - -export const metadata: Metadata = { - description: - "Interact with EVM blockchains using thirdweb SDK. Create seamless NFT minting experience. Airdrop tokens to millions of users", - metadataBase, - title: "Blockchain API | thirdweb Connect", -}; - -export default function Page() { - return ( - - - Performant, reliable and type safe API to read write to any contract - on any EVM chain through our RPC Edge endpoints. - - } - docsLink="https://portal.thirdweb.com/typescript/v5?utm_source=playground" - title="Blockchain API" - > -
- - - - - - -
-
-
- ); -} - -function ReadContractRaw() { - return ( - - ); -} -`} - header={{ - description: - "Read data from any contract or wallet. Type safe functions and hooks without needing full ABIs.", - title: "Query blockchain data", - }} - lang="tsx" - preview={} - /> - ); -} - -function ReadContractExtension() { - return ( - - ); -} -`} - header={{ - description: - "Extensions let you do more with less code. High level functions with simple API that do pre and post processing for all common standards.", - title: "Prebuilt read extensions", - }} - lang="tsx" - preview={} - /> - ); -} - -function WriteContractExtension() { - return ( - - claimTo({ - contract: twCoinContract, - to: account.address, - quantity: "10", - }) - } - > - Claim - -} -`} - header={{ - description: - "Extensions let you do more with less code. High level functions with simple API that do pre and post processing for all common standards.", - title: "Prebuilt write extensions", - }} - lang="tsx" - preview={} - /> - ); -} - -function WriteContractRaw() { - return ( - - prepareContractCall({ - contract: tw_coin, - method: - "function transfer(address to, uint256 value) returns (bool)", - params: [ - "0x...", - toUnits("5", 18), - ], - }) - } - > - Send - -} -`} - header={{ - description: - "Send transactions with the connected wallet. Type safe functions and hooks to send contracts call or raw transaction.", - title: "Write data to blockchain", - }} - lang="tsx" - preview={} - /> - ); -} - -function WatchEvent() { - return ( - { - const { from, to, value } = item.args; - console.log("{from}...{value} USDC...{to}"); - }); -} -`} - header={{ - description: - "Subscribe to any contract event. Auto polling hooks and functions with type safe event extensions for all common standards.", - title: "Listen to blockchain events", - }} - lang="tsx" - preview={} - /> - ); -} diff --git a/apps/playground-web/src/app/wallets/in-app-wallet/ecosystem/page.tsx b/apps/playground-web/src/app/wallets/ecosystem-wallet/page.tsx similarity index 56% rename from apps/playground-web/src/app/wallets/in-app-wallet/ecosystem/page.tsx rename to apps/playground-web/src/app/wallets/ecosystem-wallet/page.tsx index 04a5be61f82..5a2202de4a1 100644 --- a/apps/playground-web/src/app/wallets/in-app-wallet/ecosystem/page.tsx +++ b/apps/playground-web/src/app/wallets/ecosystem-wallet/page.tsx @@ -1,30 +1,32 @@ -import type { Metadata } from "next"; +import { AtomIcon } from "lucide-react"; +import { PageLayout } from "@/components/blocks/APIHeader"; import { CodeExample } from "@/components/code/code-example"; -import { PageLayout } from "../../../../components/blocks/APIHeader"; -import { EcosystemConnectEmbed } from "../../../../components/in-app-wallet/ecosystem"; -import { Profiles } from "../../../../components/in-app-wallet/profile-sections"; -import ThirdwebProvider from "../../../../components/thirdweb-provider"; -import { metadataBase } from "../../../../lib/constants"; +import { EcosystemConnectEmbed } from "@/components/in-app-wallet/ecosystem"; +import { Profiles } from "@/components/in-app-wallet/profile-sections"; +import ThirdwebProvider from "@/components/thirdweb-provider"; +import { createMetadata } from "@/lib/metadata"; -export const metadata: Metadata = { - description: - "Build a public or permissioned ecosystem by allowing third party apps and games to connect to the same accounts.", - metadataBase, - title: "Build your own Ecosystem | thirdweb", -}; +const title = "Ecosystem Wallets"; +const description = + "Enable global wallet-based identity across apps and games. Create public or permissioned ecosystems where users keep one account everywhere."; + +export const metadata = createMetadata({ + description: description, + title, + image: { + icon: "wallets", + title, + }, +}); export default function Page() { return ( - Build a public or permissioned ecosystem by allowing third party - apps and games to connect to the same accounts. - - } + icon={AtomIcon} + title={title} + description={description} docsLink="https://portal.thirdweb.com/wallets/ecosystem/set-up?utm_source=playground" - title="Build your own Ecosystem" >
diff --git a/apps/playground-web/src/app/wallets/headless/account-components/page.tsx b/apps/playground-web/src/app/wallets/headless/account-components/page.tsx index c30d19b6a8b..46632e6969b 100644 --- a/apps/playground-web/src/app/wallets/headless/account-components/page.tsx +++ b/apps/playground-web/src/app/wallets/headless/account-components/page.tsx @@ -1,4 +1,4 @@ -import type { Metadata } from "next"; +import { CircleUserIcon } from "lucide-react"; import { PageLayout } from "@/components/blocks/APIHeader"; import { AccountAvatarExample, @@ -7,27 +7,29 @@ import { AccountNameExample, } from "@/components/headless-ui/account-examples"; import ThirdwebProvider from "@/components/thirdweb-provider"; -import { metadataBase } from "@/lib/constants"; +import { createMetadata } from "@/lib/metadata"; -export const metadata: Metadata = { - description: - "Headless components for rendering account information like ENS name, ENS avatar, account balance and more", - metadataBase, - title: "Account Components", -}; +const title = "Account Components"; +const description = + "Headless components for rendering account information like ENS name, ENS avatar, account balance and more"; + +export const metadata = createMetadata({ + title, + description, + image: { + icon: "wallets", + title, + }, +}); export default function Page() { return ( - Headless components for rendering account information like ENS name, - ENS avatar, account balance and more - - } + icon={CircleUserIcon} + title={title} + description={description} docsLink="https://portal.thirdweb.com/react/v5/components/account?utm_source=playground" - title="Account Components" >
diff --git a/apps/playground-web/src/app/wallets/headless/chain-components/page.tsx b/apps/playground-web/src/app/wallets/headless/chain-components/page.tsx index 8df27db802e..0a82921a4fe 100644 --- a/apps/playground-web/src/app/wallets/headless/chain-components/page.tsx +++ b/apps/playground-web/src/app/wallets/headless/chain-components/page.tsx @@ -1,28 +1,33 @@ -import type { Metadata } from "next"; +import { LinkIcon } from "lucide-react"; import { PageLayout } from "@/components/blocks/APIHeader"; import { ChainIconExample, ChainNameExample, } from "@/components/headless-ui/chain-examples"; import ThirdwebProvider from "@/components/thirdweb-provider"; -import { metadataBase } from "@/lib/constants"; +import { createMetadata } from "@/lib/metadata"; -export const metadata: Metadata = { - description: "Headless UI components for rendering chain name and icon", - metadataBase, - title: "Chain Components", -}; +const title = "Chain Components"; +const description = "Headless UI components for rendering chain name and icon"; + +export const metadata = createMetadata({ + title, + description, + image: { + icon: "wallets", + title, + }, +}); export default function Page() { return ( Headless UI components for rendering chain name and icon - } + title={title} + description={description} docsLink="https://portal.thirdweb.com/react/v5/components/onchain#chains?utm_source=playground" - title="Chain Components" > diff --git a/apps/playground-web/src/app/wallets/headless/wallet-components/page.tsx b/apps/playground-web/src/app/wallets/headless/wallet-components/page.tsx index 64b14767169..eec5ad2fcc4 100644 --- a/apps/playground-web/src/app/wallets/headless/wallet-components/page.tsx +++ b/apps/playground-web/src/app/wallets/headless/wallet-components/page.tsx @@ -1,28 +1,33 @@ -import type { Metadata } from "next"; +import { WalletCardsIcon } from "lucide-react"; import { PageLayout } from "@/components/blocks/APIHeader"; import { WalletIconExample, WalletNameExample, } from "@/components/headless-ui/wallet-examples"; import ThirdwebProvider from "@/components/thirdweb-provider"; -import { metadataBase } from "@/lib/constants"; +import { createMetadata } from "@/lib/metadata"; -export const metadata: Metadata = { - description: "Headless UI components for rendering wallet name and icon", - metadataBase, - title: "Wallet Components", -}; +const title = "Wallet Components"; +const description = "Headless UI components for rendering wallet name and icon"; + +export const metadata = createMetadata({ + title, + description, + image: { + icon: "wallets", + title, + }, +}); export default function Page() { return ( Headless UI components for rendering wallet name and icon - } + description={description} docsLink="https://portal.thirdweb.com/react/v5/connecting-wallets/ui-components?utm_source=playground" - title="Wallet Components" + title={title} > diff --git a/apps/playground-web/src/app/wallets/in-app-wallet/page.tsx b/apps/playground-web/src/app/wallets/in-app-wallet/page.tsx index 3f5e5efe8ea..6bb4590ea1d 100644 --- a/apps/playground-web/src/app/wallets/in-app-wallet/page.tsx +++ b/apps/playground-web/src/app/wallets/in-app-wallet/page.tsx @@ -1,33 +1,33 @@ -import type { Metadata } from "next"; +import { UserIcon } from "lucide-react"; import { CodeExample } from "@/components/code/code-example"; import { CustomLoginForm } from "@/components/in-app-wallet/custom-login-form"; +import { createMetadata } from "@/lib/metadata"; import { PageLayout } from "../../../components/blocks/APIHeader"; import { InAppConnectEmbed } from "../../../components/in-app-wallet/connect-button"; import { Profiles } from "../../../components/in-app-wallet/profile-sections"; import ThirdwebProvider from "../../../components/thirdweb-provider"; -import { metadataBase } from "../../../lib/constants"; -export const metadata: Metadata = { - description: - "Let users sign up with their email, phone number, social media accounts or directly with a wallet", - metadataBase, - title: "Any Auth | thirdweb in-app wallet", -}; +const title = "In-App Wallets"; +const description = + "Add social login, passkey, phone, or email sign-in to your app. Use built-in auth or connect your own backend with custom endpoints."; + +export const metadata = createMetadata({ + description, + title, + image: { + icon: "wallets", + title, + }, +}); export default function Page() { return ( - Use any of the built-in auth methods or bring your own. -
- Supports custom auth endpoints to integrate with your existing user - base. - - } + description={description} docsLink="https://portal.thirdweb.com/wallets/users?utm_source=playground" - title="Onboard users to web3 with any auth method" + title={title} + icon={UserIcon} >
diff --git a/apps/playground-web/src/app/wallets/in-app-wallet/sponsor/page.tsx b/apps/playground-web/src/app/wallets/in-app-wallet/sponsor/page.tsx deleted file mode 100644 index 373a00c053b..00000000000 --- a/apps/playground-web/src/app/wallets/in-app-wallet/sponsor/page.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import type { Metadata } from "next"; -import { CodeExample } from "@/components/code/code-example"; -import { PageLayout } from "../../../../components/blocks/APIHeader"; -import { SponsoredInAppTxPreview } from "../../../../components/in-app-wallet/sponsored-tx"; -import ThirdwebProvider from "../../../../components/thirdweb-provider"; -import { metadataBase } from "../../../../lib/constants"; - -export const metadata: Metadata = { - description: - "With in-app wallets, users don't need to confirm every transaction. Combine it with smart account flag to cover gas costs for the best UX", - metadataBase, - title: "Signless Sponsored Transactions | thirdweb in-app wallet", -}; - -export default function Page() { - return ( - - - With in-app wallets, users {"don't"} need to confirm every - transaction. -
- Combine it with smart account flag to cover gas costs for the best - UX. - - } - docsLink="https://portal.thirdweb.com/wallets/sponsor-gas?utm_source=playground" - title="Signless Sponsored Transactions" - > - -
-
- ); -} - -function SponsoredInAppTx() { - return ( - - - - {/* signless, sponsored transactions */} - - claimTo({ - contract, - to: "0x123...", - tokenId: 0n, - quantity: 1n, - }) - } - > - Mint - - - ); -}`} - lang="tsx" - preview={} - /> - ); -} diff --git a/apps/playground-web/src/app/wallets/sign-in/button/RightSection.tsx b/apps/playground-web/src/app/wallets/sign-in/button/RightSection.tsx index a8267404768..f42ceb37789 100644 --- a/apps/playground-web/src/app/wallets/sign-in/button/RightSection.tsx +++ b/apps/playground-web/src/app/wallets/sign-in/button/RightSection.tsx @@ -151,7 +151,7 @@ export function RightSection(props: { ); return ( -
+
-
+
{props.tabs.map((tab) => ( diff --git a/apps/playground-web/src/app/wallets/sign-in/button/connect-button-page.tsx b/apps/playground-web/src/app/wallets/sign-in/button/connect-button-page.tsx new file mode 100644 index 00000000000..975cef1edd0 --- /dev/null +++ b/apps/playground-web/src/app/wallets/sign-in/button/connect-button-page.tsx @@ -0,0 +1,102 @@ +"use client"; + +import { RectangleHorizontalIcon } from "lucide-react"; +import { useTheme } from "next-themes"; +import { useEffect, useState } from "react"; +import ThirdwebProvider from "@/components/thirdweb-provider"; +import { PageLayout } from "../../../../components/blocks/APIHeader"; +import type { ConnectPlaygroundOptions } from "../components/types"; +import { LeftSection } from "./LeftSection"; +import { RightSection } from "./RightSection"; + +const defaultInAppLoginMethods: ConnectPlaygroundOptions["inAppWallet"]["methods"] = + [ + "google", + "discord", + "telegram", + "farcaster", + "email", + "x", + "passkey", + "phone", + ]; + +// NOTE: Only set the values that are actually the default values used by Connect component +const defaultConnectOptions: ConnectPlaygroundOptions = { + buttonLabel: undefined, + enableAccountAbstraction: false, + enableAuth: false, + inAppWallet: { + enabled: true, + methods: defaultInAppLoginMethods, + }, + localeId: "en_US", + modalSize: "compact", + modalTitle: undefined, + modalTitleIcon: undefined, + privacyPolicyLink: undefined, + requireApproval: false, + ShowThirdwebBranding: true, + termsOfServiceLink: undefined, + theme: { + darkColorOverrides: {}, + lightColorOverrides: {}, + type: "dark", + }, + walletIds: [ + "io.metamask", + "com.coinbase.wallet", + "me.rainbow", + "io.rabby", + "io.zerion.wallet", + ], +}; + +export function ConnectButtonPage(props: { + title: string; + description: string; + tab: string; +}) { + const { theme } = useTheme(); + const [connectOptions, setConnectOptions] = + useState(() => ({ + ...defaultConnectOptions, + theme: { + ...defaultConnectOptions.theme, + type: theme === "dark" ? "dark" : "light", + }, + })); + + // change theme on global theme change + useEffect(() => { + setConnectOptions((prev) => ({ + ...prev, + theme: { + ...prev.theme, + type: theme === "dark" ? "dark" : "light", + }, + })); + }, [theme]); + + return ( + + +
+
+ +
+ + +
+
+
+ ); +} diff --git a/apps/playground-web/src/app/wallets/sign-in/button/page.tsx b/apps/playground-web/src/app/wallets/sign-in/button/page.tsx index 1178447129b..a2a42e8773d 100644 --- a/apps/playground-web/src/app/wallets/sign-in/button/page.tsx +++ b/apps/playground-web/src/app/wallets/sign-in/button/page.tsx @@ -1,90 +1,31 @@ -"use client"; +import { createMetadata } from "@/lib/metadata"; +import { ConnectButtonPage } from "./connect-button-page"; -import { use, useState } from "react"; -import ThirdwebProvider from "@/components/thirdweb-provider"; -import { PageLayout } from "../../../../components/blocks/APIHeader"; -import type { ConnectPlaygroundOptions } from "../components/types"; -import { LeftSection } from "./LeftSection"; -import { RightSection } from "./RightSection"; +const title = "Connect Button"; +const description = + "Wallet connection component to enable sign-in to any 500+ EOA (external wallets) or in-app wallets via email, phone number, passkeys, or social logins"; +const ogDescription = + "Plug-and-play wallet connect UI with support for 500+ wallets, passkey and social login, ERC-4337 upgrades, gasless flows, and Sign In with Ethereum."; -const defaultInAppLoginMethods: ConnectPlaygroundOptions["inAppWallet"]["methods"] = - [ - "google", - "discord", - "telegram", - "farcaster", - "email", - "x", - "passkey", - "phone", - ]; - -// NOTE: Only set the values that are actually the default values used by Connect component -const defaultConnectOptions: ConnectPlaygroundOptions = { - buttonLabel: undefined, - enableAccountAbstraction: false, - enableAuth: false, - inAppWallet: { - enabled: true, - methods: defaultInAppLoginMethods, - }, - localeId: "en_US", - modalSize: "compact", - modalTitle: undefined, - modalTitleIcon: undefined, - privacyPolicyLink: undefined, - requireApproval: false, - ShowThirdwebBranding: true, - termsOfServiceLink: undefined, - theme: { - darkColorOverrides: {}, - lightColorOverrides: {}, - type: "dark", +export const metadata = createMetadata({ + description: ogDescription, + title, + image: { + icon: "wallets", + title, }, - walletIds: [ - "io.metamask", - "com.coinbase.wallet", - "me.rainbow", - "io.rabby", - "io.zerion.wallet", - ], -}; +}); -export default function Page(props: { - searchParams: Promise<{ tab: string }>; +export default async function Page(props: { + searchParams: Promise<{ tab: string | undefined | string[] }>; }) { - const searchParams = use(props.searchParams); - const [connectOptions, setConnectOptions] = - useState(defaultConnectOptions); + const searchParams = await props.searchParams; return ( - - - A fully featured wallet connection component that allows to Connect - to 500+ external wallets, connect via email, phone number, passkey - or social logins, Convert any wallet to a ERC4337 smart wallet for - gasless transactions and provides SIWE (Sign In With Ethereum) - - } - docsLink="https://portal.thirdweb.com/wallets/auth?utm_source=playground" - title="ConnectButton" - > -
-
- -
- - -
-
-
+ ); } diff --git a/apps/playground-web/src/app/wallets/sign-in/components/CollapsibleSection.tsx b/apps/playground-web/src/app/wallets/sign-in/components/CollapsibleSection.tsx index de31a2b246f..ad241681cd1 100644 --- a/apps/playground-web/src/app/wallets/sign-in/components/CollapsibleSection.tsx +++ b/apps/playground-web/src/app/wallets/sign-in/components/CollapsibleSection.tsx @@ -1,5 +1,5 @@ import { CustomAccordion } from "@/components/ui/CustomAccordion"; -import { cn } from "../../../../lib/utils"; +import { cn } from "@/lib/utils"; export function CollapsibleSection(props: { children: React.ReactNode; @@ -10,13 +10,13 @@ export function CollapsibleSection(props: { }) { return ( + {props.title} diff --git a/apps/playground-web/src/app/wallets/sign-in/embed/page.tsx b/apps/playground-web/src/app/wallets/sign-in/embed/page.tsx index ed290405ef0..a31f4aa307f 100644 --- a/apps/playground-web/src/app/wallets/sign-in/embed/page.tsx +++ b/apps/playground-web/src/app/wallets/sign-in/embed/page.tsx @@ -1,30 +1,33 @@ -import type { Metadata } from "next"; +import { PanelTopIcon } from "lucide-react"; +import { PageLayout } from "@/components/blocks/APIHeader"; import { CodeExample } from "@/components/code/code-example"; import { StyledConnectEmbed } from "@/components/styled-connect-embed"; import ThirdwebProvider from "@/components/thirdweb-provider"; -import { metadataBase } from "@/lib/constants"; -import { PageLayout } from "../../../../components/blocks/APIHeader"; +import { createMetadata } from "@/lib/metadata"; -export const metadata: Metadata = { - description: - "Let users sign up with their email, phone number, social media accounts or directly with a wallet. Seamlessly integrate account abstraction and SIWE auth.", - metadataBase, - title: "Sign In, Account Abstraction and SIWE Auth | thirdweb ConnectEmbed", -}; +const title = "Connect Embed"; +const description = + "Embeddable wallet component to manage logged in wallet states, view wallet balance, view assets, or buy and receive funds within an application."; +const ogDescription = + "Wallet component to manage logged in wallet, view wallet balance, view assets, or buy and receive funds within an application"; + +export const metadata = createMetadata({ + description: ogDescription, + title, + image: { + icon: "wallets", + title, + }, +}); export default function Page() { return ( - Create a login experience tailor-made for your app. Add your wallets - of choice, enable web2 sign-in options and create a modal that fits - your brand. - - } + icon={PanelTopIcon} + title={title} + description={description} docsLink="https://portal.thirdweb.com/wallets?utm_source=playground" - title="ConnectEmbed" > diff --git a/apps/playground-web/src/app/wallets/sign-in/headless/page.tsx b/apps/playground-web/src/app/wallets/sign-in/headless/page.tsx index c3302eb2334..ca8f712790a 100644 --- a/apps/playground-web/src/app/wallets/sign-in/headless/page.tsx +++ b/apps/playground-web/src/app/wallets/sign-in/headless/page.tsx @@ -1,31 +1,34 @@ -import type { Metadata } from "next"; +import { SquircleDashedIcon } from "lucide-react"; import { PageLayout } from "@/components/blocks/APIHeader"; import { CodeExample } from "@/components/code/code-example"; import { HooksPreview } from "@/components/sign-in/hooks"; import ThirdwebProvider from "@/components/thirdweb-provider"; -import { metadataBase } from "@/lib/constants"; +import { createMetadata } from "@/lib/metadata"; import { ModalPreview } from "../../../../components/sign-in/modal"; -export const metadata: Metadata = { - description: - "Let users sign up with their email, phone number, social media accounts or directly with a wallet. Seamlessly integrate account abstraction and SIWE auth.", - metadataBase, - title: "Sign In, Account Abstraction and SIWE Auth | thirdweb Connect", -}; +const title = "Headless Connect"; +const description = + "Create sign-in flow with your choice of wallets and sign-in options built custom to your branding. Use React hooks for full UI control and built-in wallet state management."; +const ogDescription = + "Build a custom login flow with your choice of wallets and sign-in options. Use React hooks for full UI control and built-in wallet state management."; + +export const metadata = createMetadata({ + description: ogDescription, + title, + image: { + icon: "wallets", + title, + }, +}); export default function Page() { return ( - Create a login experience tailor-made for your app. Add your wallets - of choice, enable web2 sign-in options and create a modal that fits - your brand. - - } + icon={SquircleDashedIcon} + title={title} + description={description} docsLink="https://portal.thirdweb.com/wallets?utm_source=playground" - title="Headless" >
diff --git a/apps/playground-web/src/app/wallets/social/page.tsx b/apps/playground-web/src/app/wallets/social/page.tsx index ce032bcddf4..24ff246215c 100644 --- a/apps/playground-web/src/app/wallets/social/page.tsx +++ b/apps/playground-web/src/app/wallets/social/page.tsx @@ -1,29 +1,33 @@ -import type { Metadata } from "next"; +import { GlobeIcon } from "lucide-react"; +import { PageLayout } from "@/components/blocks/APIHeader"; import { CodeExample } from "@/components/code/code-example"; import { SocialProfiles } from "@/components/social/social-profiles"; import ThirdwebProvider from "@/components/thirdweb-provider"; -import { metadataBase } from "@/lib/constants"; -import { PageLayout } from "../../../components/blocks/APIHeader"; +import { createMetadata } from "@/lib/metadata"; -export const metadata: Metadata = { - description: - "Retrieve any user's onchain identity from popular protocols like ENS, Lens, Farcaster, and more.", - metadataBase, - title: "Social APIs | thirdweb Connect", -}; +const title = "Social Profiles"; +const description = + "Enhance wallet authentication and context about user profiles with social identity data from ENS, Lens, and Farcaster"; +const ogDescription = + "Enhance wallet authentication with social identity data from ENS, Lens, and Farcaster. Gain context-rich profiles on user login."; + +export const metadata = createMetadata({ + description: ogDescription, + title, + image: { + icon: "wallets", + title, + }, +}); export default function Page() { return ( - Gain context about your users and their profiles across other apps - as soon as they sign into your app. - - } + icon={GlobeIcon} + title={title} + description={description} docsLink="https://portal.thirdweb.com/wallets?utm_source=playground" - title="Get any user's onchain identity" // TODO: update this once we have Social API docs > diff --git a/apps/playground-web/src/components/ThemeToggle.tsx b/apps/playground-web/src/components/ThemeToggle.tsx new file mode 100644 index 00000000000..24e9a4b3263 --- /dev/null +++ b/apps/playground-web/src/components/ThemeToggle.tsx @@ -0,0 +1,37 @@ +"use client"; + +import { MoonIcon, SunIcon } from "lucide-react"; +import { useTheme } from "next-themes"; +import { ClientOnly } from "@/components/ClientOnly"; +import { Button } from "@/components/ui/button"; +import { Skeleton } from "@/components/ui/skeleton"; + +export function ThemeToggle() { + const { setTheme, theme } = useTheme(); + + return ( +
+ } + > + + +
+ ); +} diff --git a/apps/playground-web/src/components/account-abstraction/connect-smart-account.tsx b/apps/playground-web/src/components/account-abstraction/connect-smart-account.tsx index 57407f3ce7f..a97712e57ab 100644 --- a/apps/playground-web/src/components/account-abstraction/connect-smart-account.tsx +++ b/apps/playground-web/src/components/account-abstraction/connect-smart-account.tsx @@ -11,26 +11,8 @@ import { import { shortenAddress } from "thirdweb/utils"; import { inAppWallet, socialIcons } from "thirdweb/wallets/in-app"; import { THIRDWEB_CLIENT } from "../../lib/client"; -import { StyledConnectButton } from "../styled-connect-button"; import { Button } from "../ui/button"; -export function ConnectSmartAccountPreview() { - return ( -
- -
- ); -} - export function ConnectSmartAccountCustomPreview() { const account = useActiveAccount(); const wallet = useActiveWallet(); diff --git a/apps/playground-web/src/components/blocks/APIHeader.tsx b/apps/playground-web/src/components/blocks/APIHeader.tsx index 7b295ca2c50..d68b7034adc 100644 --- a/apps/playground-web/src/components/blocks/APIHeader.tsx +++ b/apps/playground-web/src/components/blocks/APIHeader.tsx @@ -1,4 +1,4 @@ -import { BookOpenIcon, PresentationIcon } from "lucide-react"; +import { ArrowUpRightIcon } from "lucide-react"; import Link from "next/link"; import { cn } from "../../lib/utils"; import { Button } from "../ui/button"; @@ -7,38 +7,40 @@ export function PageHeader(props: { title: string; description: React.ReactNode; docsLink: string; + icon: React.FC<{ className?: string }>; + containerClassName?: string; }) { return ( -
-
+
+
{/* Left */}
-

+
+
+ +
+
+ +

{props.title}

-

+

{props.description}

{/* right */} -
- - -
+
); @@ -50,15 +52,17 @@ export function PageLayout(props: { docsLink: string; children: React.ReactNode; containerClassName?: string; + icon: React.FC<{ className?: string }>; }) { return (
-
+
{props.children}
diff --git a/apps/playground-web/src/components/blocks/NetworkSelectors.tsx b/apps/playground-web/src/components/blocks/NetworkSelectors.tsx index ce5c77ec17f..7bdeabb87cb 100644 --- a/apps/playground-web/src/components/blocks/NetworkSelectors.tsx +++ b/apps/playground-web/src/components/blocks/NetworkSelectors.tsx @@ -1,11 +1,11 @@ "use client"; import { useCallback, useMemo } from "react"; +import { ChainIcon } from "@/components/blocks/ChainIcon"; +import { MultiSelect } from "@/components/blocks/multi-select"; import { Badge } from "@/components/ui/badge"; -import { useAllChainsData } from "../../app/hooks/chains"; -import { SelectWithSearch } from "../ui/select-with-search"; -import { ChainIcon } from "./ChainIcon"; -import { MultiSelect } from "./multi-select"; +import { SelectWithSearch } from "@/components/ui/select-with-search"; +import { useAllChainsData } from "@/hooks/chains"; function cleanChainName(chainName: string) { return chainName.replace("Mainnet", ""); diff --git a/apps/playground-web/src/components/blocks/full-width-sidebar-layout.tsx b/apps/playground-web/src/components/blocks/full-width-sidebar-layout.tsx new file mode 100644 index 00000000000..67a74ee7b40 --- /dev/null +++ b/apps/playground-web/src/components/blocks/full-width-sidebar-layout.tsx @@ -0,0 +1,434 @@ +"use client"; + +import { ChevronDownIcon, ChevronRightIcon } from "lucide-react"; +import Link from "next/link"; +import { Suspense, useMemo, useState } from "react"; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "@/components/ui/collapsible"; +import { DynamicHeight } from "@/components/ui/DynamicHeight"; +import { NavLink } from "@/components/ui/NavLink"; +import { Separator } from "@/components/ui/separator"; +import { + Sidebar, + SidebarContent, + SidebarFooter, + SidebarGroup, + SidebarGroupContent, + SidebarGroupLabel, + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, + SidebarMenuSub, + SidebarMenuSubItem, + SidebarRail, + SidebarSeparator, + SidebarTrigger, + useSidebar, +} from "@/components/ui/sidebar"; +import { cn } from "@/lib/utils"; +import { useFullPath } from "../../hooks/full-path"; +import { ThirdwebIcon } from "../../icons/ThirdwebMiniLogo"; +import { ThemeToggle } from "../ThemeToggle"; + +type ShadcnSidebarBaseLink = { + href: string; + label: React.ReactNode; + exactMatch?: boolean; + icon?: React.FC<{ className?: string }>; +}; + +export type ShadcnSidebarLink = + | ShadcnSidebarBaseLink + | { + group: string; + links: ShadcnSidebarLink[]; + } + | { + separator: true; + className?: string; + } + | { + subMenu: { + icon?: React.FC<{ className?: string }>; + label: string; + }; + links: ShadcnSidebarLink[]; + }; + +export function FullWidthSidebarLayout(props: { + contentSidebarLinks: ShadcnSidebarLink[]; + footerSidebarLinks?: ShadcnSidebarLink[]; + children: React.ReactNode; + className?: string; +}) { + const { contentSidebarLinks, children, footerSidebarLinks } = props; + const sidebar = useSidebar(); + + return ( +
+ {/* left - sidebar */} + +
+ + + {sidebar.open && ( + + Playground + + )} + +
+ + + + + + + + {footerSidebarLinks && ( + + + + + )} + + +
+ + {/* right - content */} +
+
+
+ + + Playground + +
+
+ + + +
+ {children} +
+
+
+ ); +} + +function MobileSidebarTrigger(props: { links: ShadcnSidebarLink[] }) { + return ( + + + + ); +} + +function MobileSidebarTriggerInner(props: { links: ShadcnSidebarLink[] }) { + const activeLink = useActiveShadcnSidebarLink(props.links); + + return ( +
+ + + {activeLink && "subMenu" in activeLink && ( + <> + {activeLink.subMenu.label} + + + )} + {activeLink && "label" in activeLink && ( + {activeLink.label} + )} +
+ ); +} + +function useActiveShadcnSidebarLink(links: ShadcnSidebarLink[]) { + const pathname = useFullPath(); + + const activeLink = useMemo(() => { + const isActive = (link: ShadcnSidebarLink): boolean => { + if ("href" in link) { + // Handle exact match + if (link.exactMatch) { + return link.href === pathname; + } + + // Handle prefix match (ensure we don't match partial paths) + return pathname.startsWith(link.href); + } + + if ("links" in link) { + return link.links.some(isActive); + } + + return false; + }; + + return links.find(isActive); + }, [links, pathname]); + + return activeLink; +} + +function useIsSubnavActive(links: ShadcnSidebarLink[]) { + const pathname = useFullPath(); + + const isSubnavActive = useMemo(() => { + const isActive = (link: ShadcnSidebarLink): boolean => { + if ("href" in link) { + // Handle exact match + if (link.exactMatch) { + return link.href === pathname; + } + + return pathname.startsWith(link.href); + } + + if ("links" in link) { + return link.links.some(isActive); + } + + return false; + }; + + return links.some(isActive); + }, [links, pathname]); + + return isSubnavActive; +} + +function RenderSidebarGroup(props: { + sidebarLinks: ShadcnSidebarLink[]; + groupName: string; +}) { + return ( + + + + ); +} + +function RenderSidebarGroupInner(props: { + sidebarLinks: ShadcnSidebarLink[]; + groupName: string; +}) { + return ( + + + {props.groupName} + + + + + + ); +} + +function RenderSidebarSubmenu(props: { + links: ShadcnSidebarLink[]; + subMenu: Omit; +}) { + return ( + + + + ); +} + +function RenderSidebarSubmenuInner(props: { + links: ShadcnSidebarLink[]; + subMenu: Omit; +}) { + const sidebar = useSidebar(); + const isSubnavActive = useIsSubnavActive(props.links); + const [_open, setOpen] = useState(undefined); + const open = _open === undefined ? isSubnavActive : _open; + + // Add null check for links + if (!props.links || props.links.length === 0) { + return null; + } + + return ( + + + + + + + {props.subMenu.icon && ( + + )} + {props.subMenu.label} + + {sidebar.open && ( + + )} + + + + + + {props.links.map((link, index) => { + if ("href" in link) { + return ( + + { + sidebar.setOpenMobile(false); + }} + > + {link.icon && } + {link.label} + + + ); + } + + if ("subMenu" in link) { + return ( + + ); + } + + if ("group" in link) { + return ( + + ); + } + + return null; + })} + + + + + + + ); +} + +function RenderSidebarMenu(props: { links: ShadcnSidebarLink[] }) { + const sidebar = useSidebar(); + + // Add null check for links + if (!props.links || props.links.length === 0) { + return null; + } + + return ( + + {props.links.map((link, idx) => { + // link + if ("href" in link) { + return ( + + + { + sidebar.setOpenMobile(false); + }} + > + {link.icon && } + {link.label} + + + + ); + } + + // separator + if ("separator" in link) { + return ( + + ); + } + + // subnav + if ("subMenu" in link) { + return ( + + ); + } + + // group + if ("group" in link) { + return ( + + ); + } + + return null; + })} + + ); +} diff --git a/apps/playground-web/src/components/blocks/multi-select.tsx b/apps/playground-web/src/components/blocks/multi-select.tsx index c92845f3fe0..e8838ff199b 100644 --- a/apps/playground-web/src/components/blocks/multi-select.tsx +++ b/apps/playground-web/src/components/blocks/multi-select.tsx @@ -145,7 +145,7 @@ export const MultiSelect = forwardRef( // scroll to top when options change const popoverElRef = useRef(null); - // biome-ignore lint/correctness/useExhaustiveDependencies: + // biome-ignore lint/correctness/useExhaustiveDependencies: ok useEffect(() => { const scrollContainer = popoverElRef.current?.querySelector("[data-scrollable]"); diff --git a/apps/playground-web/src/components/code/RenderCode.tsx b/apps/playground-web/src/components/code/RenderCode.tsx index 0357c59dee7..7ee8020b4d8 100644 --- a/apps/playground-web/src/components/code/RenderCode.tsx +++ b/apps/playground-web/src/components/code/RenderCode.tsx @@ -22,7 +22,7 @@ export function RenderCode(props: { props.scrollableContainerClassName, )} scrollableClassName={cn("p-4", props.scrollableClassName)} - shadowColor="hsl(var(--muted))" + shadowColor="hsl(var(--card))" >
= ({ return (
{header && ( -
-

+
+

{header.title}

-

+

{header.description}

@@ -44,7 +44,7 @@ export const CodeExample: React.FC = ({
{preview} diff --git a/apps/playground-web/src/components/headless-ui/wallet-previews.tsx b/apps/playground-web/src/components/headless-ui/wallet-previews.tsx index ece57057173..bedcb692906 100644 --- a/apps/playground-web/src/components/headless-ui/wallet-previews.tsx +++ b/apps/playground-web/src/components/headless-ui/wallet-previews.tsx @@ -4,7 +4,6 @@ import { WalletIcon, WalletName, WalletProvider } from "thirdweb/react"; export function WalletIconBasicPreview() { return ( - // biome-ignore lint/nursery/useUniqueElementIds:ID is not the html attribute in this case Loading...} /> @@ -25,7 +23,6 @@ export function WalletNameBasicPreview() { export function WalletNameFormatPreview() { return ( - // biome-ignore lint/nursery/useUniqueElementIds:ID is not the html attribute in this case `${str} Wallet`} diff --git a/apps/playground-web/src/components/pay/direct-payment.tsx b/apps/playground-web/src/components/pay/direct-payment.tsx index 5fcd2aeaf12..5abbdf6f5be 100644 --- a/apps/playground-web/src/components/pay/direct-payment.tsx +++ b/apps/playground-web/src/components/pay/direct-payment.tsx @@ -5,19 +5,17 @@ import { THIRDWEB_CLIENT } from "../../lib/client"; export function BuyMerchPreview() { return ( - <> - - + ); } diff --git a/apps/playground-web/src/components/ui/NavLink.tsx b/apps/playground-web/src/components/ui/NavLink.tsx new file mode 100644 index 00000000000..24959e44ea8 --- /dev/null +++ b/apps/playground-web/src/components/ui/NavLink.tsx @@ -0,0 +1,42 @@ +"use client"; + +import Link from "next/link"; +import { Suspense } from "react"; +import { cn } from "@/lib/utils"; +import { useFullPath } from "../../hooks/full-path"; + +export type NavButtonProps = { + className?: string; + activeClassName?: string; + href: string; + exactMatch?: boolean; + onClick?: () => void; +}; + +export function NavLink(props: React.PropsWithChildren) { + return ( + + + + ); +} + +function NavLinkInner(props: React.PropsWithChildren) { + const pathname = useFullPath(); + + const isActive = props.exactMatch + ? pathname === props.href + : pathname.startsWith(props.href); + + return ( + + {props.children} + + ); +} diff --git a/apps/playground-web/src/components/ui/TokenSelector.tsx b/apps/playground-web/src/components/ui/TokenSelector.tsx index f1d55616de9..2462f0a4c11 100644 --- a/apps/playground-web/src/components/ui/TokenSelector.tsx +++ b/apps/playground-web/src/components/ui/TokenSelector.tsx @@ -17,10 +17,10 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { useAllChainsData } from "@/hooks/chains"; import { useTokensData } from "@/hooks/useTokensData"; import type { TokenMetadata } from "@/lib/types"; import { cn, fallbackChainIcon, replaceIpfsUrl } from "@/lib/utils"; -import { useAllChainsData } from "../../app/hooks/chains"; const checksummedNativeTokenAddress = getAddress(NATIVE_TOKEN_ADDRESS); diff --git a/apps/playground-web/src/components/ui/collapsible.tsx b/apps/playground-web/src/components/ui/collapsible.tsx new file mode 100644 index 00000000000..cb003d17563 --- /dev/null +++ b/apps/playground-web/src/components/ui/collapsible.tsx @@ -0,0 +1,11 @@ +"use client"; + +import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"; + +const Collapsible = CollapsiblePrimitive.Root; + +const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger; + +const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent; + +export { Collapsible, CollapsibleTrigger, CollapsibleContent }; diff --git a/apps/playground-web/src/components/ui/select.tsx b/apps/playground-web/src/components/ui/select.tsx index d2587e781c3..402d92432f2 100644 --- a/apps/playground-web/src/components/ui/select.tsx +++ b/apps/playground-web/src/components/ui/select.tsx @@ -20,7 +20,7 @@ const SelectTrigger = React.forwardRef< >(({ className, children, chevronClassName, ...props }, ref) => ( span]:line-clamp-1 [&>span]:w-full", + "flex h-10 w-full items-center justify-between gap-2 rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1", className, )} ref={ref} @@ -119,7 +119,7 @@ const SelectItem = React.forwardRef< >(({ className, children, ...props }, ref) => ( span]:w-full", + "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", className, )} ref={ref} @@ -127,7 +127,7 @@ const SelectItem = React.forwardRef< > - + diff --git a/apps/playground-web/src/components/ui/sheet.tsx b/apps/playground-web/src/components/ui/sheet.tsx new file mode 100644 index 00000000000..dd76042ff3f --- /dev/null +++ b/apps/playground-web/src/components/ui/sheet.tsx @@ -0,0 +1,143 @@ +"use client"; + +import * as SheetPrimitive from "@radix-ui/react-dialog"; +import { cva, type VariantProps } from "class-variance-authority"; +import { XIcon } from "lucide-react"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const Sheet = SheetPrimitive.Root; + +const SheetTrigger = SheetPrimitive.Trigger; + +const SheetClose = SheetPrimitive.Close; + +const SheetPortal = SheetPrimitive.Portal; + +const SheetOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +SheetOverlay.displayName = SheetPrimitive.Overlay.displayName; + +const sheetVariants = cva( + "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500 border-border", + { + defaultVariants: { + side: "right", + }, + variants: { + side: { + bottom: + "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom", + left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm", + right: + "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm", + top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top", + }, + }, + }, +); + +interface SheetContentProps + extends React.ComponentPropsWithoutRef, + VariantProps {} + +const SheetContent = React.forwardRef< + React.ElementRef, + SheetContentProps +>(({ side = "right", className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)); +SheetContent.displayName = SheetPrimitive.Content.displayName; + +const SheetHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +SheetHeader.displayName = "SheetHeader"; + +const SheetFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +SheetFooter.displayName = "SheetFooter"; + +const SheetTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +SheetTitle.displayName = SheetPrimitive.Title.displayName; + +const SheetDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +SheetDescription.displayName = SheetPrimitive.Description.displayName; + +export { + Sheet, + SheetPortal, + SheetOverlay, + SheetTrigger, + SheetClose, + SheetContent, + SheetHeader, + SheetFooter, + SheetTitle, + SheetDescription, +}; diff --git a/apps/playground-web/src/components/ui/sidebar.tsx b/apps/playground-web/src/components/ui/sidebar.tsx index 2d9a0e7b0a4..5e4fd468313 100644 --- a/apps/playground-web/src/components/ui/sidebar.tsx +++ b/apps/playground-web/src/components/ui/sidebar.tsx @@ -1,229 +1,774 @@ +/** biome-ignore-all lint/suspicious/noDocumentCookie: FIXME */ "use client"; -import clsx from "clsx"; -import Link from "next/link"; -import { usePathname, useSearchParams } from "next/navigation"; -import { Suspense, useRef } from "react"; +import { Slot } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; +import { PanelLeftIcon } from "lucide-react"; +import * as React from "react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Separator } from "@/components/ui/separator"; +import { + Sheet, + SheetContent, + SheetDescription, + SheetHeader, + SheetTitle, +} from "@/components/ui/sheet"; +import { Skeleton } from "@/components/ui/skeleton"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { useIsMobile } from "@/hooks/use-mobile"; import { cn } from "@/lib/utils"; -import { ClientOnly } from "../ClientOnly"; -import { CustomAccordion } from "./CustomAccordion"; -export type LinkMeta = { - name: string; - href: string; - crossedOut?: boolean; -}; +const SIDEBAR_COOKIE_NAME = "sidebar_state"; +const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7; +const SIDEBAR_WIDTH = "19rem"; +const SIDEBAR_WIDTH_MOBILE = "19rem"; +const SIDEBAR_WIDTH_ICON = "3rem"; +const SIDEBAR_KEYBOARD_SHORTCUT = "b"; -export type LinkGroup = { - name: string; - href?: string; - links: SidebarLink[]; - expanded?: boolean; - /** - * If set to false, the group will not be rendered as an accordion - * @defaultValue true - */ - isCollapsible?: boolean; +type SidebarContextProps = { + state: "expanded" | "collapsed"; + open: boolean; + setOpen: (open: boolean) => void; + openMobile: boolean; + setOpenMobile: (open: boolean) => void; + isMobile: boolean; + toggleSidebar: () => void; }; -export type SidebarLink = LinkMeta | LinkGroup | { separator: true }; +const SidebarContext = React.createContext(null); -type ReferenceSideBarProps = { - links: SidebarLink[]; - onLinkClick?: () => void; -}; +function useSidebar() { + const context = React.useContext(SidebarContext); + if (!context) { + throw new Error("useSidebar must be used within a SidebarProvider."); + } -export function Sidebar(props: ReferenceSideBarProps) { - return ( -
    - {props.links.map((link, i) => ( - // biome-ignore lint/suspicious/noArrayIndexKey: -
  • - - - -
  • - ))} -
- ); + return context; } -function SidebarItem(props: { link: SidebarLink; onLinkClick?: () => void }) { - const href = useCurrentHref(); +const SidebarProvider = React.forwardRef< + HTMLDivElement, + React.ComponentProps<"div"> & { + defaultOpen?: boolean; + open?: boolean; + onOpenChange?: (open: boolean) => void; + } +>( + ( + { + defaultOpen = true, + open: openProp, + onOpenChange: setOpenProp, + className, + style, + children, + ...props + }, + ref, + ) => { + const isMobile = useIsMobile(); + const [openMobile, setOpenMobile] = React.useState(false); + + // This is the internal state of the sidebar. + // We use openProp and setOpenProp for control from outside the component. + const [_open, _setOpen] = React.useState(defaultOpen); + const open = openProp ?? _open; + const setOpen = React.useCallback( + (value: boolean | ((value: boolean) => boolean)) => { + const openState = typeof value === "function" ? value(open) : value; + if (setOpenProp) { + setOpenProp(openState); + } else { + _setOpen(openState); + } + + // This sets the cookie to keep the sidebar state. + // eslint-disable-next-line react-compiler/react-compiler + document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`; + }, + [setOpenProp, open], + ); + + // Helper to toggle the sidebar. + const toggleSidebar = React.useCallback(() => { + return isMobile + ? setOpenMobile((open) => !open) + : setOpen((open) => !open); + }, [isMobile, setOpen]); + + // Adds a keyboard shortcut to toggle the sidebar. + React.useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + if ( + event.key === SIDEBAR_KEYBOARD_SHORTCUT && + (event.metaKey || event.ctrlKey) + ) { + event.preventDefault(); + toggleSidebar(); + } + }; + + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, [toggleSidebar]); + + // We add a state so that we can do data-state="expanded" or "collapsed". + // This makes it easier to style the sidebar with Tailwind classes. + const state = open ? "expanded" : "collapsed"; + + const contextValue = React.useMemo( + () => ({ + isMobile, + open, + openMobile, + setOpen, + setOpenMobile, + state, + toggleSidebar, + }), + [state, open, setOpen, isMobile, openMobile, toggleSidebar], + ); + + return ( + + +
+ {children} +
+
+
+ ); + }, +); +SidebarProvider.displayName = "SidebarProvider"; - if ("separator" in props.link) { - return
; +const Sidebar = React.forwardRef< + HTMLDivElement, + React.ComponentProps<"div"> & { + side?: "left" | "right"; + variant?: "sidebar" | "floating" | "inset"; + collapsible?: "offcanvas" | "icon" | "none"; } +>( + ( + { + side = "left", + variant = "sidebar", + collapsible = "offcanvas", + className, + children, + ...props + }, + ref, + ) => { + const { isMobile, state, openMobile, setOpenMobile } = useSidebar(); - const isActive = props.link.href ? isSamePage(href, props.link.href) : false; + if (collapsible === "none") { + return ( +
+ {children} +
+ ); + } - const { link } = props; - if ("links" in link) { - if (link.isCollapsible === false) { + if (isMobile) { return ( - + + + + Sidebar + Displays the mobile sidebar. + +
{children}
+
+
); } + return ( - +
+ {/* This is what handles the sidebar gap on desktop */} +
+ +
); - } + }, +); +Sidebar.displayName = "Sidebar"; + +const SidebarTrigger = React.forwardRef< + React.ElementRef, + React.ComponentProps +>(({ className, onClick, ...props }, ref) => { + const { toggleSidebar } = useSidebar(); return ( - { + onClick?.(event); + toggleSidebar(); + }} + ref={ref} + size="icon" + variant="ghost" + {...props} > - {link.name} - + + Toggle Sidebar + ); -} +}); +SidebarTrigger.displayName = "SidebarTrigger"; -function DocSidebarNonCollapsible(props: { - linkGroup: LinkGroup; - onLinkClick?: () => void; -}) { - const currentHref = useCurrentHref(); - const { href, name, links } = props.linkGroup; - const isCategoryActive = href ? isSamePage(currentHref, href) : false; +const SidebarRail = React.forwardRef< + HTMLButtonElement, + React.ComponentProps<"button"> +>(({ className, ...props }, ref) => { + const { toggleSidebar } = useSidebar(); return ( -
-
- {href ? ( - - {name} - - ) : ( -
{name}
- )} -
-
    - {links.map((link, i) => { - return ( - // biome-ignore lint/suspicious/noArrayIndexKey: order is static -
  • - -
  • - ); - })} -
-
+
@@ -250,7 +261,7 @@ function SDKCard(props: {
-

+