Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions apps/dashboard/src/@/constants/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@ export const THIRDWEB_ACCESS_TOKEN = process.env.THIRDWEB_ACCESS_TOKEN;
// Comma-separated list of chain IDs to disable faucet for.
export const DISABLE_FAUCET_CHAIN_IDS = process.env.DISABLE_FAUCET_CHAIN_IDS;

export const BASE_URL = isProd
? "https://thirdweb.com"
: (process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL
? `https://${process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL}`
: "http://localhost:3000") || "https://thirdweb-dev.com";

export function getAbsoluteUrlFromPath(path: string) {
const url = new URL(
isProd
? "https://thirdweb.com"
: (process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL
? `https://${process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL}`
: "http://localhost:3000") || "https://thirdweb-dev.com",
);
const url = new URL(BASE_URL);

url.pathname = path;
return url;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { CreateEcosystemForm } from "./components/client/create-ecosystem-form.client";
import { EcosystemWalletPricingCard } from "./components/pricing-card";

export function EcosystemCreatePage(props: {
ecosystemLayoutPath: string;
}) {
export async function EcosystemCreatePage(props: { teamSlug: string }) {
return (
<div className="flex w-full flex-col gap-6 md:mx-auto md:max-w-lg lg:max-w-4xl">
<header className="flex flex-col gap-1">
Expand All @@ -19,9 +17,7 @@ export function EcosystemCreatePage(props: {
<EcosystemWalletPricingCard />
</section>
<section className="mb-12 lg:px-4">
<CreateEcosystemForm
ecosystemLayoutPath={props.ecosystemLayoutPath}
/>
<CreateEcosystemForm teamSlug={props.teamSlug} />
</section>
</main>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"use server";
import "server-only";
import { API_SERVER_URL, BASE_URL } from "@/constants/env";
import { getThirdwebClient } from "@/constants/thirdweb.server";
import { redirect } from "next/navigation";
import { upload } from "thirdweb/storage";
import { getAuthToken } from "../../../../../../../api/lib/getAuthToken";

export async function createEcosystem(options: {
teamSlug: string;
name: string;
logo: File;
permission: "PARTNER_WHITELIST" | "ANYONE";
}) {
const token = await getAuthToken();
if (!token) {
return {
status: 401,
};
}

const { teamSlug, logo, ...data } = options;

const imageUrl = await upload({
client: getThirdwebClient(token),
files: [logo],
});

const res = await fetch(
`${API_SERVER_URL}/v1/teams/${teamSlug}/checkout/create-link`,
{
method: "POST",
body: JSON.stringify({
baseUrl: BASE_URL,
sku: "product:ecosystem_wallets",
metadata: {
...data,
imageUrl,
// not something we pass in today during creation, but required to be there
authOptions: [],
},
}),
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
},
);
if (!res.ok) {
return {
status: res.status,
};
}

const json = await res.json();

if (!json.result) {
return {
status: 500,
};
}

// redirect to the stripe billing portal
redirect(json.result);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
"use client";
import { ConfirmationDialog } from "@/components/ui/ConfirmationDialog";
import { Button } from "@/components/ui/button";
import {
Form,
Expand All @@ -14,16 +13,13 @@ import {
import { ImageUpload } from "@/components/ui/image-upload";
import { Input } from "@/components/ui/input";
import { RadioGroup, RadioGroupItemButton } from "@/components/ui/radio-group";
import { useDashboardRouter } from "@/lib/DashboardRouter";
import { zodResolver } from "@hookform/resolvers/zod";
import { Loader2 } from "lucide-react";
import Link from "next/link";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import invariant from "tiny-invariant";
import { z } from "zod";
import { useCreateEcosystem } from "../../hooks/use-create-ecosystem";
import { createEcosystem } from "../../actions/create-ecosystem";

const formSchema = z.object({
name: z
Expand All @@ -40,41 +36,47 @@ const formSchema = z.object({
permission: z.union([z.literal("PARTNER_WHITELIST"), z.literal("ANYONE")]),
});

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<typeof formSchema> | undefined
>();

const router = useDashboardRouter();
export function CreateEcosystemForm(props: { teamSlug: string }) {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
permission: "PARTNER_WHITELIST",
},
});

const { mutateAsync: createEcosystem, isPending } = useCreateEcosystem({
onError: (error) => {
const message =
error instanceof Error ? error.message : "Failed to create ecosystem";
toast.error(message);
},
onSuccess: (slug: string) => {
form.reset();
router.push(`${props.ecosystemLayoutPath}/${slug}`);
},
});

return (
<>
<Form {...form}>
<form
onSubmit={form.handleSubmit((values) =>
setFormDataToBeConfirmed(values),
)}
onSubmit={form.handleSubmit(async (values) => {
const res = await createEcosystem({
teamSlug: props.teamSlug,
...values,
});
switch (res.status) {
case 401: {
toast.error("Please login to create an ecosystem");
break;
}
case 403:
{
toast.error(
"You are not authorized to create an ecosystem, please ask your team owner to create it.",
);
}
break;
case 409: {
toast.error("An ecosystem with that name already exists.");
break;
}
// any other status code treat as a random failure
default: {
toast.error(
"Failed to create ecosystem, please try again later.",
);
}
}
})}
className="flex flex-col items-stretch gap-8"
>
<div className="grid gap-6">
Expand Down Expand Up @@ -166,29 +168,15 @@ export function CreateEcosystemForm(props: {
type="submit"
variant="primary"
className="w-full"
disabled={isPending}
disabled={form.formState.isSubmitting}
>
{isPending && <Loader2 className="mr-1 h-4 w-4 animate-spin" />}
{form.formState.isSubmitting && (
<Loader2 className="mr-1 h-4 w-4 animate-spin" />
)}
Create
</Button>
</form>
</Form>
<ConfirmationDialog
open={formDataToBeConfirmed !== undefined}
onOpenChange={(open) =>
!open ? setFormDataToBeConfirmed(undefined) : null
}
title={`Are you sure you want to create ecosystem ${form.getValues().name}?`}
description="Your account will be charged $250 per month."
onSubmit={() => {
invariant(formDataToBeConfirmed, "Form data not found");
createEcosystem({
name: formDataToBeConfirmed.name,
logo: formDataToBeConfirmed.logo,
permission: formDataToBeConfirmed.permission,
});
}}
/>
</>
);
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,5 @@ export default async function Page(props: {
params: Promise<{ team_slug: string }>;
}) {
const { team_slug } = await props.params;
return (
<EcosystemCreatePage
ecosystemLayoutPath={`/team/${team_slug}/~/ecosystem`}
/>
);
return <EcosystemCreatePage teamSlug={team_slug} />;
}
Loading