From a9e1e00c55c45110e9ed4230c6e9832704841de8 Mon Sep 17 00:00:00 2001 From: Manan Tank Date: Tue, 10 Sep 2024 02:59:32 +0530 Subject: [PATCH] Add Ecosystem wallet pages in teams --- .../ecosystem/EcosystemLandingPage.tsx | 99 ++++++++++++++++ .../[slug]/(active)/EcosystemSlugLayout.tsx | 42 +++++++ .../client/ecosystem-header.client.tsx | 21 +++- .../ecosystem/[slug]/(active)/layout.tsx | 50 +------- .../permissions/EcosystemPermissionsPage.tsx | 19 +++ .../[slug]/(active)/permissions/page.tsx | 16 +-- .../ecosystem/create/EcosystemCreatePage.tsx | 29 +++++ .../client/create-ecosystem-form.client.tsx | 6 +- .../connect/ecosystem/create/page.tsx | 22 +--- .../dashboard/connect/ecosystem/page.tsx | 112 +----------------- .../connect/ecosystem/utils/fetchEcosystem.ts | 20 ++++ .../ecosystem/utils/fetchEcosystemList.ts | 21 ++++ .../analytics/_components/EmptyChartState.tsx | 2 +- .../ecosystem/[slug]/(active)/layout.tsx | 19 +++ .../[slug]/(active)/permissions/page.tsx | 5 + .../connect/ecosystem/create/page.tsx | 12 ++ .../[project_slug]/connect/ecosystem/page.tsx | 18 ++- 17 files changed, 311 insertions(+), 202 deletions(-) create mode 100644 apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/EcosystemLandingPage.tsx create mode 100644 apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/EcosystemSlugLayout.tsx create mode 100644 apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/permissions/EcosystemPermissionsPage.tsx create mode 100644 apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/create/EcosystemCreatePage.tsx create mode 100644 apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/utils/fetchEcosystem.ts create mode 100644 apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/utils/fetchEcosystemList.ts create mode 100644 apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/ecosystem/[slug]/(active)/layout.tsx create mode 100644 apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/ecosystem/[slug]/(active)/permissions/page.tsx create mode 100644 apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/ecosystem/create/page.tsx diff --git a/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/EcosystemLandingPage.tsx b/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/EcosystemLandingPage.tsx new file mode 100644 index 00000000000..e7c223a4d2f --- /dev/null +++ b/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/EcosystemLandingPage.tsx @@ -0,0 +1,99 @@ +import { Button } from "@/components/ui/button"; +import { Card } from "@/components/ui/card"; +import { ToolTipLabel } from "@/components/ui/tooltip"; +import { COOKIE_ACTIVE_ACCOUNT, COOKIE_PREFIX_TOKEN } from "@/constants/cookie"; +import { BookMarkedIcon, ExternalLinkIcon, MoveRightIcon } from "lucide-react"; +import { cookies } from "next/headers"; +import Image from "next/image"; +import Link from "next/link"; +import { redirect } from "next/navigation"; +import headerImage from "./assets/header.png"; +import { fetchEcosystemList } from "./utils/fetchEcosystemList"; + +export async function EcosystemLandingPage(props: { + ecosystemLayoutPath: string; +}) { + const cookiesManager = cookies(); + const activeAccount = cookiesManager.get(COOKIE_ACTIVE_ACCOUNT)?.value; + const authToken = activeAccount + ? cookiesManager.get(`${COOKIE_PREFIX_TOKEN}${activeAccount}`)?.value + : null; + + // if user is logged in and has an ecosystem, redirect to the first ecosystem + if (authToken) { + const ecosystems = await fetchEcosystemList(authToken).catch((err) => { + console.error("failed to fetch ecosystems", err); + return []; + }); + if (ecosystems.length > 0) { + redirect(`${props.ecosystemLayoutPath}/${ecosystems[0].slug}`); + } + } + + // otherwise we fall through to the page + + return ( +
+ Ecosystems +
+

+ One wallet, a whole ecosystem of apps and games +

+

+ With Ecosystem Wallets, your users can access their assets across + hundreds of apps and games within your ecosystem. You can control + which apps join your ecosystem and how their users interact with your + wallet. +

+
+
+ {!authToken ? ( + + + + ) : ( + + )} + +
+
+ + +
+ +

Learn more

+
+

+ Learn how to create and manage Ecosystem Wallets in our docs. +

+
+ View Docs +
+
+ +
+ ); +} diff --git a/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/EcosystemSlugLayout.tsx b/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/EcosystemSlugLayout.tsx new file mode 100644 index 00000000000..65e7ab4bbe2 --- /dev/null +++ b/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/EcosystemSlugLayout.tsx @@ -0,0 +1,42 @@ +import { COOKIE_ACTIVE_ACCOUNT, COOKIE_PREFIX_TOKEN } from "@/constants/cookie"; +import { cookies } from "next/headers"; +import { redirect } from "next/navigation"; +import { getAddress } from "thirdweb"; +import { fetchEcosystem } from "../../utils/fetchEcosystem"; +import { EcosystemHeader } from "./components/client/ecosystem-header.client"; + +export async function EcosystemLayoutSlug({ + children, + params, + ecosystemLayoutPath, +}: { + children: React.ReactNode; + params: { slug: string }; + ecosystemLayoutPath: string; +}) { + const cookiesManager = cookies(); + const activeAccount = cookiesManager.get(COOKIE_ACTIVE_ACCOUNT)?.value; + const authToken = activeAccount + ? cookies().get(COOKIE_PREFIX_TOKEN + getAddress(activeAccount))?.value + : null; + + if (!authToken) { + redirect(ecosystemLayoutPath); + } + + const ecosystem = await fetchEcosystem(params.slug, authToken); + + if (!ecosystem) { + redirect(ecosystemLayoutPath); + } + + return ( +
+ + {children} +
+ ); +} diff --git a/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/components/client/ecosystem-header.client.tsx b/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/components/client/ecosystem-header.client.tsx index 8a675607a0e..9934045e075 100644 --- a/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/components/client/ecosystem-header.client.tsx +++ b/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/components/client/ecosystem-header.client.tsx @@ -60,7 +60,10 @@ function EcosystemAlertBanner({ ecosystem }: { ecosystem: Ecosystem }) { } } -function EcosystemSelect(props: { ecosystem: Ecosystem }) { +function EcosystemSelect(props: { + ecosystem: Ecosystem; + ecosystemLayoutPath: string; +}) { const { data: ecosystems, isLoading } = useEcosystemList(); return isLoading ? ( @@ -81,7 +84,7 @@ function EcosystemSelect(props: { ecosystem: Ecosystem }) { {ecosystems?.map((ecosystem) => ( {ecosystem.slug === props.ecosystem.slug && ( @@ -93,7 +96,7 @@ function EcosystemSelect(props: { ecosystem: Ecosystem }) { ))} - +
New Ecosystem
@@ -104,7 +107,10 @@ function EcosystemSelect(props: { ecosystem: Ecosystem }) { ); } -export function EcosystemHeader(props: { ecosystem: Ecosystem }) { +export function EcosystemHeader(props: { + ecosystem: Ecosystem; + ecosystemLayoutPath: string; +}) { const pathname = usePathname(); const { data: fetchedEcosystem } = useEcosystem({ slug: props.ecosystem.slug, @@ -166,14 +172,17 @@ export function EcosystemHeader(props: { ecosystem: Ecosystem }) {
- +
- + {children} - + ); } diff --git a/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/permissions/EcosystemPermissionsPage.tsx b/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/permissions/EcosystemPermissionsPage.tsx new file mode 100644 index 00000000000..97275c2e33f --- /dev/null +++ b/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/permissions/EcosystemPermissionsPage.tsx @@ -0,0 +1,19 @@ +"use client"; +import { EcosystemPartnersSection } from "../components/server/ecosystem-partners-section"; +import { IntegrationPermissionsSection } from "../components/server/integration-permissions-section"; +import { useEcosystem } from "../hooks/use-ecosystem"; + +export function EcosystemPermissionsPage({ + params, +}: { params: { slug: string } }) { + const { ecosystem } = useEcosystem({ slug: params.slug }); + + return ( +
+ + {ecosystem?.permission === "PARTNER_WHITELIST" && ( + + )} +
+ ); +} diff --git a/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/permissions/page.tsx b/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/permissions/page.tsx index 02b129d6872..c49a8d4465c 100644 --- a/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/permissions/page.tsx +++ b/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/permissions/page.tsx @@ -1,17 +1,5 @@ -"use client"; -import { EcosystemPartnersSection } from "../components/server/ecosystem-partners-section"; -import { IntegrationPermissionsSection } from "../components/server/integration-permissions-section"; -import { useEcosystem } from "../hooks/use-ecosystem"; +import { EcosystemPermissionsPage } from "./EcosystemPermissionsPage"; export default function Page({ params }: { params: { slug: string } }) { - const { ecosystem } = useEcosystem({ slug: params.slug }); - - return ( -
- - {ecosystem?.permission === "PARTNER_WHITELIST" && ( - - )} -
- ); + return ; } diff --git a/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/create/EcosystemCreatePage.tsx b/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/create/EcosystemCreatePage.tsx new file mode 100644 index 00000000000..e2a8fa22ea8 --- /dev/null +++ b/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/create/EcosystemCreatePage.tsx @@ -0,0 +1,29 @@ +import { CreateEcosystemForm } from "./components/client/create-ecosystem-form.client"; +import { EcosystemWalletPricingCard } from "./components/pricing-card"; + +export function EcosystemCreatePage(props: { + ecosystemLayoutPath: string; +}) { + return ( +
+
+

+ Create an Ecosystem +

+

+ Create wallets that work across every chain and every app. +

+
+
+
+ +
+
+ +
+
+
+ ); +} diff --git a/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/create/components/client/create-ecosystem-form.client.tsx b/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/create/components/client/create-ecosystem-form.client.tsx index ba0b50d3304..efdad3db4f2 100644 --- a/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/create/components/client/create-ecosystem-form.client.tsx +++ b/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/create/components/client/create-ecosystem-form.client.tsx @@ -42,7 +42,9 @@ const formSchema = z.object({ permission: z.union([z.literal("PARTNER_WHITELIST"), z.literal("ANYONE")]), }); -export function CreateEcosystemForm() { +export function CreateEcosystemForm(props: { + ecosystemLayoutPath: string; +}) { // When set, the confirmation modal is open the this contains the form data to be submitted const [formDataToBeConfirmed, setFormDataToBeConfirmed] = useState< z.infer | undefined @@ -65,7 +67,7 @@ export function CreateEcosystemForm() { }, onSuccess: (slug: string) => { form.reset(); - router.push(`/dashboard/connect/ecosystem/${slug}`); + router.push(`${props.ecosystemLayoutPath}/${slug}`); }, }); diff --git a/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/create/page.tsx b/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/create/page.tsx index 017db068bd6..5005ebc851d 100644 --- a/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/create/page.tsx +++ b/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/create/page.tsx @@ -1,25 +1,7 @@ -import { CreateEcosystemForm } from "./components/client/create-ecosystem-form.client"; -import { EcosystemWalletPricingCard } from "./components/pricing-card"; +import { EcosystemCreatePage } from "./EcosystemCreatePage"; export default function Page() { return ( -
-
-

- Create an Ecosystem -

-

- Create wallets that work across every chain and every app. -

-
-
-
- -
-
- -
-
-
+ ); } diff --git a/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/page.tsx b/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/page.tsx index 18391a2d9ba..8dff0422804 100644 --- a/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/page.tsx +++ b/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/page.tsx @@ -1,116 +1,8 @@ -import { Button } from "@/components/ui/button"; -import { Card } from "@/components/ui/card"; -import { ToolTipLabel } from "@/components/ui/tooltip"; -import { COOKIE_ACTIVE_ACCOUNT, COOKIE_PREFIX_TOKEN } from "@/constants/cookie"; -import { BookMarkedIcon, ExternalLinkIcon, MoveRightIcon } from "lucide-react"; -import { cookies } from "next/headers"; -import Image from "next/image"; -import Link from "next/link"; -import { redirect } from "next/navigation"; -import headerImage from "./assets/header.png"; -import type { Ecosystem } from "./types"; - -async function fetchEcosystemList(authToken: string) { - const res = await fetch( - `${process.env.NEXT_PUBLIC_THIRDWEB_API_HOST || "https://api.thirdweb.com"}/v1/ecosystem-wallet/list`, - { - headers: { - Authorization: `Bearer ${authToken}`, - }, - }, - ); - - if (!res.ok) { - const data = await res.json(); - console.error(data); - throw new Error(data?.error?.message ?? "Failed to fetch ecosystems"); - } - - const data = (await res.json()) as { result: Ecosystem[] }; - return data.result; -} +import { EcosystemLandingPage } from "./EcosystemLandingPage"; export default async function Page() { - const cookiesManager = cookies(); - const activeAccount = cookiesManager.get(COOKIE_ACTIVE_ACCOUNT)?.value; - const authToken = activeAccount - ? cookiesManager.get(`${COOKIE_PREFIX_TOKEN}${activeAccount}`)?.value - : null; - - // if user is logged in and has an ecosystem, redirect to the first ecosystem - if (authToken) { - const ecosystems = await fetchEcosystemList(authToken).catch((err) => { - console.error("failed to fetch ecosystems", err); - return []; - }); - if (ecosystems.length > 0) { - redirect(`/dashboard/connect/ecosystem/${ecosystems[0].slug}`); - } - } - - // otherwise we fall through to the page - return ( -
- Ecosystems -
-

- One wallet, a whole ecosystem of apps and games -

-

- With Ecosystem Wallets, your users can access their assets across - hundreds of apps and games within your ecosystem. You can control - which apps join your ecosystem and how their users interact with your - wallet. -

-
-
- {!authToken ? ( - - - - ) : ( - - - - )} - -
-
- - -
- -

Learn more

-
-

- Learn how to create and manage Ecosystem Wallets in our docs. -

-
- View Docs -
-
- -
+ ); } diff --git a/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/utils/fetchEcosystem.ts b/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/utils/fetchEcosystem.ts new file mode 100644 index 00000000000..3c163e4dbec --- /dev/null +++ b/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/utils/fetchEcosystem.ts @@ -0,0 +1,20 @@ +import type { Ecosystem } from "../types"; + +export async function fetchEcosystem(slug: string, authToken: string) { + const res = await fetch( + `${process.env.NEXT_PUBLIC_THIRDWEB_API_HOST || "https://api.thirdweb.com"}/v1/ecosystem-wallet/${slug}`, + { + headers: { + Authorization: `Bearer ${authToken}`, + }, + }, + ); + if (!res.ok) { + const data = await res.json(); + console.error(data); + return null; + } + + const data = (await res.json()) as { result: Ecosystem }; + return data.result; +} diff --git a/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/utils/fetchEcosystemList.ts b/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/utils/fetchEcosystemList.ts new file mode 100644 index 00000000000..d87600059ac --- /dev/null +++ b/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/utils/fetchEcosystemList.ts @@ -0,0 +1,21 @@ +import type { Ecosystem } from "../types"; + +export async function fetchEcosystemList(authToken: string) { + const res = await fetch( + `${process.env.NEXT_PUBLIC_THIRDWEB_API_HOST || "https://api.thirdweb.com"}/v1/ecosystem-wallet/list`, + { + headers: { + Authorization: `Bearer ${authToken}`, + }, + }, + ); + + if (!res.ok) { + const data = await res.json(); + console.error(data); + throw new Error(data?.error?.message ?? "Failed to fetch ecosystems"); + } + + const data = (await res.json()) as { result: Ecosystem[] }; + return data.result; +} diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/analytics/_components/EmptyChartState.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/analytics/_components/EmptyChartState.tsx index 9ecd5f70498..e85707ceab9 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/analytics/_components/EmptyChartState.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/analytics/_components/EmptyChartState.tsx @@ -26,7 +26,7 @@ export function EmptyChartState() { const barChartData = useMemo(() => generateRandomData(), []); return ( -
+
No data available diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/ecosystem/[slug]/(active)/layout.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/ecosystem/[slug]/(active)/layout.tsx new file mode 100644 index 00000000000..4909a0581e4 --- /dev/null +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/ecosystem/[slug]/(active)/layout.tsx @@ -0,0 +1,19 @@ +import { EcosystemLayoutSlug } from "../../../../../../../(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/EcosystemSlugLayout"; + +export default async function Layout(props: { + params: { team_slug: string; project_slug: string; slug: string }; + children: React.ReactNode; +}) { + const { team_slug, project_slug } = props.params; + return ( + + {props.children} + + ); +} + +// because cookies() is used +export const dynamic = "force-dynamic"; diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/ecosystem/[slug]/(active)/permissions/page.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/ecosystem/[slug]/(active)/permissions/page.tsx new file mode 100644 index 00000000000..5b069d720d7 --- /dev/null +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/ecosystem/[slug]/(active)/permissions/page.tsx @@ -0,0 +1,5 @@ +import { EcosystemPermissionsPage } from "../../../../../../../../(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/permissions/EcosystemPermissionsPage"; + +export default function Page({ params }: { params: { slug: string } }) { + return ; +} diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/ecosystem/create/page.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/ecosystem/create/page.tsx new file mode 100644 index 00000000000..6fada436e52 --- /dev/null +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/ecosystem/create/page.tsx @@ -0,0 +1,12 @@ +import { EcosystemCreatePage } from "../../../../../../(dashboard)/dashboard/connect/ecosystem/create/EcosystemCreatePage"; + +export default function Page(props: { + params: { team_slug: string; project_slug: string }; +}) { + const { team_slug, project_slug } = props.params; + return ( + + ); +} diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/ecosystem/page.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/ecosystem/page.tsx index eefab8e9eac..2bad40cd354 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/ecosystem/page.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/ecosystem/page.tsx @@ -1,9 +1,17 @@ -export default function Page() { +import { EcosystemLandingPage } from "../../../../../(dashboard)/dashboard/connect/ecosystem/EcosystemLandingPage"; + +export default async function Page(props: { + params: { team_slug: string; project_slug: string }; +}) { + const { team_slug, project_slug } = props.params; return ( -
-

- Ecosystem Wallets -

+
+
); } + +// because cookies() is used +export const dynamic = "force-dynamic";