Skip to content

Commit d9d657e

Browse files
committed
[Dashboard] Feature: Integrate ecosystem creation with Stripe billing
1 parent d98c133 commit d9d657e

File tree

6 files changed

+105
-151
lines changed

6 files changed

+105
-151
lines changed

apps/dashboard/src/@/constants/env.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,14 @@ export const THIRDWEB_ACCESS_TOKEN = process.env.THIRDWEB_ACCESS_TOKEN;
3434
// Comma-separated list of chain IDs to disable faucet for.
3535
export const DISABLE_FAUCET_CHAIN_IDS = process.env.DISABLE_FAUCET_CHAIN_IDS;
3636

37+
export const BASE_URL = isProd
38+
? "https://thirdweb.com"
39+
: (process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL
40+
? `https://${process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL}`
41+
: "http://localhost:3000") || "https://thirdweb-dev.com";
42+
3743
export function getAbsoluteUrlFromPath(path: string) {
38-
const url = new URL(
39-
isProd
40-
? "https://thirdweb.com"
41-
: (process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL
42-
? `https://${process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL}`
43-
: "http://localhost:3000") || "https://thirdweb-dev.com",
44-
);
44+
const url = new URL(BASE_URL);
4545

4646
url.pathname = path;
4747
return url;

apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/create/EcosystemCreatePage.tsx

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import { CreateEcosystemForm } from "./components/client/create-ecosystem-form.client";
22
import { EcosystemWalletPricingCard } from "./components/pricing-card";
33

4-
export function EcosystemCreatePage(props: {
5-
ecosystemLayoutPath: string;
6-
}) {
4+
export async function EcosystemCreatePage(props: { teamSlug: string }) {
75
return (
86
<div className="flex w-full flex-col gap-6 md:mx-auto md:max-w-lg lg:max-w-4xl">
97
<header className="flex flex-col gap-1">
@@ -19,9 +17,7 @@ export function EcosystemCreatePage(props: {
1917
<EcosystemWalletPricingCard />
2018
</section>
2119
<section className="mb-12 lg:px-4">
22-
<CreateEcosystemForm
23-
ecosystemLayoutPath={props.ecosystemLayoutPath}
24-
/>
20+
<CreateEcosystemForm teamSlug={props.teamSlug} />
2521
</section>
2622
</main>
2723
</div>
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
"use server";
2+
import "server-only";
3+
import { API_SERVER_URL, BASE_URL } from "@/constants/env";
4+
import { getThirdwebClient } from "@/constants/thirdweb.server";
5+
import { redirect } from "next/navigation";
6+
import { upload } from "thirdweb/storage";
7+
import { getAuthToken } from "../../../../../../../api/lib/getAuthToken";
8+
9+
export async function createEcosystem(options: {
10+
teamSlug: string;
11+
name: string;
12+
logo: File;
13+
permission: "PARTNER_WHITELIST" | "ANYONE";
14+
}) {
15+
const token = await getAuthToken();
16+
if (!token) {
17+
return {
18+
status: 401,
19+
};
20+
}
21+
22+
const { teamSlug, logo, ...data } = options;
23+
24+
const imageUrl = await upload({
25+
client: getThirdwebClient(token),
26+
files: [logo],
27+
});
28+
29+
const res = await fetch(
30+
`${API_SERVER_URL}/v1/teams/${teamSlug}/checkout/create-link`,
31+
{
32+
method: "POST",
33+
body: JSON.stringify({
34+
baseUrl: BASE_URL,
35+
sku: "product:ecosystem_wallets",
36+
metadata: {
37+
...data,
38+
imageUrl,
39+
// not something we pass in today during creation, but required to be there
40+
authOptions: [],
41+
},
42+
}),
43+
headers: {
44+
"Content-Type": "application/json",
45+
Authorization: `Bearer ${token}`,
46+
},
47+
},
48+
);
49+
if (!res.ok) {
50+
return {
51+
status: res.status,
52+
};
53+
}
54+
55+
const json = await res.json();
56+
57+
if (!json.result) {
58+
return {
59+
status: 500,
60+
};
61+
}
62+
63+
// redirect to the stripe billing portal
64+
redirect(json.result);
65+
}

apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/create/components/client/create-ecosystem-form.client.tsx

Lines changed: 30 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
"use client";
2-
import { ConfirmationDialog } from "@/components/ui/ConfirmationDialog";
32
import { Button } from "@/components/ui/button";
43
import {
54
Form,
@@ -14,16 +13,13 @@ import {
1413
import { ImageUpload } from "@/components/ui/image-upload";
1514
import { Input } from "@/components/ui/input";
1615
import { RadioGroup, RadioGroupItemButton } from "@/components/ui/radio-group";
17-
import { useDashboardRouter } from "@/lib/DashboardRouter";
1816
import { zodResolver } from "@hookform/resolvers/zod";
1917
import { Loader2 } from "lucide-react";
2018
import Link from "next/link";
21-
import { useState } from "react";
2219
import { useForm } from "react-hook-form";
2320
import { toast } from "sonner";
24-
import invariant from "tiny-invariant";
2521
import { z } from "zod";
26-
import { useCreateEcosystem } from "../../hooks/use-create-ecosystem";
22+
import { createEcosystem } from "../../actions/create-ecosystem";
2723

2824
const formSchema = z.object({
2925
name: z
@@ -40,41 +36,42 @@ const formSchema = z.object({
4036
permission: z.union([z.literal("PARTNER_WHITELIST"), z.literal("ANYONE")]),
4137
});
4238

43-
export function CreateEcosystemForm(props: {
44-
ecosystemLayoutPath: string;
45-
}) {
46-
// When set, the confirmation modal is open the this contains the form data to be submitted
47-
const [formDataToBeConfirmed, setFormDataToBeConfirmed] = useState<
48-
z.infer<typeof formSchema> | undefined
49-
>();
50-
51-
const router = useDashboardRouter();
39+
export function CreateEcosystemForm(props: { teamSlug: string }) {
5240
const form = useForm<z.infer<typeof formSchema>>({
5341
resolver: zodResolver(formSchema),
5442
defaultValues: {
5543
permission: "PARTNER_WHITELIST",
5644
},
5745
});
5846

59-
const { mutateAsync: createEcosystem, isPending } = useCreateEcosystem({
60-
onError: (error) => {
61-
const message =
62-
error instanceof Error ? error.message : "Failed to create ecosystem";
63-
toast.error(message);
64-
},
65-
onSuccess: (slug: string) => {
66-
form.reset();
67-
router.push(`${props.ecosystemLayoutPath}/${slug}`);
68-
},
69-
});
70-
7147
return (
7248
<>
7349
<Form {...form}>
7450
<form
75-
onSubmit={form.handleSubmit((values) =>
76-
setFormDataToBeConfirmed(values),
77-
)}
51+
onSubmit={form.handleSubmit(async (values) => {
52+
const res = await createEcosystem({
53+
teamSlug: props.teamSlug,
54+
...values,
55+
});
56+
switch (res.status) {
57+
case 401:
58+
toast.error("Please login to create an ecosystem");
59+
break;
60+
case 403:
61+
toast.error(
62+
"You are not authorized to create an ecosystem, please ask your team owner to create it.",
63+
);
64+
break;
65+
case 409:
66+
toast.error("An ecosystem with that name already exists.");
67+
break;
68+
case 500:
69+
toast.error(
70+
"Failed to create ecosystem, please try again later.",
71+
);
72+
break;
73+
}
74+
})}
7875
className="flex flex-col items-stretch gap-8"
7976
>
8077
<div className="grid gap-6">
@@ -166,29 +163,15 @@ export function CreateEcosystemForm(props: {
166163
type="submit"
167164
variant="primary"
168165
className="w-full"
169-
disabled={isPending}
166+
disabled={form.formState.isSubmitting}
170167
>
171-
{isPending && <Loader2 className="mr-1 h-4 w-4 animate-spin" />}
168+
{form.formState.isSubmitting && (
169+
<Loader2 className="mr-1 h-4 w-4 animate-spin" />
170+
)}
172171
Create
173172
</Button>
174173
</form>
175174
</Form>
176-
<ConfirmationDialog
177-
open={formDataToBeConfirmed !== undefined}
178-
onOpenChange={(open) =>
179-
!open ? setFormDataToBeConfirmed(undefined) : null
180-
}
181-
title={`Are you sure you want to create ecosystem ${form.getValues().name}?`}
182-
description="Your account will be charged $250 per month."
183-
onSubmit={() => {
184-
invariant(formDataToBeConfirmed, "Form data not found");
185-
createEcosystem({
186-
name: formDataToBeConfirmed.name,
187-
logo: formDataToBeConfirmed.logo,
188-
permission: formDataToBeConfirmed.permission,
189-
});
190-
}}
191-
/>
192175
</>
193176
);
194177
}

apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/create/hooks/use-create-ecosystem.ts

Lines changed: 0 additions & 86 deletions
This file was deleted.

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

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,5 @@ export default async function Page(props: {
44
params: Promise<{ team_slug: string }>;
55
}) {
66
const { team_slug } = await props.params;
7-
return (
8-
<EcosystemCreatePage
9-
ecosystemLayoutPath={`/team/${team_slug}/~/ecosystem`}
10-
/>
11-
);
7+
return <EcosystemCreatePage teamSlug={team_slug} />;
128
}

0 commit comments

Comments
 (0)