+ {favoriteButton &&
{favoriteButton}
}
;
}) {
+ const authToken = await getAuthToken();
const headersList = await headers();
const viewportWithHint = Number(
headersList.get("Sec-Ch-Viewport-Width") || 0,
@@ -70,7 +72,11 @@ export default async function ChainListPage(props: {
{/* we used to have suspense + spinner here, that feels more jarring than the page loading _minutely_ slower */}
-
+
);
}
diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/components/client/star-button.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/components/client/star-button.tsx
index c25bdf197dd..ed3a5debb7a 100644
--- a/apps/dashboard/src/app/(dashboard)/(chain)/components/client/star-button.tsx
+++ b/apps/dashboard/src/app/(dashboard)/(chain)/components/client/star-button.tsx
@@ -1,44 +1,54 @@
"use client";
+import { apiServerProxy } from "@/actions/proxies";
+import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Button, type ButtonProps } from "@/components/ui/button";
import { ToolTipLabel } from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
-import { THIRDWEB_API_HOST } from "constants/urls";
import { Star } from "lucide-react";
import { useActiveAccount } from "thirdweb/react";
async function favoriteChains() {
- const res = await fetch(`${THIRDWEB_API_HOST}/v1/chains/favorites`, {
+ const res = await apiServerProxy<{ data: string[] }>({
+ pathname: "/v1/chains/favorites",
method: "GET",
});
- const result = await res.json();
+ if (!res.ok) {
+ throw new Error(res.error);
+ }
- return (result.data ?? []) as string[];
+ const result = res.data;
+ return result.data ?? [];
}
async function addChainToFavorites(chainId: number) {
- const res = await fetch(
- `${THIRDWEB_API_HOST}/v1/chains/${chainId}/favorite`,
- {
- method: "POST",
- // without body - the API returns 400
- body: JSON.stringify({}),
- },
- );
- const result = await res.json();
+ const res = await apiServerProxy({
+ method: "POST",
+ body: JSON.stringify({}),
+ pathname: `/v1/chains/${chainId}/favorite`,
+ });
+
+ if (!res.ok) {
+ throw new Error(res.error);
+ }
+
+ const result = res.data as { data?: { favorite: boolean } };
return result?.data?.favorite;
}
async function removeChainFromFavorites(chainId: number) {
- const res = await fetch(
- `${THIRDWEB_API_HOST}/v1/chains/${chainId}/favorite`,
- {
- method: "DELETE",
- },
- );
- const result = await res.json();
+ const res = await apiServerProxy<{ data?: { favorite: boolean } }>({
+ pathname: `/v1/chains/${chainId}/favorite`,
+ method: "DELETE",
+ });
+
+ if (!res.ok) {
+ throw new Error(res.error);
+ }
+
+ const result = res.data;
return result?.data?.favorite;
}
@@ -57,7 +67,6 @@ export function StarButton(props: {
iconClassName?: string;
variant?: ButtonProps["variant"];
}) {
- const address = useActiveAccount()?.address;
const queryClient = useQueryClient();
const favChainsQuery = useFavoriteChainIds();
@@ -81,27 +90,31 @@ export function StarButton(props: {
const label = isPreferred ? "Remove from Favorites" : "Add to Favorites";
return (
-
+
+
+
);
}
diff --git a/apps/dashboard/src/app/(landing)/ThemeProvider.tsx b/apps/dashboard/src/app/(landing)/ThemeProvider.tsx
new file mode 100644
index 00000000000..5613ec0466f
--- /dev/null
+++ b/apps/dashboard/src/app/(landing)/ThemeProvider.tsx
@@ -0,0 +1,118 @@
+"use client";
+
+import { ThemeProvider } from "@/components/theme-provider";
+import { useForceDarkTheme } from "@/components/theme-provider";
+import { ChakraProvider, useColorMode } from "@chakra-ui/react";
+import { Global, css } from "@emotion/react";
+import { useTheme } from "next-themes";
+import {
+ IBM_Plex_Mono as ibmPlexMonoConstructor,
+ Inter as interConstructor,
+} from "next/font/google";
+import { useEffect } from "react";
+import { generateBreakpointTypographyCssVars } from "tw-components/utils/typography";
+import chakraTheme from "../../theme";
+
+const inter = interConstructor({
+ subsets: ["latin"],
+ display: "swap",
+ fallback: ["system-ui", "Helvetica Neue", "Arial", "sans-serif"],
+ adjustFontFallback: true,
+});
+
+const ibmPlexMono = ibmPlexMonoConstructor({
+ weight: ["400", "500", "600", "700"],
+ subsets: ["latin"],
+ display: "swap",
+ fallback: ["Consolas", "Courier New", "monospace"],
+});
+
+const fontSizeCssVars = generateBreakpointTypographyCssVars();
+
+const chakraThemeWithFonts = {
+ ...chakraTheme,
+ fonts: {
+ ...chakraTheme.fonts,
+ heading: inter.style.fontFamily,
+ body: inter.style.fontFamily,
+ mono: ibmPlexMono.style.fontFamily,
+ },
+};
+
+export function LandingPageThemeProvider(props: {
+ children: React.ReactNode;
+}) {
+ return (
+
+ div > svg {
+ font-size: 10px !important;
+ }
+ `}
+ />
+
+ {props.children}
+
+
+
+
+ );
+}
+
+function TailwindTheme(props: { children: React.ReactNode }) {
+ return (
+
+ {props.children}
+
+ );
+}
+
+const SyncTheme: React.FC = () => {
+ const { theme, setTheme } = useTheme();
+ const { setColorMode } = useColorMode();
+ // eslint-disable-next-line no-restricted-syntax
+ useEffect(() => {
+ setColorMode(theme === "light" ? "light" : "dark");
+ }, [setColorMode, theme]);
+
+ // handle dashboard with now old "system" set
+ // eslint-disable-next-line no-restricted-syntax
+ useEffect(() => {
+ if (theme === "system") {
+ setTheme("dark");
+ setColorMode("dark");
+ }
+ }, [theme, setTheme, setColorMode]);
+
+ return null;
+};
+
+function ForceDarkTheme() {
+ useForceDarkTheme();
+ return null;
+}
diff --git a/apps/dashboard/src/app/(landing)/account-abstraction/page.tsx b/apps/dashboard/src/app/(landing)/account-abstraction/page.tsx
new file mode 100644
index 00000000000..e40e5f8168d
--- /dev/null
+++ b/apps/dashboard/src/app/(landing)/account-abstraction/page.tsx
@@ -0,0 +1,207 @@
+import { Box, Container, Flex } from "@chakra-ui/react";
+import { ChakraNextImage } from "components/Image";
+import { LandingDynamicSelector } from "components/landing-pages/dynamic-selector";
+import { LandingEndCTA } from "components/landing-pages/end-cta";
+import { LandingGuidesShowcase } from "components/landing-pages/guide-showcase";
+import { LandingHeroWithSideImage } from "components/landing-pages/hero-with-side-image";
+import { LandingShowcaseImage } from "components/landing-pages/showcase-image";
+import type { Metadata } from "next";
+import { Heading, Text } from "tw-components";
+// images
+import smartWalletMiniImage from "../../../../public/assets/product-icons/smart-wallet.png";
+import accountAbstractionImage from "../../../../public/assets/product-pages/smart-wallet/account-abstraction.png";
+import batchTxImage from "../../../../public/assets/product-pages/smart-wallet/batch-txns.png";
+import dashboardImage from "../../../../public/assets/product-pages/smart-wallet/dashboard.png";
+import desktopHero from "../../../../public/assets/product-pages/smart-wallet/desktop-hero.png";
+import fullyProgrammaticImage from "../../../../public/assets/product-pages/smart-wallet/full-programmability.png";
+import getStartedImage from "../../../../public/assets/product-pages/smart-wallet/get-started.png";
+import invisibleWalletsImage from "../../../../public/assets/product-pages/smart-wallet/invisible-wallet.png";
+import managedInfrastructureImage from "../../../../public/assets/product-pages/smart-wallet/managed-infrastructure.png";
+import mobileHero from "../../../../public/assets/product-pages/smart-wallet/mobile-hero.png";
+import pairAnyWalletImage from "../../../../public/assets/product-pages/smart-wallet/pair-any-wallet.png";
+import smartContractsImage from "../../../../public/assets/product-pages/smart-wallet/smart-contracts.png";
+import uiComponentsImage from "../../../../public/assets/product-pages/smart-wallet/ui-components.png";
+import chooseContractImage from "../../../../public/assets/product-pages/smart-wallet/which-contract.png";
+import { getAbsoluteUrl } from "../../../lib/vercel-utils";
+
+const GUIDES = [
+ {
+ title: "The Quick-Start Guide to Account Abstraction",
+ image: getStartedImage,
+ link: "https://portal.thirdweb.com/wallets/smart-wallet/get-started",
+ },
+ {
+ title: "Choosing Between Simple, Managed, & Dynamic Smart Accounts",
+ image: chooseContractImage,
+ link: "https://blog.thirdweb.com/smart-contract-deep-dive-building-smart-wallets-for-individuals-and-teams/",
+ },
+ {
+ title: "How to Enable Batch Transactions with Account Abstraction",
+ image: batchTxImage,
+ link: "https://blog.thirdweb.com/guides/how-to-batch-transactions-with-the-thirdweb-sdk/",
+ },
+];
+
+const TRACKING_CATEGORY = "smart-wallet-landing";
+
+const title = "The Complete Account Abstraction Toolkit";
+const description =
+ "Add account abstraction to your web3 app & unlock powerful features for seamless onboarding, customizable transactions, & maximum security. Learn more.";
+
+export const metadata: Metadata = {
+ title,
+ description,
+ openGraph: {
+ title,
+ description,
+ images: [
+ {
+ url: `${getAbsoluteUrl()}/assets/og-image/smart-wallet.png`,
+ width: 1200,
+ height: 630,
+ },
+ ],
+ },
+};
+
+export default function Page() {
+ return (
+
+
+
+ ),
+ },
+ {
+ title: "Instant onboarding for every user",
+ description:
+ "Auth for the most popular web3 wallets and web2 login flows — with just an email, phone number, social account, or passkeys.",
+ Component: (
+
+ ),
+ },
+ {
+ title: "Enterprise-grade security",
+ description:
+ "Wallet recovery, 2FA, and multi-signature support for ultimate peace of mind — for users & teams.",
+ Component: (
+
+ ),
+ },
+ ]}
+ />
+
+
+ An all-in-one solution for
+
+ Account Abstraction
+
+
+
+ Implement account abstraction into any web3 app — with a best-in-class
+ SDK, full wallet customizability, and managed infrastructure.
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/dashboard/src/app/(landing)/auth/page.tsx b/apps/dashboard/src/app/(landing)/auth/page.tsx
new file mode 100644
index 00000000000..27038fdb2b6
--- /dev/null
+++ b/apps/dashboard/src/app/(landing)/auth/page.tsx
@@ -0,0 +1,210 @@
+import { Container, Flex } from "@chakra-ui/react";
+import { LandingEndCTA } from "components/landing-pages/end-cta";
+import { LandingGridSection } from "components/landing-pages/grid-section";
+import { LandingGuidesShowcase } from "components/landing-pages/guide-showcase";
+import { LandingHeroWithSideImage } from "components/landing-pages/hero-with-side-image";
+import { LandingIconSectionItem } from "components/landing-pages/icon-section-item";
+import { LandingSectionHeading } from "components/landing-pages/section-heading";
+import type { Metadata } from "next";
+import { Card, TrackedLink } from "tw-components";
+// images
+import authIcon from "../../../../public/assets/product-icons/auth.png";
+import iconBuild from "../../../../public/assets/product-pages-icons/wallets/icon-build.svg";
+import iconDataCheck from "../../../../public/assets/product-pages-icons/wallets/icon-data-check.svg";
+import iconEfficient from "../../../../public/assets/product-pages-icons/wallets/icon-efficient.svg";
+import iconPrivate from "../../../../public/assets/product-pages-icons/wallets/icon-private.svg";
+import iconSecure from "../../../../public/assets/product-pages-icons/wallets/icon-secure.svg";
+import iconSimpleClick from "../../../../public/assets/product-pages-icons/wallets/icon-simple-click.svg";
+import iconVerified from "../../../../public/assets/product-pages-icons/wallets/icon-verified.svg";
+import iconWalletManagement from "../../../../public/assets/product-pages-icons/wallets/icon-wallet-management.svg";
+// images
+import desktopHeroAuthImage from "../../../../public/assets/product-pages/hero/desktop-hero-auth.png";
+import mobileHeroAuthImage from "../../../../public/assets/product-pages/hero/mobile-hero-auth.png";
+import { getAbsoluteUrl } from "../../../lib/vercel-utils";
+
+const TRACKING_CATEGORY = "auth-landing";
+
+const GUIDES = [
+ {
+ title: "How to Build a Web3 Creator Platform with a Web2 Backend",
+ image:
+ "https://blog.thirdweb.com/content/images/size/w2000/2023/03/How-to-create-a-web3-creator----platform-with-a-web2-backend.png",
+ link: "https://blog.thirdweb.com/guides/how-to-create-a-web3-creator-platform/",
+ },
+ {
+ title: "Create An NFT Gated Website",
+ image:
+ "https://blog.thirdweb.com/content/images/size/w2000/2022/08/thumbnail-31.png",
+ link: "https://blog.thirdweb.com/guides/nft-gated-website/",
+ },
+ {
+ title: "Accept Stripe Subscription Payments For Your Web3 App",
+ image:
+ "https://blog.thirdweb.com/content/images/size/w2000/2023/03/Add-stripe-subscriptions--with-web3-auth-2.png",
+ link: "https://blog.thirdweb.com/guides/add-stripe-subscriptions-with-web3-auth/",
+ },
+];
+
+const title = "The Complete Toolkit for Web3 Authentication";
+const description =
+ "Auth for the most popular web3 wallets & web2 login flows. Verify your users' identities & prove wallet ownership to off-chain systems.";
+
+export const metadata: Metadata = {
+ title,
+ description,
+ openGraph: {
+ title,
+ description,
+ images: [
+ {
+ url: `${getAbsoluteUrl()}/assets/og-image/auth.png`,
+ width: 1200,
+ height: 630,
+ },
+ ],
+ },
+};
+
+export default function Page() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ Built on the SIWE (
+
+ Sign-in with Ethereum
+
+ ) standard. Securely verify a user's on-chain identity,
+ without relying on a centralized database to verify their
+ identity.
+ >
+ }
+ />
+
+
+
+
+
+
+ Secure your backend with a web3-compatible authentication system
+ compliant with the widely used{" "}
+
+ JSON Web Token
+ {" "}
+ standard.
+ >
+ }
+ />
+
+
+
+
+ }
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/dashboard/src/app/(landing)/in-app-wallets/page.tsx b/apps/dashboard/src/app/(landing)/in-app-wallets/page.tsx
new file mode 100644
index 00000000000..d29e6112ffe
--- /dev/null
+++ b/apps/dashboard/src/app/(landing)/in-app-wallets/page.tsx
@@ -0,0 +1,272 @@
+import { Container, Flex } from "@chakra-ui/react";
+import { ChakraNextImage } from "components/Image";
+import { LandingCardWithImage } from "components/landing-pages/card-with-image";
+import { LandingDynamicSelector } from "components/landing-pages/dynamic-selector";
+import { LandingEndCTA } from "components/landing-pages/end-cta";
+import { LandingGridSection } from "components/landing-pages/grid-section";
+import { LandingGuidesShowcase } from "components/landing-pages/guide-showcase";
+import { LandingHeroWithSideImage } from "components/landing-pages/hero-with-side-image";
+import { getAbsoluteUrl } from "lib/vercel-utils";
+import type { Metadata } from "next";
+import { Heading } from "tw-components";
+// images
+import analyticsImage from "../../../../public/assets/landingpage/desktop/analytics.png";
+import authDesktopImage from "../../../../public/assets/landingpage/desktop/auth.png";
+import crossPlatformDesktopImage from "../../../../public/assets/landingpage/desktop/cross-platform.png";
+import enterpriseSecurityImage from "../../../../public/assets/landingpage/desktop/enterprise-security.png";
+import guestImage from "../../../../public/assets/landingpage/desktop/guest.png";
+import magicImage from "../../../../public/assets/landingpage/desktop/magic.png";
+import onboardImage from "../../../../public/assets/landingpage/desktop/onboard.png";
+import powerfulImage from "../../../../public/assets/landingpage/desktop/powerful.png";
+import siweImage from "../../../../public/assets/landingpage/desktop/siwe.png";
+import walletImage from "../../../../public/assets/landingpage/desktop/wallet.png";
+import analyticsMobileImage from "../../../../public/assets/landingpage/mobile/analytics.png";
+import authMobileImage from "../../../../public/assets/landingpage/mobile/auth.png";
+import crossPlatformMobileImage from "../../../../public/assets/landingpage/mobile/cross-platform.png";
+import enterpriseSecurityMobileImage from "../../../../public/assets/landingpage/mobile/enterprise-security.png";
+import guestMobileImage from "../../../../public/assets/landingpage/mobile/guest.png";
+import magicMobileImage from "../../../../public/assets/landingpage/mobile/magic.png";
+import mobileOnboardImage from "../../../../public/assets/landingpage/mobile/onboard.png";
+import powerfulMobileImage from "../../../../public/assets/landingpage/mobile/powerful.png";
+import siweMobileImage from "../../../../public/assets/landingpage/mobile/siwe.png";
+import walletMobileImage from "../../../../public/assets/landingpage/mobile/wallet.png";
+import embeddedWalletIcon from "../../../../public/assets/product-icons/embedded-wallet.png";
+import authImage from "../../../../public/assets/product-pages/embedded-wallets/auth.png";
+import crossPlatformImage from "../../../../public/assets/product-pages/embedded-wallets/cross-platform.png";
+import embeddedWalletImage from "../../../../public/assets/product-pages/embedded-wallets/embedded-wallet.png";
+import paperImage from "../../../../public/assets/product-pages/embedded-wallets/paper.png";
+import seamlessImage from "../../../../public/assets/product-pages/embedded-wallets/seamless.png";
+import desktopHeroEmbeddedWalletsImage from "../../../../public/assets/product-pages/hero/desktop-hero-embedded-wallets.png";
+import mobileHeroEmbeddedWalletsImage from "../../../../public/assets/product-pages/hero/mobile-hero-embedded-wallets.png";
+import getStartedImage from "../../../../public/assets/product-pages/smart-wallet/get-started.png";
+
+const TRACKING_CATEGORY = "embedded-wallets-landing";
+
+const GUIDES = [
+ {
+ title: "Docs: In-App Wallets Overview",
+ image: embeddedWalletImage,
+ link: "https://portal.thirdweb.com/connect/in-app-wallet/overview",
+ },
+ {
+ title: "Live Demo: In-App Wallets",
+ image: paperImage,
+ link: "https://catattack.thirdweb.com",
+ },
+ {
+ title: "Quick-Start Template: In-App Wallet + Account Abstraction",
+ image: getStartedImage,
+ link: "https://github.com/thirdweb-example/embedded-smart-wallet",
+ },
+];
+
+const title = "In-App Wallets: Onboard Everyone to your App";
+const description =
+ "Onboard anyone with an email or Google account—with 1-click login flows, flexible auth options, & secure account recovery. Learn more.";
+
+export const metadata: Metadata = {
+ title,
+ description,
+ openGraph: {
+ title,
+ description,
+ images: [
+ {
+ url: `${getAbsoluteUrl()}/assets/og-image/embedded-wallets.png`,
+ width: 1200,
+ height: 630,
+ },
+ ],
+ },
+};
+
+export default function Page() {
+ return (
+
+
+
+
+ ),
+ },
+ {
+ title: "Integrate with your own custom auth",
+ description:
+ "Spin up in-app wallets for your users with your app or game's existing auth system.",
+ Component: (
+
+ ),
+ },
+ {
+ title: "Cross-platform support",
+ description:
+ "Enable users to log into their accounts (and access their wallets) from any device, in one click. Support for web, mobile, & Unity.",
+ Component: (
+
+ ),
+ },
+ ]}
+ />
+
+
+
+ Abstract away complexity for your users
+
+
+
+ );
+}
diff --git a/apps/dashboard/src/app/(landing)/layout.tsx b/apps/dashboard/src/app/(landing)/layout.tsx
new file mode 100644
index 00000000000..c43a80acff7
--- /dev/null
+++ b/apps/dashboard/src/app/(landing)/layout.tsx
@@ -0,0 +1,13 @@
+import type React from "react";
+import { LandingLayout } from "../../components/landing-pages/layout";
+import { LandingPageThemeProvider } from "./ThemeProvider";
+
+export default function Layout(props: {
+ children: React.ReactNode;
+}) {
+ return (
+
+ );
+}
diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/configuration/components/client/EcosystemPermissionsPage.tsx b/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/configuration/components/client/EcosystemPermissionsPage.tsx
index a9317dcd5c3..3713f5a9cc3 100644
--- a/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/configuration/components/client/EcosystemPermissionsPage.tsx
+++ b/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/configuration/components/client/EcosystemPermissionsPage.tsx
@@ -8,7 +8,7 @@ export function EcosystemPermissionsPage({
params,
authToken,
}: { params: { slug: string }; authToken: string }) {
- const { ecosystem } = useEcosystem({ slug: params.slug });
+ const { data: ecosystem } = useEcosystem({ slug: params.slug });
return (
diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/hooks/use-ecosystem.ts b/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/hooks/use-ecosystem.ts
index b86a7ce9af6..7cbd47cb15b 100644
--- a/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/hooks/use-ecosystem.ts
+++ b/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/hooks/use-ecosystem.ts
@@ -1,6 +1,5 @@
+import { apiServerProxy } from "@/actions/proxies";
import { useQuery } from "@tanstack/react-query";
-import { THIRDWEB_API_HOST } from "constants/urls";
-import { FetchError } from "utils/error";
import type { Ecosystem } from "../../../types";
export function useEcosystem({
@@ -14,23 +13,19 @@ export function useEcosystem({
refetchOnWindowFocus?: boolean;
initialData?: Ecosystem;
}) {
- const ecosystemQuery = useQuery({
+ return useQuery({
queryKey: ["ecosystems", slug],
queryFn: async () => {
- const res = await fetch(
- `${THIRDWEB_API_HOST}/v1/ecosystem-wallet/${slug}`,
- );
+ const res = await apiServerProxy({
+ pathname: `/v1/ecosystem-wallet/${slug}`,
+ method: "GET",
+ });
if (!res.ok) {
- const data = await res.json();
- console.error(data);
- throw new FetchError(
- res,
- data?.message ?? data?.error?.message ?? "Failed to fetch ecosystems",
- );
+ throw new Error(res.error);
}
- const data = (await res.json()) as { result: Ecosystem };
+ const data = res.data as { result: Ecosystem };
return data.result;
},
retry: false,
@@ -38,10 +33,4 @@ export function useEcosystem({
refetchOnWindowFocus,
initialData,
});
-
- return {
- ...ecosystemQuery,
- error: ecosystemQuery.error as FetchError | undefined,
- ecosystem: ecosystemQuery.data,
- };
}
diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/hooks/use-ecosystem-list.ts b/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/hooks/use-ecosystem-list.ts
index 9a9b996a203..c11289e8912 100644
--- a/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/hooks/use-ecosystem-list.ts
+++ b/apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/hooks/use-ecosystem-list.ts
@@ -1,30 +1,25 @@
+import { apiServerProxy } from "@/actions/proxies";
import { useQuery } from "@tanstack/react-query";
-import { THIRDWEB_API_HOST } from "constants/urls";
import { useActiveAccount } from "thirdweb/react";
import type { Ecosystem } from "../types";
export function useEcosystemList() {
const address = useActiveAccount()?.address;
- const ecosystemQuery = useQuery({
+ return useQuery({
queryKey: ["ecosystems", address],
queryFn: async () => {
- const res = await fetch(`${THIRDWEB_API_HOST}/v1/ecosystem-wallet/list`);
+ const res = await apiServerProxy({
+ pathname: "/v1/ecosystem-wallet/list",
+ method: "GET",
+ });
if (!res.ok) {
- const data = await res.json();
- console.error(data);
- throw new Error(data?.error?.message ?? "Failed to fetch ecosystems");
+ throw new Error(res.error ?? "Failed to fetch ecosystems");
}
- const data = (await res.json()) as { result: Ecosystem[] };
+ const data = res.data as { result: Ecosystem[] };
return data.result;
},
retry: false,
});
-
- return {
- ...ecosystemQuery,
- isPending: ecosystemQuery.isPending,
- ecosystems: (ecosystemQuery.data ?? []) satisfies Ecosystem[],
- };
}
diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(general)/import/EngineImportPage.tsx b/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(general)/import/EngineImportPage.tsx
index f984249b3ea..2d76770c47d 100644
--- a/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(general)/import/EngineImportPage.tsx
+++ b/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(general)/import/EngineImportPage.tsx
@@ -1,11 +1,13 @@
"use client";
+import { apiServerProxy } from "@/actions/proxies";
+import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { TrackedLinkTW } from "@/components/ui/tracked-link";
import { useDashboardRouter } from "@/lib/DashboardRouter";
import { FormControl } from "@chakra-ui/react";
-import { THIRDWEB_API_HOST } from "constants/urls";
+import { useMutation } from "@tanstack/react-query";
import {
CircleAlertIcon,
CloudDownloadIcon,
@@ -34,12 +36,13 @@ export const EngineImportPage = (props: {
},
});
- const onSubmit = async (data: ImportEngineInput) => {
- try {
+ const importMutation = useMutation({
+ mutationFn: async (data: ImportEngineInput) => {
// Instance URLs should end with a /.
const url = data.url.endsWith("/") ? data.url : `${data.url}/`;
- const res = await fetch(`${THIRDWEB_API_HOST}/v1/engine`, {
+ const res = await apiServerProxy({
+ pathname: "/v1/engine",
method: "POST",
headers: {
"Content-Type": "application/json",
@@ -49,10 +52,16 @@ export const EngineImportPage = (props: {
url,
}),
});
+
if (!res.ok) {
- throw new Error(`Unexpected status ${res.status}`);
+ throw new Error(res.error);
}
+ },
+ });
+ const onSubmit = async (data: ImportEngineInput) => {
+ try {
+ await importMutation.mutateAsync(data);
toast.success("Engine imported successfully");
router.push(`/team/${props.teamSlug}/~/engine`);
} catch {
@@ -128,7 +137,11 @@ export const EngineImportPage = (props: {
variant="primary"
className="w-full gap-2 text-base"
>
-
+ {importMutation.isPending ? (
+
+ ) : (
+
+ )}
Import
diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/in-app-wallets/_components/footer.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/in-app-wallets/_components/footer.tsx
index 15690bb38d3..0b6b9f8dc58 100644
--- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/in-app-wallets/_components/footer.tsx
+++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/in-app-wallets/_components/footer.tsx
@@ -18,7 +18,7 @@ function ViewDocs(props: {
}) {
const TRACKING_CATEGORY = props.trackingCategory;
return (
-
+
View Docs
@@ -130,7 +130,7 @@ function Templates(props: {
trackingCategory: string;
}) {
return (
-
+
Relevant Templates
diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/pay/webhooks/components/webhooks.client.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/pay/webhooks/components/webhooks.client.tsx
index cc8afb98123..532c181fcbc 100644
--- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/pay/webhooks/components/webhooks.client.tsx
+++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/pay/webhooks/components/webhooks.client.tsx
@@ -1,7 +1,9 @@
"use client";
+import { payServerProxy } from "@/actions/proxies";
import { GenericLoadingPage } from "@/components/blocks/skeletons/GenericLoadingPage";
import { CopyTextButton } from "@/components/ui/CopyTextButton";
+import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Button } from "@/components/ui/button";
import {
Dialog,
@@ -65,16 +67,23 @@ export function PayWebhooksPage(props: PayWebhooksPageProps) {
const webhooksQuery = useQuery({
queryKey: ["webhooks", props.clientId],
queryFn: async () => {
- const res = await fetch(
- `/api/server-proxy/pay/webhooks/get-all?clientId=${props.clientId}`,
- {
- headers: {
- "Content-Type": "application/json",
- },
+ const res = await payServerProxy({
+ method: "GET",
+ pathname: "/webhooks/get-all",
+ searchParams: {
+ clientId: props.clientId,
},
- );
- const json = await res.json();
- return json.result as Array
;
+ headers: {
+ "Content-Type": "application/json",
+ },
+ });
+
+ if (!res.ok) {
+ throw new Error();
+ }
+
+ const json = res.data as { result: Array };
+ return json.result;
},
});
@@ -97,61 +106,68 @@ export function PayWebhooksPage(props: PayWebhooksPageProps) {
}
return (
-
-
-
-
- Label
- Url
- Secret
- Created
-
-
-
-
-
-
-
-
- {webhooksQuery.data.map((webhook) => (
-
- {webhook.label}
- {webhook.url}
-
-
-
-
- {formatDistanceToNow(webhook.createdAt, { addSuffix: true })}
-
-
-
-
-
-
+
+
+
Webhooks
+
+
+
+
+
+
+
+
+
+
+
+ Label
+ Url
+ Secret
+ Created
+ Delete
- ))}
-
-
-
+
+
+ {webhooksQuery.data.map((webhook) => (
+
+ {webhook.label}
+ {webhook.url}
+
+
+
+
+ {formatDistanceToNow(webhook.createdAt, { addSuffix: true })}
+
+
+
+
+
+
+
+ ))}
+
+
+
+
);
}
const formSchema = z.object({
url: z.string().url("Please enter a valid URL."),
- label: z.string().min(1, "Please enter a label."),
+ label: z.string().min(3, "Label must be at least 3 characters long"),
});
function CreateWebhookButton(props: PropsWithChildren
) {
@@ -166,15 +182,21 @@ function CreateWebhookButton(props: PropsWithChildren) {
const queryClient = useQueryClient();
const createMutation = useMutation({
mutationFn: async (values: z.infer) => {
- const res = await fetch("/api/server-proxy/pay/webhooks/create", {
+ const res = await payServerProxy({
method: "POST",
+ pathname: "/webhooks/create",
+ body: JSON.stringify({ ...values, clientId: props.clientId }),
headers: {
"Content-Type": "application/json",
},
- body: JSON.stringify({ ...values, clientId: props.clientId }),
});
- const json = await res.json();
- return json.result as string;
+
+ if (!res.ok) {
+ throw new Error(res.error);
+ }
+
+ const json = res.data as { result: string };
+ return json.result;
},
onSuccess: () => {
return queryClient.invalidateQueries({
@@ -189,26 +211,21 @@ function CreateWebhookButton(props: PropsWithChildren) {
@@ -284,15 +306,21 @@ function DeleteWebhookButton(
const queryClient = useQueryClient();
const deleteMutation = useMutation({
mutationFn: async (id: string) => {
- const res = await fetch("/api/server-proxy/pay/webhooks/revoke", {
+ const res = await payServerProxy({
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ id, clientId: props.clientId }),
+ pathname: "/webhooks/revoke",
});
- const json = await res.json();
- return json.result as string;
+
+ if (!res.ok) {
+ throw new Error("Failed to delete webhook");
+ }
+
+ const json = res.data as { result: string };
+ return json.result;
},
onSuccess: () => {
return queryClient.invalidateQueries({
@@ -314,24 +342,28 @@ function DeleteWebhookButton(
diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/page.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/page.tsx
index 44ce7556077..ddb734706ed 100644
--- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/page.tsx
+++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/page.tsx
@@ -114,7 +114,7 @@ async function ProjectAnalytics(props: {
inAppWalletUsage,
userOpUsageTimeSeries,
userOpUsage,
- ] = await Promise.all([
+ ] = await Promise.allSettled([
// Aggregated wallet connections
getWalletConnections({
clientId: project.publishableKey,
@@ -153,7 +153,8 @@ async function ProjectAnalytics(props: {
return (
- {walletUserStatsTimeSeries.some((w) => w.totalUsers !== 0) ? (
+ {walletUserStatsTimeSeries.status === "fulfilled" &&
+ walletUserStatsTimeSeries.value.some((w) => w.totalUsers !== 0) ? (
@@ -180,16 +181,18 @@ async function ProjectAnalytics(props: {
clientId={project.publishableKey}
/>
- {walletConnections.length > 0 ? (
-
+ {walletConnections.status === "fulfilled" &&
+ walletConnections.value.length > 0 ? (
+
) : (
)}
- {inAppWalletUsage.length > 0 ? (
-
+ {inAppWalletUsage.status === "fulfilled" &&
+ inAppWalletUsage.value.length > 0 ? (
+
) : (
)}
- {userOpUsage.length > 0 ? (
+ {userOpUsageTimeSeries.status === "fulfilled" &&
+ userOpUsage.status === "fulfilled" &&
+ userOpUsage.value.length > 0 ? (
) : (
diff --git a/apps/dashboard/src/components/Image/index.tsx b/apps/dashboard/src/components/Image/index.tsx
index c135a5fb32e..b8ddfd026db 100644
--- a/apps/dashboard/src/components/Image/index.tsx
+++ b/apps/dashboard/src/components/Image/index.tsx
@@ -1,3 +1,5 @@
+"use client";
+
import { chakra } from "@chakra-ui/react";
import NextImage from "next/image";
diff --git a/apps/dashboard/src/components/analytics/stat.tsx b/apps/dashboard/src/components/analytics/stat.tsx
index d65978013f7..142f7d5245d 100644
--- a/apps/dashboard/src/components/analytics/stat.tsx
+++ b/apps/dashboard/src/components/analytics/stat.tsx
@@ -5,7 +5,7 @@ export const Stat: React.FC<{
formatter?: (value: number) => string;
}> = ({ label, value, formatter, icon: Icon }) => {
return (
-
+
-
{value !== undefined && formatter
diff --git a/apps/dashboard/src/components/buttons/MismatchButton.tsx b/apps/dashboard/src/components/buttons/MismatchButton.tsx
index d398d00ec01..53b0f6321d9 100644
--- a/apps/dashboard/src/components/buttons/MismatchButton.tsx
+++ b/apps/dashboard/src/components/buttons/MismatchButton.tsx
@@ -1,5 +1,6 @@
"use client";
+import { apiServerProxy } from "@/actions/proxies";
import { DynamicHeight } from "@/components/ui/DynamicHeight";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Button } from "@/components/ui/button";
@@ -47,7 +48,6 @@ import {
} from "thirdweb/react";
import { privateKeyToAccount } from "thirdweb/wallets";
import { getFaucetClaimAmount } from "../../app/api/testnet-faucet/claim/claim-amount";
-import { THIRDWEB_API_HOST } from "../../constants/urls";
import { useAllChainsData } from "../../hooks/chains/allChains";
import { useV5DashboardChain } from "../../lib/v5-adapter";
@@ -297,18 +297,24 @@ function NoFundsDialogContent(props: {
const chainWithServiceInfoQuery = useQuery({
queryKey: ["chain-with-services", props.chain.id],
queryFn: async () => {
- const [chain, chainServices] = await Promise.all([
- fetch(`${THIRDWEB_API_HOST}/v1/chains/${props.chain.id}`).then((res) =>
- res.json(),
- ) as Promise<{ data: ChainMetadata }>,
- fetch(`${THIRDWEB_API_HOST}/v1/chains/${props.chain.id}/services`).then(
- (res) => res.json(),
- ) as Promise<{ data: ChainServices }>,
+ const [chainRes, chainServicesRes] = await Promise.all([
+ apiServerProxy<{ data: ChainMetadata }>({
+ pathname: `/v1/chains/${props.chain.id}`,
+ method: "GET",
+ }),
+ apiServerProxy<{ data: ChainServices }>({
+ pathname: `/v1/chains/${props.chain.id}/services`,
+ method: "GET",
+ }),
]);
+ if (!chainRes.ok || !chainServicesRes.ok) {
+ throw new Error("Failed to fetch chain with services");
+ }
+
return {
- ...chain.data,
- services: chainServices.data.services,
+ ...chainRes.data.data,
+ services: chainServicesRes.data.data.services,
} satisfies ChainMetadataWithServices;
},
enabled: !!props.chain.id,
diff --git a/apps/dashboard/src/components/embedded-wallets/Analytics/InAppWalletUsersChartCard.tsx b/apps/dashboard/src/components/embedded-wallets/Analytics/InAppWalletUsersChartCard.tsx
index 55d71489d41..394f8c3c5d4 100644
--- a/apps/dashboard/src/components/embedded-wallets/Analytics/InAppWalletUsersChartCard.tsx
+++ b/apps/dashboard/src/components/embedded-wallets/Analytics/InAppWalletUsersChartCard.tsx
@@ -114,7 +114,7 @@ export function InAppWalletUsersChartCardUI(props: {
chartData.every((data) => data.sponsoredUsd === 0);
return (
-
+
{props.title}
diff --git a/apps/dashboard/src/components/homepage/sections/NewsletterSection.tsx b/apps/dashboard/src/components/homepage/sections/NewsletterSection.tsx
index 96a9aa88b8b..28342633097 100644
--- a/apps/dashboard/src/components/homepage/sections/NewsletterSection.tsx
+++ b/apps/dashboard/src/components/homepage/sections/NewsletterSection.tsx
@@ -1,6 +1,10 @@
+"use client";
+
+import { emailSignup } from "@/actions/emailSignup";
import { Box, Container, Flex, FormControl, Input } from "@chakra-ui/react";
import { MailCheckIcon } from "lucide-react";
import { useState } from "react";
+import { toast } from "sonner";
import { Button, Text } from "tw-components";
export const NewsletterSection = () => {
@@ -17,12 +21,17 @@ export const NewsletterSection = () => {
setIsSubmitting(true);
try {
- await fetch("/api/email-signup", {
- method: "POST",
- body: JSON.stringify({ email }),
+ const res = await emailSignup({
+ email,
});
+
+ if (res.status.toString().startsWith("2")) {
+ toast.success("Successfully signed up for our newsletter!");
+ }
+
setEmail("");
} catch (err) {
+ toast.error("Failed to sign up for our newsletter");
console.error(err);
}
diff --git a/apps/dashboard/src/components/landing-pages/dynamic-selector.tsx b/apps/dashboard/src/components/landing-pages/dynamic-selector.tsx
index db4fca9ffc5..4bd100f1afb 100644
--- a/apps/dashboard/src/components/landing-pages/dynamic-selector.tsx
+++ b/apps/dashboard/src/components/landing-pages/dynamic-selector.tsx
@@ -1,3 +1,5 @@
+"use client";
+
import { Box, Flex, GridItem, SimpleGrid } from "@chakra-ui/react";
import { useTrack } from "hooks/analytics/useTrack";
import { type ReactElement, useState } from "react";
diff --git a/apps/dashboard/src/components/landing-pages/guide-showcase.tsx b/apps/dashboard/src/components/landing-pages/guide-showcase.tsx
index 336539e4b2b..5cc76be0336 100644
--- a/apps/dashboard/src/components/landing-pages/guide-showcase.tsx
+++ b/apps/dashboard/src/components/landing-pages/guide-showcase.tsx
@@ -1,12 +1,13 @@
import { Flex, SimpleGrid } from "@chakra-ui/react";
import { GuideCard } from "components/product-pages/common/GuideCard";
import { MoveRightIcon } from "lucide-react";
+import type { StaticImageData } from "next/image";
import { Heading, TrackedLink, type TrackedLinkProps } from "tw-components";
type BlogPost = {
title: string;
description?: string;
- image: string;
+ image: string | StaticImageData;
link: string;
};
diff --git a/apps/dashboard/src/components/landing-pages/layout.tsx b/apps/dashboard/src/components/landing-pages/layout.tsx
index 600bacba143..ebd204bc250 100644
--- a/apps/dashboard/src/components/landing-pages/layout.tsx
+++ b/apps/dashboard/src/components/landing-pages/layout.tsx
@@ -1,28 +1,21 @@
-import { useForceDarkTheme } from "@/components/theme-provider";
import { Box, type BoxProps, Flex } from "@chakra-ui/react";
import { HomepageFooter } from "components/footer/Footer";
import { NewsletterSection } from "components/homepage/sections/NewsletterSection";
import { HomepageTopNav } from "components/product-pages/common/Topnav";
-import { NextSeo, type NextSeoProps } from "next-seo";
import type { ComponentWithChildren } from "types/component-with-children";
interface LandingLayoutProps {
- seo: NextSeoProps;
bgColor?: string;
py?: BoxProps["py"];
}
export const LandingLayout: ComponentWithChildren
= ({
- seo,
bgColor = "#000",
children,
py,
}) => {
- useForceDarkTheme();
-
return (
<>
-
= ({
});
try {
- const response = await fetch("/api/apply-op-sponsorship", {
- method: "POST",
- body: JSON.stringify({ fields }),
+ const response = await applyOpSponsorship({
+ fields,
});
if (!response.ok) {
@@ -113,8 +113,6 @@ export const ApplyForOpCreditsForm: React.FC = ({
throw new Error("Form submission failed");
}
- await response.json();
-
trackEvent({
category: "op-sponsorship",
action: "apply",
diff --git a/apps/dashboard/src/components/onboarding/applyOpSponsorship.ts b/apps/dashboard/src/components/onboarding/applyOpSponsorship.ts
new file mode 100644
index 00000000000..c4c67b14f5f
--- /dev/null
+++ b/apps/dashboard/src/components/onboarding/applyOpSponsorship.ts
@@ -0,0 +1,42 @@
+"use server";
+
+export async function applyOpSponsorship(params: {
+ fields: {
+ name: string;
+ // biome-ignore lint/suspicious/noExplicitAny: FIXME
+ value: any;
+ }[];
+}) {
+ const { fields } = params;
+
+ if (!process.env.HUBSPOT_ACCESS_TOKEN) {
+ return {
+ error: "missing HUBSPOT_ACCESS_TOKEN",
+ ok: false,
+ };
+ }
+
+ const response = await fetch(
+ "https://api.hsforms.com/submissions/v3/integration/secure/submit/23987964/2fbf6a3b-d4cc-4a23-a4f5-42674e8487b9",
+ {
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${process.env.HUBSPOT_ACCESS_TOKEN}`,
+ },
+ method: "POST",
+ body: JSON.stringify({ fields }),
+ },
+ );
+
+ if (!response.ok) {
+ const errorMessage = await response.text();
+ return {
+ error: errorMessage,
+ ok: response.ok,
+ };
+ }
+
+ return {
+ ok: response.ok,
+ };
+}
diff --git a/apps/dashboard/src/components/pay/PayAnalytics/components/common.tsx b/apps/dashboard/src/components/pay/PayAnalytics/components/common.tsx
index e085728df01..6d5263993b9 100644
--- a/apps/dashboard/src/components/pay/PayAnalytics/components/common.tsx
+++ b/apps/dashboard/src/components/pay/PayAnalytics/components/common.tsx
@@ -65,12 +65,7 @@ export function TableData({ children }: { children: React.ReactNode }) {
}
export function TableHeadingRow({ children }: { children: React.ReactNode }) {
- return (
-
- {children}
-
-
- );
+ return {children}
;
}
export function TableHeading(props: { children: React.ReactNode }) {
diff --git a/apps/dashboard/src/components/pay/PayAnalytics/hooks/usePayCustomers.ts b/apps/dashboard/src/components/pay/PayAnalytics/hooks/usePayCustomers.ts
index ef5e570481a..93d2d2f43bc 100644
--- a/apps/dashboard/src/components/pay/PayAnalytics/hooks/usePayCustomers.ts
+++ b/apps/dashboard/src/components/pay/PayAnalytics/hooks/usePayCustomers.ts
@@ -1,3 +1,4 @@
+import { payServerProxy } from "@/actions/proxies";
import { useInfiniteQuery } from "@tanstack/react-query";
import { useActiveAccount } from "thirdweb/react";
@@ -31,31 +32,25 @@ export function usePayCustomers(options: {
? "/stats/new-customers/v1"
: "/stats/customers/v1";
- const searchParams = new URLSearchParams();
-
const start = options.pageSize * pageParam;
- searchParams.append("skip", `${start}`);
- searchParams.append("take", `${options.pageSize}`);
-
- searchParams.append("clientId", options.clientId);
- searchParams.append("fromDate", `${options.from.getTime()}`);
- searchParams.append("toDate", `${options.to.getTime()}`);
- const res = await fetch(
- `/api/server-proxy/pay/${endpoint}?${searchParams.toString()}`,
- {
- method: "GET",
- headers: {
- "Content-Type": "application/json",
- },
+ const res = await payServerProxy({
+ method: "GET",
+ pathname: endpoint,
+ searchParams: {
+ skip: `${start}`,
+ take: `${options.pageSize}`,
+ clientId: options.clientId,
+ fromDate: `${options.from.getTime()}`,
+ toDate: `${options.to.getTime()}`,
},
- );
+ });
if (!res.ok) {
throw new Error("Failed to fetch pay volume");
}
- const resJSON = (await res.json()) as Response;
+ const resJSON = res.data as Response;
const pageData = resJSON.result.data;
const itemsRequested = options.pageSize * (pageParam + 1);
diff --git a/apps/dashboard/src/components/pay/PayAnalytics/hooks/usePayNewCustomers.ts b/apps/dashboard/src/components/pay/PayAnalytics/hooks/usePayNewCustomers.ts
index 5f5445c7c6c..f359e3241cc 100644
--- a/apps/dashboard/src/components/pay/PayAnalytics/hooks/usePayNewCustomers.ts
+++ b/apps/dashboard/src/components/pay/PayAnalytics/hooks/usePayNewCustomers.ts
@@ -1,3 +1,4 @@
+import { payServerProxy } from "@/actions/proxies";
import { useQuery } from "@tanstack/react-query";
import { useActiveAccount } from "thirdweb/react";
@@ -33,27 +34,25 @@ export function usePayNewCustomers(options: {
return useQuery({
queryKey: ["usePayNewCustomers", address, options],
queryFn: async () => {
- const searchParams = new URLSearchParams();
- searchParams.append("intervalType", options.intervalType);
- searchParams.append("clientId", options.clientId);
- searchParams.append("fromDate", `${options.from.getTime()}`);
- searchParams.append("toDate", `${options.to.getTime()}`);
-
- const res = await fetch(
- `/api/server-proxy/pay/stats/aggregate/customers/v1?${searchParams.toString()}`,
- {
- method: "GET",
- headers: {
- "Content-Type": "application/json",
- },
+ const res = await payServerProxy({
+ pathname: "/stats/aggregate/customers/v1",
+ searchParams: {
+ intervalType: options.intervalType,
+ clientId: options.clientId,
+ fromDate: `${options.from.getTime()}`,
+ toDate: `${options.to.getTime()}`,
+ },
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
},
- );
+ });
if (!res.ok) {
throw new Error("Failed to fetch new customers");
}
- const resJSON = (await res.json()) as Response;
+ const resJSON = res.data as Response;
return resJSON.result.data;
},
diff --git a/apps/dashboard/src/components/pay/PayAnalytics/hooks/usePayPurchases.ts b/apps/dashboard/src/components/pay/PayAnalytics/hooks/usePayPurchases.ts
index 3834f9588bd..c7796431be1 100644
--- a/apps/dashboard/src/components/pay/PayAnalytics/hooks/usePayPurchases.ts
+++ b/apps/dashboard/src/components/pay/PayAnalytics/hooks/usePayPurchases.ts
@@ -1,3 +1,4 @@
+import { payServerProxy } from "@/actions/proxies";
import { keepPreviousData, useQuery } from "@tanstack/react-query";
import { useActiveAccount } from "thirdweb/react";
@@ -71,29 +72,26 @@ export function usePayPurchases(options: PayPurchaseOptions) {
}
export async function getPayPurchases(options: PayPurchaseOptions) {
- const searchParams = new URLSearchParams();
- searchParams.append("skip", `${options.start}`);
- searchParams.append("take", `${options.count}`);
-
- searchParams.append("clientId", options.clientId);
- searchParams.append("fromDate", `${options.from.getTime()}`);
- searchParams.append("toDate", `${options.to.getTime()}`);
-
- const res = await fetch(
- `/api/server-proxy/pay/stats/purchases/v1?${searchParams.toString()}`,
- {
- method: "GET",
- headers: {
- "Content-Type": "application/json",
- },
+ const res = await payServerProxy({
+ pathname: "/stats/purchases/v1",
+ searchParams: {
+ skip: `${options.start}`,
+ take: `${options.count}`,
+ clientId: options.clientId,
+ fromDate: `${options.from.getTime()}`,
+ toDate: `${options.to.getTime()}`,
},
- );
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ });
if (!res.ok) {
throw new Error("Failed to fetch pay volume");
}
- const resJSON = (await res.json()) as Response;
+ const resJSON = res.data as Response;
return resJSON.result.data;
}
diff --git a/apps/dashboard/src/components/pay/PayAnalytics/hooks/usePayVolume.ts b/apps/dashboard/src/components/pay/PayAnalytics/hooks/usePayVolume.ts
index 3009972ee82..b767e5719be 100644
--- a/apps/dashboard/src/components/pay/PayAnalytics/hooks/usePayVolume.ts
+++ b/apps/dashboard/src/components/pay/PayAnalytics/hooks/usePayVolume.ts
@@ -1,3 +1,4 @@
+import { payServerProxy } from "@/actions/proxies";
import { useQuery } from "@tanstack/react-query";
import { useActiveAccount } from "thirdweb/react";
@@ -64,29 +65,27 @@ export function usePayVolume(options: {
return useQuery({
queryKey: ["usePayVolume", address, options],
queryFn: async () => {
- const searchParams = new URLSearchParams();
- searchParams.append("intervalType", options.intervalType);
- searchParams.append("clientId", options.clientId);
- searchParams.append("fromDate", `${options.from.getTime()}`);
- searchParams.append("toDate", `${options.to.getTime()}`);
-
- const res = await fetch(
- `/api/server-proxy/pay/stats/aggregate/volume/v1?${searchParams.toString()}`,
- {
- method: "GET",
- headers: {
- "Content-Type": "application/json",
- },
+ const res = await payServerProxy({
+ pathname: "/stats/aggregate/volume/v1",
+ searchParams: {
+ intervalType: options.intervalType,
+ clientId: options.clientId,
+ fromDate: `${options.from.getTime()}`,
+ toDate: `${options.to.getTime()}`,
+ },
+ headers: {
+ "Content-Type": "application/json",
},
- );
+ method: "GET",
+ });
if (!res.ok) {
throw new Error("Failed to fetch pay volume");
}
- const resJSON = (await res.json()) as Response;
+ const json = res.data as Response;
- return resJSON.result.data;
+ return json.result.data;
},
retry: false,
});
diff --git a/apps/dashboard/src/components/product-pages/common/GuideCard.tsx b/apps/dashboard/src/components/product-pages/common/GuideCard.tsx
index a67090c3115..6c61eb2835c 100644
--- a/apps/dashboard/src/components/product-pages/common/GuideCard.tsx
+++ b/apps/dashboard/src/components/product-pages/common/GuideCard.tsx
@@ -1,5 +1,5 @@
import { Box, Flex } from "@chakra-ui/react";
-import NextImage from "next/image";
+import NextImage, { type StaticImageData } from "next/image";
import {
Heading,
Text,
@@ -9,7 +9,7 @@ import {
interface GuideCardProps
extends Pick {
- image: string;
+ image: string | StaticImageData;
title: string;
description?: string;
link: string;
diff --git a/apps/dashboard/src/components/settings/Account/Billing/CouponCard.tsx b/apps/dashboard/src/components/settings/Account/Billing/CouponCard.tsx
index d6aefc7fa5e..925c262d9bc 100644
--- a/apps/dashboard/src/components/settings/Account/Billing/CouponCard.tsx
+++ b/apps/dashboard/src/components/settings/Account/Billing/CouponCard.tsx
@@ -1,5 +1,6 @@
"use client";
+import { apiServerProxy } from "@/actions/proxies";
import { DangerSettingCard } from "@/components/blocks/DangerSettingCard";
import { SettingsCard } from "@/components/blocks/SettingsCard";
import { Spinner } from "@/components/ui/Spinner/Spinner";
@@ -63,8 +64,9 @@ function ApplyCouponCard(props: {
scrollIntoView={!!couponCode}
isPaymentSetup={props.isPaymentSetup}
submit={async (promoCode: string) => {
- const res = await fetch("/api/server-proxy/api/v1/coupons/redeem", {
+ const res = await apiServerProxy<{ data: ActiveCouponResponse }>({
method: "POST",
+ pathname: "/v1/coupons/redeem",
headers: {
"Content-Type": "application/json",
},
@@ -75,10 +77,10 @@ function ApplyCouponCard(props: {
});
if (res.ok) {
- const json = await res.json();
+ const json = res.data;
return {
status: 200,
- data: json.data as ActiveCouponResponse,
+ data: json.data,
};
}
@@ -111,7 +113,7 @@ export function ApplyCouponCardUI(props: {
const form = useForm>({
resolver: zodResolver(couponFormSchema),
defaultValues: {
- promoCode: props.prefillPromoCode,
+ promoCode: props.prefillPromoCode || "",
},
});
@@ -297,29 +299,28 @@ export function CouponSection(props: {
const activeCoupon = useQuery({
queryKey: ["active-coupon", address, props.teamId],
queryFn: async () => {
- const res = await fetch(
- `/api/server-proxy/api/v1/active-coupon${
- props.teamId ? `?teamId=${props.teamId}` : ""
- }`,
- );
+ const res = await apiServerProxy<{ data: ActiveCouponResponse }>({
+ method: "GET",
+ pathname: "/v1/active-coupon",
+ searchParams: props.teamId ? { teamId: props.teamId } : undefined,
+ });
+
if (!res.ok) {
return null;
}
- const json = await res.json();
- return json.data as ActiveCouponResponse;
+ const json = res.data;
+ return json.data;
},
});
const deleteActiveCoupon = useMutation({
mutationFn: async () => {
- const res = await fetch(
- `/api/server-proxy/api/v1/active-coupon${
- props.teamId ? `?teamId=${props.teamId}` : ""
- }`,
- {
- method: "DELETE",
- },
- );
+ const res = await apiServerProxy({
+ method: "DELETE",
+ pathname: "/v1/active-coupon",
+ searchParams: props.teamId ? { teamId: props.teamId } : undefined,
+ });
+
if (!res.ok) {
throw new Error("Failed to delete coupon");
}
diff --git a/apps/dashboard/src/components/shared/ProgressBar.tsx b/apps/dashboard/src/components/shared/ProgressBar.tsx
deleted file mode 100644
index bc04b65a654..00000000000
--- a/apps/dashboard/src/components/shared/ProgressBar.tsx
+++ /dev/null
@@ -1,117 +0,0 @@
-import { useRouter } from "next/router";
-import { useEffect, useState } from "react";
-
-// Modified code from https://github.com/MatinAniss/traction.js
-
-interface ProgressBarProps {
- color: string;
- incrementInterval: number;
- incrementAmount: number;
- transitionDuration: number;
- transitionTimingFunction:
- | "ease"
- | "linear"
- | "ease-in"
- | "ease-out"
- | "ease-in-out";
-}
-
-const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
-
-export const ProgressBar: React.FC = (props) => {
- // Declare states
- const router = useRouter();
- const [progress, setProgress] = useState(0);
- const [isVisible, setIsVisible] = useState(false);
-
- // Progress bar inline styling
- const styling = {
- position: "fixed",
- top: 0,
- left: 0,
- width: `${progress}%`,
- height: "2px",
- backgroundColor: props.color,
- transition: `width ${props.transitionDuration}ms ${props.transitionTimingFunction}`,
- opacity: isVisible ? 1 : 0,
- zIndex: 9999999999,
- } as React.CSSProperties;
-
- // somewhat legitimate use-case
- // TODO: do we really need this?
- // eslint-disable-next-line no-restricted-syntax
- useEffect(() => {
- // Declare timeout
- let status: "in-progress" | "idle" = "idle";
- let intervalId: ReturnType;
-
- // Route change start function
- const onRouteChangeStart = async () => {
- status = "in-progress";
-
- // only show progress bar if it takes longer than 200ms for the page to load
- await wait(200);
-
- if (status !== "in-progress") {
- return;
- }
-
- setIsVisible(true);
- setProgress(props.incrementAmount);
- // clear any existing interval
- clearInterval(intervalId);
-
- const newIntervalId = setInterval(() => {
- if (status === "idle") {
- clearInterval(newIntervalId);
- return;
- }
- setProgress((_progress) => {
- return Math.min(_progress + props.incrementAmount, 90);
- });
- }, props.incrementInterval);
-
- intervalId = newIntervalId;
- };
-
- // Route change complete function
- const onRouteChangeComplete = () => {
- status = "idle";
- clearInterval(intervalId);
- setProgress(100);
- setTimeout(() => {
- if (status === "idle") {
- setIsVisible(false);
- setProgress(0);
- }
- }, props.transitionDuration);
- };
-
- // Route change error function
- const onRouteChangeError = () => {
- status = "idle";
- clearInterval(intervalId);
- setIsVisible(false);
- setProgress(0);
- };
-
- // Router event listeners
- router.events.on("routeChangeStart", onRouteChangeStart);
- router.events.on("routeChangeComplete", onRouteChangeComplete);
- router.events.on("routeChangeError", onRouteChangeError);
-
- return () => {
- router.events.off("routeChangeStart", onRouteChangeStart);
- router.events.off("routeChangeComplete", onRouteChangeComplete);
- router.events.off("routeChangeError", onRouteChangeError);
- clearInterval(intervalId);
- };
- }, [
- props.incrementAmount,
- props.incrementInterval,
- props.transitionDuration,
- router.events,
- ]);
-
- return ;
-};
diff --git a/apps/dashboard/src/components/smart-wallets/AccountAbstractionAnalytics/SponsoredTransactionsChartCard.tsx b/apps/dashboard/src/components/smart-wallets/AccountAbstractionAnalytics/SponsoredTransactionsChartCard.tsx
index 608d72863c1..f3cfe56cecd 100644
--- a/apps/dashboard/src/components/smart-wallets/AccountAbstractionAnalytics/SponsoredTransactionsChartCard.tsx
+++ b/apps/dashboard/src/components/smart-wallets/AccountAbstractionAnalytics/SponsoredTransactionsChartCard.tsx
@@ -111,7 +111,7 @@ export function SponsoredTransactionsChartCard(props: {
chartData.every((data) => data.transactions === 0);
return (
-
+
Sponsored Transactions
diff --git a/apps/dashboard/src/components/smart-wallets/AccountAbstractionAnalytics/TotalSponsoredChartCard.tsx b/apps/dashboard/src/components/smart-wallets/AccountAbstractionAnalytics/TotalSponsoredChartCard.tsx
index 33d0efa3ad5..2a24eea67e0 100644
--- a/apps/dashboard/src/components/smart-wallets/AccountAbstractionAnalytics/TotalSponsoredChartCard.tsx
+++ b/apps/dashboard/src/components/smart-wallets/AccountAbstractionAnalytics/TotalSponsoredChartCard.tsx
@@ -111,7 +111,7 @@ export function TotalSponsoredChartCard(props: {
chartData.every((data) => data.sponsoredUsd === 0);
return (
-
+
Gas Sponsored
diff --git a/apps/dashboard/src/constants/urls.ts b/apps/dashboard/src/constants/urls.ts
index 8f9db171323..aca7f7a9eb2 100644
--- a/apps/dashboard/src/constants/urls.ts
+++ b/apps/dashboard/src/constants/urls.ts
@@ -1,7 +1,3 @@
-export const THIRDWEB_API_HOST = "/api/server-proxy/api";
-
-export const THIRDWEB_ANALYTICS_API_HOST = "/api/server-proxy/analytics";
-
export const THIRDWEB_EWS_API_HOST =
process.env.NEXT_PUBLIC_THIRDWEB_EWS_API_HOST ||
"https://in-app-wallet.thirdweb.com";
diff --git a/apps/dashboard/src/data/analytics/fetch-api-server.ts b/apps/dashboard/src/data/analytics/fetch-api-server.ts
index cb2fb4635d9..82baaf9b93e 100644
--- a/apps/dashboard/src/data/analytics/fetch-api-server.ts
+++ b/apps/dashboard/src/data/analytics/fetch-api-server.ts
@@ -1,4 +1,5 @@
import { COOKIE_ACTIVE_ACCOUNT, COOKIE_PREFIX_TOKEN } from "@/constants/cookie";
+import { API_SERVER_URL } from "@/constants/env";
import { cookies } from "next/headers";
import { getAddress } from "thirdweb";
import "server-only";
@@ -19,22 +20,18 @@ export async function fetchApiServer(
}
// create a new URL object for the analytics server
- const API_SERVER_URL = new URL(
- process.env.NEXT_PUBLIC_THIRDWEB_API_HOST || "https://api.thirdweb.com",
- );
- API_SERVER_URL.pathname = pathname;
+ const url = new URL(API_SERVER_URL);
+
+ url.pathname = pathname;
for (const param of searchParams?.split("&") || []) {
const [key, value] = param.split("=");
if (!key || !value) {
throw new Error("Invalid input, no key or value provided");
}
- API_SERVER_URL.searchParams.append(
- decodeURIComponent(key),
- decodeURIComponent(value),
- );
+ url.searchParams.append(decodeURIComponent(key), decodeURIComponent(value));
}
- return fetch(API_SERVER_URL, {
+ return fetch(url, {
...init,
headers: {
"content-type": "application/json",
diff --git a/apps/dashboard/src/hooks/useBuildId.ts b/apps/dashboard/src/hooks/useBuildId.ts
deleted file mode 100644
index c9edb85b0e5..00000000000
--- a/apps/dashboard/src/hooks/useBuildId.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import posthog from "posthog-js";
-import { useCallback } from "react";
-
-export const useBuildId = () => {
- const shouldReload = useCallback((): boolean => {
- if (process.env.NODE_ENV !== "production") {
- return false;
- }
-
- const nextData = document.querySelector("#__NEXT_DATA__");
-
- const buildId = nextData?.textContent
- ? JSON.parse(nextData.textContent).buildId
- : null;
-
- const request = new XMLHttpRequest();
- request.open("HEAD", `/_next/static/${buildId}/_buildManifest.js`, false);
- request.setRequestHeader("Pragma", "no-cache");
- request.setRequestHeader("Cache-Control", "no-cache");
- request.setRequestHeader(
- "If-Modified-Since",
- "Thu, 01 Jun 1970 00:00:00 GMT",
- );
- request.send(null);
-
- if (request.status === 404) {
- posthog.capture("should_reload", {
- buildId,
- });
- }
-
- return request.status === 404;
- }, []);
-
- return {
- shouldReload,
- };
-};
diff --git a/apps/dashboard/src/page-id.ts b/apps/dashboard/src/page-id.ts
deleted file mode 100644
index 2e271b7cf0f..00000000000
--- a/apps/dashboard/src/page-id.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-// biome-ignore lint/nursery/noEnum: planned to be removed in the future
-export enum PageId {
- // none case (for previous page id)
- None = "none",
-
- // ---------------------------------------------------------------------------
- // marketing / growth pages
- // ---------------------------------------------------------------------------
-
- // thirdweb.com/account-abstraction
- SmartWalletLanding = "smart-wallet-landing",
-
- // thirdweb.com/embedded-wallets
- EmbeddedWalletsLanding = "embedded-wallets-landing",
-
- // thirdweb.com/auth
- AuthLanding = "auth-landing",
-
- // ---------------------------------------------------------------------------
- // general product pages
- // ---------------------------------------------------------------------------
-
- // thirdweb.com/404
- PageNotFound = "page-not-found",
-}
diff --git a/apps/dashboard/src/pages/404.tsx b/apps/dashboard/src/pages/404.tsx
deleted file mode 100644
index bbd6f5e05e9..00000000000
--- a/apps/dashboard/src/pages/404.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import { PageId } from "page-id";
-import type { ThirdwebNextPage } from "utils/types";
-import { NotFoundPage } from "../components/not-found-page";
-
-const PageNotFound: ThirdwebNextPage = () => {
- return
;
-};
-
-PageNotFound.pageId = PageId.PageNotFound;
-
-export default PageNotFound;
diff --git a/apps/dashboard/src/pages/DO_NOT_ADD_THINGS_HERE.md b/apps/dashboard/src/pages/DO_NOT_ADD_THINGS_HERE.md
deleted file mode 100644
index d869a3b0f16..00000000000
--- a/apps/dashboard/src/pages/DO_NOT_ADD_THINGS_HERE.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# DO NOT ADD NEW THINGS INSIDE THIS FOLDER OR SUB_FOLDERS
-
-This part of the dashboard is deprecated, and we are actively working on removing it. Please do not add new things here. If you need to add new things, please add them to the appropriate folder in the src folder.
\ No newline at end of file
diff --git a/apps/dashboard/src/pages/_app.tsx b/apps/dashboard/src/pages/_app.tsx
deleted file mode 100644
index 14b148a2c2c..00000000000
--- a/apps/dashboard/src/pages/_app.tsx
+++ /dev/null
@@ -1,311 +0,0 @@
-import { ThemeProvider } from "@/components/theme-provider";
-import { Toaster } from "@/components/ui/sonner";
-import { ChakraProvider, useColorMode } from "@chakra-ui/react";
-import { Global, css } from "@emotion/react";
-import type { DehydratedState } from "@tanstack/react-query";
-import { ProgressBar } from "components/shared/ProgressBar";
-import { useBuildId } from "hooks/useBuildId";
-import PlausibleProvider from "next-plausible";
-import { DefaultSeo } from "next-seo";
-import { useTheme } from "next-themes";
-import type { AppProps } from "next/app";
-import {
- IBM_Plex_Mono as ibmPlexMonoConstructor,
- Inter as interConstructor,
-} from "next/font/google";
-import { useRouter } from "next/router";
-import { PageId } from "page-id";
-import posthog from "posthog-js";
-import { memo, useEffect, useMemo, useRef } from "react";
-import { generateBreakpointTypographyCssVars } from "tw-components/utils/typography";
-import type { ThirdwebNextPage } from "utils/types";
-import chakraTheme from "../theme";
-import "@/styles/globals.css";
-import { DashboardRouterTopProgressBar } from "@/lib/DashboardRouter";
-import { UnlimitedWalletsBanner } from "../components/notices/AnnouncementBanner";
-
-const inter = interConstructor({
- subsets: ["latin"],
- display: "swap",
- fallback: ["system-ui", "Helvetica Neue", "Arial", "sans-serif"],
- adjustFontFallback: true,
-});
-
-const ibmPlexMono = ibmPlexMonoConstructor({
- weight: ["400", "500", "600", "700"],
- subsets: ["latin"],
- display: "swap",
- fallback: ["Consolas", "Courier New", "monospace"],
-});
-
-const chakraThemeWithFonts = {
- ...chakraTheme,
- fonts: {
- ...chakraTheme.fonts,
- heading: inter.style.fontFamily,
- body: inter.style.fontFamily,
- mono: ibmPlexMono.style.fontFamily,
- },
-};
-
-const fontSizeCssVars = generateBreakpointTypographyCssVars();
-
-type AppPropsWithLayout = AppProps<{ dehydratedState?: DehydratedState }> & {
- Component: ThirdwebNextPage;
-};
-
-const ConsoleAppWrapper: React.FC
= ({
- Component,
- pageProps,
-}) => {
- // run this ONCE on app load
- // eslint-disable-next-line no-restricted-syntax
-
- const router = useRouter();
- const { shouldReload } = useBuildId();
-
- // legit use-case, will go away as part of app router rewrite (once finished)
- // eslint-disable-next-line no-restricted-syntax
- useEffect(() => {
- const handleRouteChange = async () => {
- if (shouldReload()) {
- router.reload();
- }
- };
-
- router.events.on("routeChangeComplete", handleRouteChange);
-
- return () => {
- router.events.off("routeChangeComplete", handleRouteChange);
- };
- }, [router, shouldReload]);
-
- // legit use-case
- // eslint-disable-next-line no-restricted-syntax
- useEffect(() => {
- // Taken from StackOverflow. Trying to detect both Safari desktop and mobile.
- const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
- if (isSafari) {
- // This is kind of a lie.
- // We still rely on the manual Next.js scrollRestoration logic.
- // However, we *also* don't want Safari grey screen during the back swipe gesture.
- // Seems like it doesn't hurt to enable auto restore *and* Next.js logic at the same time.
- history.scrollRestoration = "auto";
- } else {
- // For other browsers, let Next.js set scrollRestoration to 'manual'.
- // It seems to work better for Chrome and Firefox which don't animate the back swipe.
- }
- }, []);
-
- // legit use-case
- // eslint-disable-next-line no-restricted-syntax
- useEffect(() => {
- // Init PostHog
- posthog.init(
- process.env.NEXT_PUBLIC_POSTHOG_API_KEY ||
- "phc_hKK4bo8cHZrKuAVXfXGpfNSLSJuucUnguAgt2j6dgSV",
- {
- api_host: "https://a.thirdweb.com",
- autocapture: true,
- debug: false,
- capture_pageview: false,
- disable_session_recording: true,
- },
- );
- // register the git commit sha on all subsequent events
- posthog.register({
- tw_dashboard_version: process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA,
- });
- // defer session recording start by 2 seconds because it synchronously loads JS
- const t = setTimeout(() => {
- posthog.startSessionRecording();
- }, 2_000);
- return () => {
- clearTimeout(t);
- };
- }, []);
-
- const pageId =
- typeof Component.pageId === "function"
- ? Component.pageId(pageProps)
- : Component.pageId;
-
- // starts out with "none" page id
- const prevPageId = useRef(PageId.None);
- // legit use-case
- // eslint-disable-next-line no-restricted-syntax
- useEffect(() => {
- // this catches the case where the hook is called twice on the same page
- if (pageId === prevPageId.current) {
- return;
- }
- posthog.register({
- page_id: pageId,
- previous_page_id: prevPageId.current,
- });
- posthog.capture("$pageview");
- return () => {
- prevPageId.current = pageId;
- };
- }, [pageId]);
-
- const canonicalUrl = useMemo(() => {
- const base = "https://thirdweb.com";
- // replace all re-written middleware paths
- const path = router.asPath
- .replace("/evm/", "/")
- .replace("/chain/", "/")
- .replace("/publish/", "/");
- return `${base}${path}`;
- }, [router.asPath]);
-
- return (
-
- );
-};
-
-interface ConsoleAppProps {
- Component: AppPropsWithLayout["Component"];
- pageProps: AppPropsWithLayout["pageProps"];
- seoCanonical: string;
- isFallback?: boolean;
-}
-
-const ConsoleApp = memo(function ConsoleApp({
- Component,
- pageProps,
- seoCanonical,
- isFallback,
-}: ConsoleAppProps) {
- const getLayout = Component.getLayout ?? ((page) => page);
-
- return (
-
- div > svg {
- font-size: 10px !important;
- }
- `}
- />
-
-
-
-
-
-
-
-
-
- {isFallback && Component.fallback
- ? Component.fallback
- : getLayout(, pageProps)}
-
-
-
-
-
- );
-});
-
-function TailwindTheme(props: { children: React.ReactNode }) {
- return (
-
- {props.children}
-
- );
-}
-
-const SyncTheme: React.FC = () => {
- const { theme, setTheme } = useTheme();
- const { setColorMode } = useColorMode();
- // eslint-disable-next-line no-restricted-syntax
- useEffect(() => {
- setColorMode(theme === "light" ? "light" : "dark");
- }, [setColorMode, theme]);
-
- // handle dashboard with now old "system" set
- // eslint-disable-next-line no-restricted-syntax
- useEffect(() => {
- if (theme === "system") {
- setTheme("dark");
- setColorMode("dark");
- }
- }, [theme, setTheme, setColorMode]);
-
- return null;
-};
-
-export default ConsoleAppWrapper;
diff --git a/apps/dashboard/src/pages/_document.tsx b/apps/dashboard/src/pages/_document.tsx
deleted file mode 100644
index 6ac3119dc26..00000000000
--- a/apps/dashboard/src/pages/_document.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import { ColorModeScript } from "@chakra-ui/react";
-import Document, { Head, Html, Main, NextScript } from "next/document";
-import chakraTheme from "../theme";
-
-class ConsoleDocument extends Document {
- render() {
- return (
-
-
- {/* preconnect to domains we know we'll be using */}
-
-
-
-
-
-
-
-
-
-
-
- );
- }
-}
-
-export default ConsoleDocument;
diff --git a/apps/dashboard/src/pages/_error.tsx b/apps/dashboard/src/pages/_error.tsx
deleted file mode 100644
index 3dd9179de70..00000000000
--- a/apps/dashboard/src/pages/_error.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-/**
- * This page is loaded by Nextjs:
- * - on the server, when data-fetching methods throw or reject
- * - on the client, when `getInitialProps` throws or rejects
- * - on the client, when a React lifecycle method throws or rejects, and it's
- * caught by the built-in Nextjs error boundary
- *
- * See:
- * - https://nextjs.org/docs/basic-features/data-fetching/overview
- * - https://nextjs.org/docs/api-reference/data-fetching/get-initial-props
- * - https://reactjs.org/docs/error-boundaries.html
- */
-import * as Sentry from "@sentry/nextjs";
-import type { NextPage } from "next";
-import NextErrorComponent, { type ErrorProps } from "next/error";
-
-const CustomErrorComponent: NextPage = (props) => (
-
-);
-
-CustomErrorComponent.getInitialProps = async (contextData) => {
- // In case this is running in a serverless function, await this in order to give Sentry
- // time to send the error before the lambda exits
- await Sentry.captureUnderscoreErrorException(contextData);
-
- if (contextData.err instanceof Error) {
- Sentry.withScope((scope) => {
- scope.setTag("page-crashed", "true");
- scope.setLevel("fatal");
- Sentry.captureException(contextData.err, {
- extra: {
- crashedPage: true,
- boundary: "global",
- router: "pages",
- },
- });
- });
- }
-
- // This will contain the status code of the response
- return NextErrorComponent.getInitialProps(contextData);
-};
-
-export default CustomErrorComponent;
diff --git a/apps/dashboard/src/pages/account-abstraction.tsx b/apps/dashboard/src/pages/account-abstraction.tsx
deleted file mode 100644
index a604eab7685..00000000000
--- a/apps/dashboard/src/pages/account-abstraction.tsx
+++ /dev/null
@@ -1,202 +0,0 @@
-import { Box, Container, Flex } from "@chakra-ui/react";
-import { ChakraNextImage } from "components/Image";
-import { LandingDynamicSelector } from "components/landing-pages/dynamic-selector";
-import { LandingEndCTA } from "components/landing-pages/end-cta";
-import { LandingGuidesShowcase } from "components/landing-pages/guide-showcase";
-import { LandingHeroWithSideImage } from "components/landing-pages/hero-with-side-image";
-import { LandingLayout } from "components/landing-pages/layout";
-import { LandingShowcaseImage } from "components/landing-pages/showcase-image";
-import { getAbsoluteUrl } from "lib/vercel-utils";
-import { PageId } from "page-id";
-import { Heading, Text } from "tw-components";
-import type { ThirdwebNextPage } from "utils/types";
-
-const TRACKING_CATEGORY = "smart-wallet-landing";
-
-const GUIDES = [
- {
- title: "The Quick-Start Guide to Account Abstraction",
- image: require("../../public/assets/product-pages/smart-wallet/get-started.png"),
- link: "https://portal.thirdweb.com/wallets/smart-wallet/get-started",
- },
- {
- title: "Choosing Between Simple, Managed, & Dynamic Smart Accounts",
- image: require("../../public/assets/product-pages/smart-wallet/which-contract.png"),
- link: "https://blog.thirdweb.com/smart-contract-deep-dive-building-smart-wallets-for-individuals-and-teams/",
- },
- {
- title: "How to Enable Batch Transactions with Account Abstraction",
- image: require("../../public/assets/product-pages/smart-wallet/batch-txns.png"),
- link: "https://blog.thirdweb.com/guides/how-to-batch-transactions-with-the-thirdweb-sdk/",
- },
-];
-
-const SmartWallet: ThirdwebNextPage = () => {
- return (
-
-
-
-
- ),
- },
- {
- title: "Instant onboarding for every user",
- description:
- "Auth for the most popular web3 wallets and web2 login flows — with just an email, phone number, social account, or passkeys.",
- Component: (
-
- ),
- },
- {
- title: "Enterprise-grade security",
- description:
- "Wallet recovery, 2FA, and multi-signature support for ultimate peace of mind — for users & teams.",
- Component: (
-
- ),
- },
- ]}
- />
-
-
- An all-in-one solution for
-
- Account Abstraction
-
-
-
- Implement account abstraction into any web3 app — with a
- best-in-class SDK, full wallet customizability, and managed
- infrastructure.
-
-
-
-
-
-
-
-
-
-
-
-
- );
-};
-
-SmartWallet.pageId = PageId.SmartWalletLanding;
-
-export default SmartWallet;
diff --git a/apps/dashboard/src/pages/api/apply-op-sponsorship.ts b/apps/dashboard/src/pages/api/apply-op-sponsorship.ts
deleted file mode 100644
index c0b52efadba..00000000000
--- a/apps/dashboard/src/pages/api/apply-op-sponsorship.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import { type NextRequest, NextResponse } from "next/server";
-import invariant from "tiny-invariant";
-
-export const config = {
- runtime: "edge",
-};
-
-interface ApplyOpSponsorshipPayload {
- // biome-ignore lint/suspicious/noExplicitAny: FIXME
- fields: any;
-}
-
-const handler = async (req: NextRequest) => {
- if (req.method !== "POST") {
- return NextResponse.json({ error: "invalid method" }, { status: 400 });
- }
-
- const requestBody = (await req.json()) as ApplyOpSponsorshipPayload;
-
- const { fields } = requestBody;
- invariant(process.env.HUBSPOT_ACCESS_TOKEN, "missing HUBSPOT_ACCESS_TOKEN");
-
- const response = await fetch(
- "https://api.hsforms.com/submissions/v3/integration/secure/submit/23987964/2fbf6a3b-d4cc-4a23-a4f5-42674e8487b9",
- {
- headers: {
- "Content-Type": "application/json",
- Authorization: `Bearer ${process.env.HUBSPOT_ACCESS_TOKEN}`,
- },
- method: "POST",
- body: JSON.stringify({ fields }),
- },
- );
-
- if (!response.ok) {
- const body = await response.json();
- console.error("error", body);
- }
-
- return NextResponse.json(
- { status: response.statusText },
- {
- status: response.status,
- },
- );
-};
-
-export default handler;
diff --git a/apps/dashboard/src/pages/api/email-signup.ts b/apps/dashboard/src/pages/api/email-signup.ts
deleted file mode 100644
index 75ff46f5471..00000000000
--- a/apps/dashboard/src/pages/api/email-signup.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-import { type NextRequest, NextResponse } from "next/server";
-import invariant from "tiny-invariant";
-
-export const config = {
- runtime: "edge",
-};
-
-interface EmailSignupPayload {
- email: string;
- send_welcome_email?: boolean;
-}
-
-const handler = async (req: NextRequest) => {
- if (req.method !== "POST") {
- return NextResponse.json({ error: "invalid method" }, { status: 400 });
- }
-
- const requestBody = (await req.json()) as EmailSignupPayload;
-
- const { email, send_welcome_email = false } = requestBody;
- invariant(process.env.BEEHIIV_API_KEY, "missing BEEHIIV_API_KEY");
-
- const response = await fetch(
- "https://api.beehiiv.com/v2/publications/pub_9f54090a-6d14-406b-adfd-dbb30574f664/subscriptions",
- {
- headers: {
- "Content-Type": "application/json",
- Authorization: `Bearer ${process.env.BEEHIIV_API_KEY}`,
- },
- method: "POST",
- body: JSON.stringify({
- email,
- send_welcome_email,
- utm_source: "thirdweb.com",
- }),
- },
- );
-
- return NextResponse.json(
- { status: response.statusText },
- {
- status: response.status,
- },
- );
-};
-
-export default handler;
diff --git a/apps/dashboard/src/pages/api/server-proxy/analytics/[...paths].ts b/apps/dashboard/src/pages/api/server-proxy/analytics/[...paths].ts
deleted file mode 100644
index 8e9bac6c576..00000000000
--- a/apps/dashboard/src/pages/api/server-proxy/analytics/[...paths].ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import type { NextRequest } from "next/server";
-
-export const config = {
- runtime: "edge",
-};
-const handler = async (req: NextRequest) => {
- // get the full path from the request including query params
- let pathname = req.nextUrl.pathname;
-
- // remove the /api/server-proxy prefix
- pathname = pathname.replace(/^\/api\/server-proxy\/analytics/, "");
-
- const searchParams = req.nextUrl.searchParams;
- searchParams.delete("paths");
-
- // create a new URL object for the analytics server
- const API_SERVER_URL = new URL(
- process.env.ANALYTICS_SERVICE_URL || "https://analytics.thirdweb.com",
- );
- API_SERVER_URL.pathname = pathname;
- searchParams.forEach((value, key) => {
- API_SERVER_URL.searchParams.append(key, value);
- });
-
- return fetch(API_SERVER_URL, {
- method: req.method,
- headers: {
- "content-type": "application/json",
- authorization: `Bearer ${process.env.ANALYTICS_SERVICE_API_KEY}`,
- },
- body: req.body,
- });
-};
-
-export default handler;
diff --git a/apps/dashboard/src/pages/api/server-proxy/api/[...paths].tsx b/apps/dashboard/src/pages/api/server-proxy/api/[...paths].tsx
deleted file mode 100644
index df4c31e96e7..00000000000
--- a/apps/dashboard/src/pages/api/server-proxy/api/[...paths].tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import { COOKIE_ACTIVE_ACCOUNT, COOKIE_PREFIX_TOKEN } from "@/constants/cookie";
-import { API_SERVER_URL } from "@/constants/env";
-import type { NextRequest } from "next/server";
-import { getAddress } from "thirdweb";
-
-export const config = {
- runtime: "edge",
-};
-const handler = async (req: NextRequest) => {
- const activeAccount = req.cookies.get(COOKIE_ACTIVE_ACCOUNT)?.value;
- const authToken = activeAccount
- ? req.cookies.get(COOKIE_PREFIX_TOKEN + getAddress(activeAccount))?.value
- : null;
-
- // get the full path from the request including query params
- let pathname = req.nextUrl.pathname;
-
- // remove the /api/server-proxy prefix
- pathname = pathname.replace(/^\/api\/server-proxy\/api/, "");
-
- const searchParams = req.nextUrl.searchParams;
- searchParams.delete("paths");
-
- // create a new URL object for the API server
- const url = new URL(API_SERVER_URL);
- url.pathname = pathname;
- searchParams.forEach((value, key) => {
- url.searchParams.append(key, value);
- });
-
- return fetch(url, {
- method: req.method,
- headers: {
- "content-type": "application/json",
- // pass the auth token if we have one
- ...(authToken ? { authorization: `Bearer ${authToken}` } : {}),
- },
- body: req.body,
- });
-};
-
-export default handler;
diff --git a/apps/dashboard/src/pages/api/server-proxy/pay/[...paths].tsx b/apps/dashboard/src/pages/api/server-proxy/pay/[...paths].tsx
deleted file mode 100644
index 2f6d88fa828..00000000000
--- a/apps/dashboard/src/pages/api/server-proxy/pay/[...paths].tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-import { COOKIE_ACTIVE_ACCOUNT, COOKIE_PREFIX_TOKEN } from "@/constants/cookie";
-import type { NextRequest } from "next/server";
-import { getAddress } from "thirdweb";
-
-export const config = {
- runtime: "edge",
-};
-const handler = async (req: NextRequest) => {
- const activeAccount = req.cookies.get(COOKIE_ACTIVE_ACCOUNT)?.value;
- const authToken = activeAccount
- ? req.cookies.get(COOKIE_PREFIX_TOKEN + getAddress(activeAccount))?.value
- : null;
-
- // get the full path from the request including query params
- let pathname = req.nextUrl.pathname;
-
- // remove the /api/server-proxy prefix
- pathname = pathname.replace(/^\/api\/server-proxy\/pay/, "");
-
- const searchParams = req.nextUrl.searchParams;
- searchParams.delete("paths");
-
- // create a new URL object for the API server
- const PAY_SERVER_URL = new URL(
- process.env.NEXT_PUBLIC_PAY_URL
- ? `https://${process.env.NEXT_PUBLIC_PAY_URL}`
- : "https://pay.thirdweb-dev.com",
- );
- PAY_SERVER_URL.pathname = pathname;
- searchParams.forEach((value, key) => {
- PAY_SERVER_URL.searchParams.append(key, value);
- });
-
- return fetch(PAY_SERVER_URL, {
- method: req.method,
- headers: {
- "content-type": "application/json",
- // pass the auth token if we have one
- ...(authToken ? { authorization: `Bearer ${authToken}` } : {}),
- },
- body: req.body,
- });
-};
-
-export default handler;
diff --git a/apps/dashboard/src/pages/api/wallet/nfts/[chainId].ts b/apps/dashboard/src/pages/api/wallet/nfts/[chainId].ts
deleted file mode 100644
index efcac10efce..00000000000
--- a/apps/dashboard/src/pages/api/wallet/nfts/[chainId].ts
+++ /dev/null
@@ -1,133 +0,0 @@
-import {
- generateAlchemyUrl,
- isAlchemySupported,
- transformAlchemyResponseToNFT,
-} from "lib/wallet/nfts/alchemy";
-import {
- generateMoralisUrl,
- isMoralisSupported,
- transformMoralisResponseToNFT,
-} from "lib/wallet/nfts/moralis";
-import {
- generateSimpleHashUrl,
- isSimpleHashSupported,
- transformSimpleHashResponseToNFT,
-} from "lib/wallet/nfts/simpleHash";
-import type { WalletNFT } from "lib/wallet/nfts/types";
-import type { NextApiRequest, NextApiResponse } from "next";
-import { getSingleQueryValue } from "utils/router";
-
-export type WalletNFTApiReturn =
- | { result: WalletNFT[]; error?: never }
- | { result?: never; error: string };
-
-const handler = async (
- req: NextApiRequest,
- res: NextApiResponse,
-) => {
- if (req.method !== "GET") {
- return res.status(400).json({ error: "invalid method" });
- }
- const queryChainId = getSingleQueryValue(req.query, "chainId");
- const owner = getSingleQueryValue(req.query, "owner");
- if (!queryChainId) {
- return res.status(400).json({ error: "missing chainId" });
- }
- if (!owner) {
- return res.status(400).json({ error: "missing owner" });
- }
- const chainId = Number.parseInt(queryChainId);
-
- const supportedChainSlug = await isSimpleHashSupported(chainId);
-
- if (supportedChainSlug && process.env.SIMPLEHASH_API_KEY) {
- const url = generateSimpleHashUrl({ chainSlug: supportedChainSlug, owner });
-
- const options = {
- method: "GET",
- headers: {
- "X-API-KEY": process.env.SIMPLEHASH_API_KEY,
- },
- };
- const response = await fetch(url, options);
-
- if (response.status >= 400) {
- return res.status(response.status).json({ error: response.statusText });
- }
- try {
- const parsedResponse = await response.json();
- const result = await transformSimpleHashResponseToNFT(
- parsedResponse,
- owner,
- );
-
- res.setHeader(
- "Cache-Control",
- "public, s-maxage=10, stale-while-revalidate=59",
- );
- return res.status(200).json({ result });
- } catch (err) {
- console.error("Error fetching NFTs", err);
- return res.status(500).json({ error: "error parsing response" });
- }
- }
-
- if (isAlchemySupported(chainId)) {
- const url = generateAlchemyUrl({ chainId, owner });
-
- const response = await fetch(url);
- if (response.status >= 400) {
- return res.status(response.status).json({ error: response.statusText });
- }
- try {
- const parsedResponse = await response.json();
- const result = await transformAlchemyResponseToNFT(parsedResponse, owner);
-
- res.setHeader(
- "Cache-Control",
- "public, s-maxage=10, stale-while-revalidate=59",
- );
- return res.status(200).json({ result });
- } catch (err) {
- console.error("Error fetching NFTs", err);
- return res.status(500).json({ error: "error parsing response" });
- }
- }
-
- if (isMoralisSupported(chainId) && process.env.MORALIS_API_KEY) {
- const url = generateMoralisUrl({ chainId, owner });
-
- const options = {
- method: "GET",
- headers: {
- "X-API-Key": process.env.MORALIS_API_KEY,
- },
- };
- const response = await fetch(url, options);
-
- if (response.status >= 400) {
- return res.status(response.status).json({ error: response.statusText });
- }
-
- try {
- const parsedResponse = await response.json();
- const result = await transformMoralisResponseToNFT(
- await parsedResponse,
- owner,
- );
-
- res.setHeader(
- "Cache-Control",
- "public, s-maxage=10, stale-while-revalidate=59",
- );
- return res.status(200).json({ result });
- } catch (err) {
- console.error("Error fetching NFTs", err);
- return res.status(500).json({ error: "error parsing response" });
- }
- }
-
- return res.status(400).json({ error: "unsupported chain" });
-};
-
-export default handler;
diff --git a/apps/dashboard/src/pages/auth.tsx b/apps/dashboard/src/pages/auth.tsx
deleted file mode 100644
index 36bd15a38a7..00000000000
--- a/apps/dashboard/src/pages/auth.tsx
+++ /dev/null
@@ -1,202 +0,0 @@
-import { Container, Flex } from "@chakra-ui/react";
-import { LandingEndCTA } from "components/landing-pages/end-cta";
-import { LandingGridSection } from "components/landing-pages/grid-section";
-import { LandingGuidesShowcase } from "components/landing-pages/guide-showcase";
-import { LandingHeroWithSideImage } from "components/landing-pages/hero-with-side-image";
-import { LandingIconSectionItem } from "components/landing-pages/icon-section-item";
-import { LandingLayout } from "components/landing-pages/layout";
-import { LandingSectionHeading } from "components/landing-pages/section-heading";
-import { getAbsoluteUrl } from "lib/vercel-utils";
-import { PageId } from "page-id";
-import { Card, TrackedLink } from "tw-components";
-import type { ThirdwebNextPage } from "utils/types";
-
-const TRACKING_CATEGORY = "auth-landing";
-
-const GUIDES = [
- {
- title: "How to Build a Web3 Creator Platform with a Web2 Backend",
- image:
- "https://blog.thirdweb.com/content/images/size/w2000/2023/03/How-to-create-a-web3-creator----platform-with-a-web2-backend.png",
- link: "https://blog.thirdweb.com/guides/how-to-create-a-web3-creator-platform/",
- },
- {
- title: "Create An NFT Gated Website",
- image:
- "https://blog.thirdweb.com/content/images/size/w2000/2022/08/thumbnail-31.png",
- link: "https://blog.thirdweb.com/guides/nft-gated-website/",
- },
- {
- title: "Accept Stripe Subscription Payments For Your Web3 App",
- image:
- "https://blog.thirdweb.com/content/images/size/w2000/2023/03/Add-stripe-subscriptions--with-web3-auth-2.png",
- link: "https://blog.thirdweb.com/guides/add-stripe-subscriptions-with-web3-auth/",
- },
-];
-
-const AuthLanding: ThirdwebNextPage = () => {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
- Built on the SIWE (
-
- Sign-in with Ethereum
-
- ) standard. Securely verify a user's on-chain identity,
- without relying on a centralized database to verify their
- identity.
- >
- }
- />
-
-
-
-
-
-
- Secure your backend with a web3-compatible authentication
- system compliant with the widely used{" "}
-
- JSON Web Token
- {" "}
- standard.
- >
- }
- />
-
-
-
-
- }
- >
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-};
-
-AuthLanding.pageId = PageId.AuthLanding;
-
-export default AuthLanding;
diff --git a/apps/dashboard/src/pages/in-app-wallets.tsx b/apps/dashboard/src/pages/in-app-wallets.tsx
deleted file mode 100644
index ef08de0cca4..00000000000
--- a/apps/dashboard/src/pages/in-app-wallets.tsx
+++ /dev/null
@@ -1,255 +0,0 @@
-import { Container, Flex } from "@chakra-ui/react";
-import { ChakraNextImage } from "components/Image";
-import { LandingCardWithImage } from "components/landing-pages/card-with-image";
-import { LandingDynamicSelector } from "components/landing-pages/dynamic-selector";
-import { LandingEndCTA } from "components/landing-pages/end-cta";
-import { LandingGridSection } from "components/landing-pages/grid-section";
-import { LandingGuidesShowcase } from "components/landing-pages/guide-showcase";
-import { LandingHeroWithSideImage } from "components/landing-pages/hero-with-side-image";
-import { LandingLayout } from "components/landing-pages/layout";
-import { getAbsoluteUrl } from "lib/vercel-utils";
-import { PageId } from "page-id";
-import { Heading } from "tw-components";
-import type { ThirdwebNextPage } from "utils/types";
-
-const TRACKING_CATEGORY = "embedded-wallets-landing";
-
-const GUIDES = [
- {
- title: "Docs: In-App Wallets Overview",
- image: require("../../public/assets/product-pages/embedded-wallets/embedded-wallet.png"),
- link: "https://portal.thirdweb.com/connect/in-app-wallet/overview",
- },
- {
- title: "Live Demo: In-App Wallets",
- image: require("../../public/assets/product-pages/embedded-wallets/paper.png"),
- link: "https://catattack.thirdweb.com",
- },
- {
- title: "Quick-Start Template: In-App Wallet + Account Abstraction",
- image: require("../../public/assets/product-pages/smart-wallet/get-started.png"),
- link: "https://github.com/thirdweb-example/embedded-smart-wallet",
- },
-];
-
-const EmbeddedWalletsLanding: ThirdwebNextPage = () => {
- return (
-
-
-
-
-
- ),
- },
- {
- title: "Integrate with your own custom auth",
- description:
- "Spin up in-app wallets for your users with your app or game's existing auth system.",
- Component: (
-
- ),
- },
- {
- title: "Cross-platform support",
- description:
- "Enable users to log into their accounts (and access their wallets) from any device, in one click. Support for web, mobile, & Unity.",
- Component: (
-
- ),
- },
- ]}
- />
-
-
-
- Abstract away complexity for your users
-
-
- }
- >
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-};
-
-EmbeddedWalletsLanding.pageId = PageId.EmbeddedWalletsLanding;
-
-export default EmbeddedWalletsLanding;
diff --git a/apps/dashboard/src/theme/index.ts b/apps/dashboard/src/theme/index.ts
index 9347f26f23f..95803f8a4fe 100644
--- a/apps/dashboard/src/theme/index.ts
+++ b/apps/dashboard/src/theme/index.ts
@@ -1,3 +1,5 @@
+"use client";
+
import { type Theme, extendTheme } from "@chakra-ui/react";
import { getColor, mode } from "@chakra-ui/theme-tools";
import { skeletonTheme } from "./chakra-componens/skeleton";
diff --git a/apps/dashboard/src/tw-components/button.tsx b/apps/dashboard/src/tw-components/button.tsx
index d32d3feaa86..345f163e70a 100644
--- a/apps/dashboard/src/tw-components/button.tsx
+++ b/apps/dashboard/src/tw-components/button.tsx
@@ -1,3 +1,5 @@
+"use client";
+
import {
Button as ChakraButton,
type ButtonProps as ChakraButtonProps,
diff --git a/apps/dashboard/src/utils/router.ts b/apps/dashboard/src/utils/router.ts
deleted file mode 100644
index c863c80c1a0..00000000000
--- a/apps/dashboard/src/utils/router.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import type { ParsedUrlQuery } from "node:querystring";
-
-/**
- * @deprecated use `SearchParams` instead
- */
-export function getSingleQueryValue(
- query: ParsedUrlQuery,
- key: string,
-): string | undefined {
- const _val = query[key];
-
- return Array.isArray(_val) ? _val[0] : _val;
-}
diff --git a/apps/dashboard/src/utils/types.ts b/apps/dashboard/src/utils/types.ts
deleted file mode 100644
index 0de2b28e85c..00000000000
--- a/apps/dashboard/src/utils/types.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import type { NextPage } from "next";
-import type { PageId } from "page-id";
-import type { ReactElement, ReactNode } from "react";
-
-// biome-ignore lint/suspicious/noExplicitAny: FIXME
-export type ThirdwebNextPage = NextPage
& {
- // biome-ignore lint/suspicious/noExplicitAny: FIXME
- getLayout?: (page: ReactElement, pageProps?: any) => ReactNode;
- // biome-ignore lint/suspicious/noExplicitAny: FIXME
- pageId: PageId | ((pageProps: any) => PageId);
- fallback?: React.ReactNode;
-};
-
-/**
- * Makes a parameter required to be passed, but still allows it to be null or undefined.
- * @internal
- */
-export type RequiredParam = T | null | undefined;
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 06ca6ea8e62..b6bdee49e3a 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -205,9 +205,6 @@ importers:
next-plausible:
specifier: ^3.12.4
version: 3.12.4(next@15.1.3(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
- next-seo:
- specifier: ^6.5.0
- version: 6.6.0(next@15.1.3(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
next-themes:
specifier: ^0.4.4
version: 0.4.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
@@ -10500,13 +10497,6 @@ packages:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
- next-seo@6.6.0:
- resolution: {integrity: sha512-0VSted/W6XNtgAtH3D+BZrMLLudqfm0D5DYNJRXHcDgan/1ZF1tDFIsWrmvQlYngALyphPfZ3ZdOqlKpKdvG6w==}
- peerDependencies:
- next: ^8.1.1-canary.54 || >=9.0.0
- react: '>=16.0.0'
- react-dom: '>=16.0.0'
-
next-sitemap@4.2.3:
resolution: {integrity: sha512-vjdCxeDuWDzldhCnyFCQipw5bfpl4HmZA7uoo3GAaYGjGgfL4Cxb1CiztPuWGmS+auYs7/8OekRS8C2cjdAsjQ==}
engines: {node: '>=14.18'}
@@ -26957,12 +26947,6 @@ snapshots:
react: 19.0.0
react-dom: 19.0.0(react@19.0.0)
- next-seo@6.6.0(next@15.1.3(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
- dependencies:
- next: 15.1.3(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
- react: 19.0.0
- react-dom: 19.0.0(react@19.0.0)
-
next-sitemap@4.2.3(next@15.1.3(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)):
dependencies:
'@corex/deepmerge': 4.0.43