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
17 changes: 12 additions & 5 deletions apps/dashboard/src/app/team/[team_slug]/(team)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getProjects } from "@/api/projects";
import { getTeams } from "@/api/team";
import { getTeamNebulaWaitList, getTeams } from "@/api/team";
import { TabPathLinks } from "@/components/ui/tabs";
import { notFound } from "next/navigation";
import { TeamHeaderLoggedIn } from "../../components/TeamHeader/team-header-logged-in.client";
Expand All @@ -22,6 +22,9 @@ export default async function TeamLayout(props: {
notFound();
}

const isOnNebulaWaitList = (await getTeamNebulaWaitList(team.slug))
?.onWaitlist;

return (
<div className="flex h-full grow flex-col">
<div className="bg-muted/50">
Expand Down Expand Up @@ -55,10 +58,14 @@ export default async function TeamLayout(props: {
path: `/team/${params.team_slug}/~/ecosystem`,
name: "Ecosystems",
},
{
path: `/team/${params.team_slug}/~/nebula`,
name: "Nebula",
},
...(isOnNebulaWaitList
? [
{
path: `/team/${params.team_slug}/~/nebula`,
name: "Nebula",
},
]
: []),
{
path: `/team/${params.team_slug}/~/usage`,
name: "Usage",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,38 +1,16 @@
import { getTeamBySlug, getTeamNebulaWaitList } from "@/api/team";
import { redirect } from "next/navigation";
import { JoinNebulaWaitlistPage } from "../../../[project_slug]/nebula/components/nebula-waitlist-page.client";
import { NebulaWaitListPage } from "../../../[project_slug]/nebula/components/nebula-waitlist-page";

export default async function Page(props: {
params: Promise<{
team_slug: string;
}>;
}) {
const params = await props.params;
const team = await getTeamBySlug(params.team_slug);

if (!team) {
redirect(
`/login?next=${encodeURIComponent(`/team/${params.team_slug}/~/nebula`)}`,
);
}

const nebulaWaitList = await getTeamNebulaWaitList(team.slug);

// this should never happen
if (!nebulaWaitList) {
return (
<div className="container flex grow flex-col py-8">
<div className="flex min-h-[300px] grow flex-col items-center justify-center rounded-lg border p-6 text-destructive-text">
Something went wrong trying to fetch the nebula waitlist
</div>
</div>
);
}

return (
<JoinNebulaWaitlistPage
onWaitlist={nebulaWaitList.onWaitlist}
teamSlug={team.slug}
<NebulaWaitListPage
redirectOnNoTeam={`/login?next=${encodeURIComponent(`/team/${params.team_slug}/~/nebula`)}`}
teamSlug={params.team_slug}
/>
);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getProjects } from "@/api/projects";
import { getTeams } from "@/api/team";
import { getTeamNebulaWaitList, getTeams } from "@/api/team";
import { TabPathLinks } from "@/components/ui/tabs";
import { notFound, redirect } from "next/navigation";
import { TeamHeaderLoggedIn } from "../../components/TeamHeader/team-header-logged-in.client";
Expand Down Expand Up @@ -34,6 +34,9 @@ export default async function TeamLayout(props: {
redirect(`/team/${params.team_slug}`);
}

const isOnNebulaWaitList = (await getTeamNebulaWaitList(team.slug))
?.onWaitlist;

return (
<div className="flex grow flex-col">
<div className="bg-muted/50">
Expand All @@ -58,10 +61,14 @@ export default async function TeamLayout(props: {
path: `/team/${params.team_slug}/${params.project_slug}/contracts`,
name: "Contracts",
},
{
path: `/team/${params.team_slug}/${params.project_slug}/nebula`,
name: "Nebula",
},
...(isOnNebulaWaitList
? [
{
path: `/team/${params.team_slug}/${params.project_slug}/nebula`,
name: "Nebula",
},
]
: []),
{
path: `/team/${params.team_slug}/${params.project_slug}/settings`,
name: "Settings",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,9 @@
"use client";

import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Button } from "@/components/ui/button";
import { ToolTipLabel } from "@/components/ui/tooltip";
import { useDashboardRouter } from "@/lib/DashboardRouter";
import { useMutation } from "@tanstack/react-query";
import { ArrowRightIcon, CheckIcon, OrbitIcon, ShareIcon } from "lucide-react";
import { OrbitIcon } from "lucide-react";
import Link from "next/link";
import { useState } from "react";
import { toast } from "sonner";

export function JoinNebulaWaitlistPageUI(props: {
onWaitlist: boolean;
joinWaitList: () => Promise<void>;
}) {
const router = useDashboardRouter();
import { ShareButton } from "./share-button.client";

export function NebulaWaitListPageUI() {
return (
<div className="flex grow flex-col">
{/* Header */}
Expand All @@ -31,89 +19,16 @@ export function JoinNebulaWaitlistPageUI(props: {
</div>

<div className="container flex grow flex-col overflow-hidden pt-32 pb-48">
{props.onWaitlist ? (
<CenteredCard
key="on-waitlist"
title="You're on the waitlist"
description="You should receive access to Nebula soon!"
footer={<ShareButton />}
/>
) : (
<CenteredCard
key="not-on-waitlist"
title="Nebula"
description="Blockchain-first AI that can read & write onchain in realtime."
footer={
<JoinWaitingListButton
joinWaitList={props.joinWaitList}
onSuccess={() => {
router.refresh();
}}
/>
}
/>
)}
<CenteredCard
title="You're on the waitlist"
description="You should receive access to Nebula soon!"
footer={<ShareButton />}
/>
</div>
</div>
);
}

function ShareButton() {
const [isCopied, setIsCopied] = useState(false);

return (
<ToolTipLabel label="Copy Page Link">
<Button
variant="outline"
className="gap-2"
onClick={() => {
navigator.clipboard.writeText("https://thirdweb.com/team/~/~/nebula");
setIsCopied(true);
setTimeout(() => setIsCopied(false), 1000);
}}
>
Share
{isCopied ? (
<CheckIcon className="size-4 text-green-500" />
) : (
<ShareIcon className="size-4" />
)}
</Button>
</ToolTipLabel>
);
}

function JoinWaitingListButton(props: {
joinWaitList: () => Promise<void>;
onSuccess: () => void;
}) {
const joinWaitListMutation = useMutation({
mutationFn: props.joinWaitList,
onSuccess: props.onSuccess,
});

return (
<Button
className="gap-2 rounded-full"
variant="primary"
onClick={() => {
const promise = joinWaitListMutation.mutateAsync();
toast.promise(promise, {
success: "Joined the waitlist!",
error: "Failed to join waitlist",
});
}}
>
Join the waitlist
{joinWaitListMutation.isPending ? (
<Spinner className="size-4" />
) : (
<ArrowRightIcon className="size-4" />
)}
</Button>
);
}

function CenteredCard(props: {
footer: React.ReactNode;
title: React.ReactNode;
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { joinTeamWaitlist } from "@/actions/joinWaitlist";
import { getTeamBySlug, getTeamNebulaWaitList } from "@/api/team";
import { Button } from "@/components/ui/button";
import Link from "next/link";
import { redirect } from "next/navigation";
import { NebulaWaitListPageUI } from "./nebula-waitlist-page-ui.client";

export async function NebulaWaitListPage(props: {
redirectOnNoTeam: string;
teamSlug: string;
}) {
const team = await getTeamBySlug(props.teamSlug);

if (!team) {
redirect(props.redirectOnNoTeam);
}

const nebulaWaitList = await getTeamNebulaWaitList(team.slug);

// this should never happen
if (!nebulaWaitList) {
return (
<UnexpectedErrorPage message="Failed to get Nebula waitlist status for your team" />
);
}

// if not already on the waitlist, join the waitlist
if (!nebulaWaitList.onWaitlist) {
const joined = await joinTeamWaitlist({
scope: "nebula",
teamSlug: team.slug,
}).catch(() => null);

// this should never happen
if (!joined) {
return (
<UnexpectedErrorPage message="Failed to join Nebula waitlist status for your team" />
);
}
}

return <NebulaWaitListPageUI />;
}

function UnexpectedErrorPage(props: {
message: string;
}) {
return (
<div className="container flex grow flex-col py-10">
<div className="flex min-h-[300px] grow flex-col items-center justify-center rounded-lg border">
<div className="flex flex-col items-center gap-4">
<span className="text-destructive-text">{props.message}</span>
<Button asChild variant="outline">
<Link href="/support">Get Support</Link>
</Button>
</div>
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { Meta, StoryObj } from "@storybook/react";
import { Toaster } from "sonner";
import { mobileViewport } from "stories/utils";
import { JoinNebulaWaitlistPageUI } from "./nebula-waitlist-page-ui.client";
import { NebulaWaitListPageUI } from "./nebula-waitlist-page-ui.client";

const meta = {
title: "nebula/waitlist",
Expand All @@ -16,48 +15,19 @@ const meta = {
export default meta;
type Story = StoryObj<typeof meta>;

export const NotInWaitingListDesktop: Story = {
export const Desktop: Story = {
args: {
inWaitlist: false,
},
};

export const InWaitingListDesktop: Story = {
args: {
inWaitlist: true,
},
};

export const NotInWaitingListMobile: Story = {
args: {
inWaitlist: false,
},
parameters: {
viewport: mobileViewport("iphone14"),
},
};

export const InWaitingListMobile: Story = {
args: {
inWaitlist: true,
},
export const Mobile: Story = {
args: {},
parameters: {
viewport: mobileViewport("iphone14"),
},
};

function Story(props: {
inWaitlist: boolean;
}) {
return (
<>
<JoinNebulaWaitlistPageUI
onWaitlist={props.inWaitlist}
joinWaitList={async () => {
await new Promise((resolve) => setTimeout(resolve, 1500));
}}
/>
<Toaster richColors />
</>
);
function Story() {
return <NebulaWaitListPageUI />;
}
Loading
Loading