diff --git a/apps/dashboard/src/app/account/components/AccountHeader.tsx b/apps/dashboard/src/app/account/components/AccountHeader.tsx index 1b17c008897..98d01c8b840 100644 --- a/apps/dashboard/src/app/account/components/AccountHeader.tsx +++ b/apps/dashboard/src/app/account/components/AccountHeader.tsx @@ -77,7 +77,7 @@ export function AccountHeader(props: { isOpen: false, }) } - onCreateAndComplete={() => { + onCreate={() => { // refresh projects router.refresh(); }} diff --git a/apps/dashboard/src/app/get-started/team/[team_slug]/create-project/page.tsx b/apps/dashboard/src/app/get-started/team/[team_slug]/create-project/page.tsx new file mode 100644 index 00000000000..5df90a7ab30 --- /dev/null +++ b/apps/dashboard/src/app/get-started/team/[team_slug]/create-project/page.tsx @@ -0,0 +1,22 @@ +import { getTeamBySlug } from "@/api/team"; +import { notFound } from "next/navigation"; +import { CreateProjectPage } from "../../../../login/onboarding/create-project-onboarding/create-project-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) { + notFound(); + } + + return ( + + ); +} diff --git a/apps/dashboard/src/app/login/onboarding/create-project-onboarding/CreateProjectPage.stories.tsx b/apps/dashboard/src/app/login/onboarding/create-project-onboarding/CreateProjectPage.stories.tsx new file mode 100644 index 00000000000..465ab679df1 --- /dev/null +++ b/apps/dashboard/src/app/login/onboarding/create-project-onboarding/CreateProjectPage.stories.tsx @@ -0,0 +1,36 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { projectStub } from "../../../../stories/stubs"; +import { CreateProjectPageUI } from "./CreateProjectPageUI"; + +const meta = { + title: "Onboarding/CreateProject", + component: Story, + parameters: { + nextjs: { + appDirectory: true, + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Variants: Story = { + args: {}, +}; + +function Story() { + return ( + { + await new Promise((resolve) => setTimeout(resolve, 1000)); + return { + project: projectStub("foo", "bar"), + secret: "secret", + }; + }} + teamSlug="foo" + enableNebulaServiceByDefault={false} + /> + ); +} diff --git a/apps/dashboard/src/app/login/onboarding/create-project-onboarding/CreateProjectPageUI.tsx b/apps/dashboard/src/app/login/onboarding/create-project-onboarding/CreateProjectPageUI.tsx new file mode 100644 index 00000000000..b3c5a2d8c8b --- /dev/null +++ b/apps/dashboard/src/app/login/onboarding/create-project-onboarding/CreateProjectPageUI.tsx @@ -0,0 +1,51 @@ +"use client"; + +import type { Project } from "@/api/projects"; +import { useState } from "react"; +import { + CreateProjectForm, + CreatedProjectDetails, +} from "../../../../components/settings/ApiKeys/Create"; +import { CreateProjectOnboardingLayout } from "../onboarding-layout"; + +export function CreateProjectPageUI(props: { + createProject: (param: Partial) => Promise<{ + project: Project; + secret: string; + }>; + enableNebulaServiceByDefault: boolean; + teamSlug: string; +}) { + const [screen, setScreen] = useState< + { id: "create" } | { id: "api-details"; project: Project; secret: string } + >({ id: "create" }); + return ( + +
+ {screen.id === "create" && ( + { + setScreen({ + id: "api-details", + project: params.project, + secret: params.secret, + }); + }} + enableNebulaServiceByDefault={props.enableNebulaServiceByDefault} + /> + )} + + {screen.id === "api-details" && ( + + )} +
+
+ ); +} diff --git a/apps/dashboard/src/app/login/onboarding/create-project-onboarding/create-project-page.tsx b/apps/dashboard/src/app/login/onboarding/create-project-onboarding/create-project-page.tsx new file mode 100644 index 00000000000..30132c48846 --- /dev/null +++ b/apps/dashboard/src/app/login/onboarding/create-project-onboarding/create-project-page.tsx @@ -0,0 +1,24 @@ +"use client"; + +import { createProjectClient } from "@3rdweb-sdk/react/hooks/useApi"; +import { CreateProjectPageUI } from "./CreateProjectPageUI"; + +export function CreateProjectPage(props: { + enableNebulaServiceByDefault: boolean; + teamSlug: string; + teamId: string; +}) { + return ( + { + const res = await createProjectClient(props.teamId, params); + return { + project: res.project, + secret: res.secret, + }; + }} + /> + ); +} diff --git a/apps/dashboard/src/app/login/onboarding/onboarding-layout.tsx b/apps/dashboard/src/app/login/onboarding/onboarding-layout.tsx index f530c772447..8de46af4bb4 100644 --- a/apps/dashboard/src/app/login/onboarding/onboarding-layout.tsx +++ b/apps/dashboard/src/app/login/onboarding/onboarding-layout.tsx @@ -4,6 +4,7 @@ import { cn } from "@/lib/utils"; import { useMutation } from "@tanstack/react-query"; import { BoxIcon, + KeyRoundIcon, LogOutIcon, MailIcon, UserIcon, @@ -99,6 +100,36 @@ export function TeamOnboardingLayout(props: { ); } +const createProjectOnboardingSteps: OnboardingStep[] = [ + { + icon: BoxIcon, + title: "Configure Project", + description: "Provide project details", + number: 1, + }, + { + icon: KeyRoundIcon, + title: "Save Keys", + description: "Securely store secret key", + number: 2, + }, +]; + +export function CreateProjectOnboardingLayout(props: { + children: React.ReactNode; + currentStep: 1 | 2; +}) { + return ( + + {props.children} + + ); +} + function OnboardingLayout(props: { steps: OnboardingStep[]; currentStep: number; diff --git a/apps/dashboard/src/app/login/onboarding/team-onboarding/team-onboarding.tsx b/apps/dashboard/src/app/login/onboarding/team-onboarding/team-onboarding.tsx index 80881e0367c..749c14538cf 100644 --- a/apps/dashboard/src/app/login/onboarding/team-onboarding/team-onboarding.tsx +++ b/apps/dashboard/src/app/login/onboarding/team-onboarding/team-onboarding.tsx @@ -104,7 +104,7 @@ export function InviteTeamMembers(props: { { - router.replace(`/team/${props.team.slug}`); + router.replace(`/get-started/team/${props.team.slug}/create-project`); }} getTeam={async () => { const res = await apiServerProxy<{ diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/page.tsx b/apps/dashboard/src/app/team/[team_slug]/(team)/page.tsx index 53481e7db65..744e6b160c0 100644 --- a/apps/dashboard/src/app/team/[team_slug]/(team)/page.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/(team)/page.tsx @@ -6,10 +6,8 @@ import { subDays } from "date-fns"; import { redirect } from "next/navigation"; import { getAuthToken } from "../../../api/lib/getAuthToken"; import { loginRedirect } from "../../../login/loginRedirect"; -import { - type ProjectWithAnalytics, - TeamProjectsPage, -} from "./~/projects/TeamProjectsPage"; +import { TeamProjectsPage } from "./~/projects/TeamProjectsPage"; +import type { ProjectWithAnalytics } from "./~/projects/TeamProjectsTable"; export default async function Page(props: { params: Promise<{ team_slug: string }>; diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/~/projects/TeamProjectsPage.tsx b/apps/dashboard/src/app/team/[team_slug]/(team)/~/projects/TeamProjectsPage.tsx index 8db877b1eac..d8b67acf95c 100644 --- a/apps/dashboard/src/app/team/[team_slug]/(team)/~/projects/TeamProjectsPage.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/(team)/~/projects/TeamProjectsPage.tsx @@ -1,234 +1,38 @@ "use client"; - -import type { Project } from "@/api/projects"; import type { Team } from "@/api/team"; -import { ProjectAvatar } from "@/components/blocks/Avatars/ProjectAvatar"; -import { PaginationButtons } from "@/components/pagination-buttons"; -import { CopyTextButton } from "@/components/ui/CopyTextButton"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, -} from "@/components/ui/select"; -import { - Table, - TableBody, - TableCell, - TableContainer, - TableHead, - TableHeader, - TableRow, -} from "@/components/ui/table"; import { useDashboardRouter } from "@/lib/DashboardRouter"; -import { cn } from "@/lib/utils"; -import { LazyCreateProjectDialog } from "components/settings/ApiKeys/Create/LazyCreateAPIKeyDialog"; -import { formatDate } from "date-fns"; -import { PlusIcon, SearchIcon } from "lucide-react"; -import Link from "next/link"; -import { useMemo, useState } from "react"; +import { useState } from "react"; import type { ThirdwebClient } from "thirdweb"; - -type SortById = "name" | "createdAt" | "monthlyActiveUsers"; - -export type ProjectWithAnalytics = Project & { - monthlyActiveUsers: number; -}; +import { LazyCreateProjectDialog } from "../../../../../../components/settings/ApiKeys/Create/LazyCreateAPIKeyDialog"; +import { + type ProjectWithAnalytics, + TeamProjectsTable, +} from "./TeamProjectsTable"; export function TeamProjectsPage(props: { projects: ProjectWithAnalytics[]; team: Team; client: ThirdwebClient; }) { - const { projects } = props; - const [searchTerm, setSearchTerm] = useState(""); - const [sortBy, setSortBy] = useState("monthlyActiveUsers"); const [isCreateProjectDialogOpen, setIsCreateProjectDialogOpen] = useState(false); const router = useDashboardRouter(); - const sortedProjects = useMemo(() => { - let _projectsToShow = !searchTerm - ? projects - : projects.filter( - (project) => - project.name.toLowerCase().includes(searchTerm.toLowerCase()) || - project.publishableKey - .toLowerCase() - .includes(searchTerm.toLowerCase()), - ); - - if (sortBy === "name") { - _projectsToShow = _projectsToShow.sort((a, b) => - a.name.localeCompare(b.name), - ); - } else if (sortBy === "createdAt") { - _projectsToShow = _projectsToShow.sort( - (a, b) => - new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), - ); - } else if (sortBy === "monthlyActiveUsers") { - _projectsToShow = _projectsToShow.sort( - (a, b) => b.monthlyActiveUsers - a.monthlyActiveUsers, - ); - } - - return _projectsToShow; - }, [searchTerm, sortBy, projects]); - - const pageSize = 10; - const [page, setPage] = useState(1); - const paginatedProjects = sortedProjects.slice( - (page - 1) * pageSize, - page * pageSize, - ); - - const showPagination = sortedProjects.length > pageSize; - const totalPages = Math.ceil(sortedProjects.length / pageSize); - return ( -
-
-

Projects

- - {/* Filters + Add New */} -
- { - setSearchTerm(v); - setPage(1); - }} - /> -
- { - setSortBy(v); - setPage(1); - }} - /> - setIsCreateProjectDialogOpen(true)} - teamMembersSettingsPath={`/team/${props.team.slug}/~/settings/members`} - /> -
-
-
- - {/* Projects Table */} - {paginatedProjects.length === 0 ? ( - <> - {searchTerm !== "" ? ( -
-
-

No projects found

-
-
- ) : ( -
-
-

- No projects created -

- -
-
- )} - - ) : ( -
- - - - - Project - Monthly Active Users - Client ID - Created - - - - {paginatedProjects.map((project) => ( - - - - - - {project.name} - - - - - {project.monthlyActiveUsers === 0 ? ( - No Users - ) : ( - project.monthlyActiveUsers - )} - - - - - - {formatDate(new Date(project.createdAt), "MMM d, yyyy")} - - - ))} - -
-
- - {showPagination && ( -
- -
- )} -
- )} +
+ setIsCreateProjectDialogOpen(true)} + /> { - // refresh projects + onCreate={() => { router.refresh(); }} enableNebulaServiceByDefault={props.team.enabledScopes.includes( @@ -238,73 +42,3 @@ export function TeamProjectsPage(props: {
); } - -function SearchInput(props: { - value: string; - onValueChange: (value: string) => void; -}) { - return ( -
- props.onValueChange(e.target.value)} - className="bg-background pl-9 lg:w-[400px]" - /> - -
- ); -} - -function AddNewButton(props: { - createProject: () => void; - teamMembersSettingsPath: string; -}) { - return ( - - ); -} - -function SelectBy(props: { - value: SortById; - onChange: (value: SortById) => void; -}) { - const values: SortById[] = ["name", "createdAt", "monthlyActiveUsers"]; - const valueToLabel: Record = { - name: "Name", - createdAt: "Creation Date", - monthlyActiveUsers: "Monthly Active Users", - }; - - return ( - - ); -} diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/~/projects/TeamProjectsTable.stories.tsx b/apps/dashboard/src/app/team/[team_slug]/(team)/~/projects/TeamProjectsTable.stories.tsx new file mode 100644 index 00000000000..1c83b33257f --- /dev/null +++ b/apps/dashboard/src/app/team/[team_slug]/(team)/~/projects/TeamProjectsTable.stories.tsx @@ -0,0 +1,83 @@ +import { getThirdwebClient } from "@/constants/thirdweb.server"; +import type { Meta, StoryObj } from "@storybook/react"; +import { projectStub, teamStub } from "../../../../../../stories/stubs"; +import { storybookLog } from "../../../../../../stories/utils"; +import { + type ProjectWithAnalytics, + TeamProjectsTable, +} from "./TeamProjectsTable"; + +const meta: Meta = { + title: "Team/Projects", + component: Variant, + parameters: { + layout: "fullscreen", + nextjs: { + appDirectory: true, + }, + }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], +}; + +export default meta; +type Story = StoryObj; + +function createProjectWithAnalyticsStub(len: number) { + return Array.from({ length: len }, (_, i) => { + return { + ...projectStub(`${i + 1}`, "foo"), + monthlyActiveUsers: Math.floor(Math.random() * 1000), + }; + }); +} + +export const MultipleProjects: Story = { + args: { + projects: createProjectWithAnalyticsStub(10), + }, +}; + +export const SingleProject: Story = { + args: { + projects: createProjectWithAnalyticsStub(1), + }, +}; + +export const NoProjects: Story = { + args: { + projects: [], + }, +}; + +export const NoUsers: Story = { + args: { + projects: createProjectWithAnalyticsStub(10).map((p) => ({ + ...p, + monthlyActiveUsers: 0, + })), + }, +}; + +const client = getThirdwebClient(); +const team = teamStub("foo", "free"); + +function Variant(props: { + projects: ProjectWithAnalytics[]; +}) { + return ( + { + storybookLog("showCreateProjectModal"); + }} + /> + ); +} diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/~/projects/TeamProjectsTable.tsx b/apps/dashboard/src/app/team/[team_slug]/(team)/~/projects/TeamProjectsTable.tsx new file mode 100644 index 00000000000..049b419ce41 --- /dev/null +++ b/apps/dashboard/src/app/team/[team_slug]/(team)/~/projects/TeamProjectsTable.tsx @@ -0,0 +1,290 @@ +"use client"; + +import type { Project } from "@/api/projects"; +import type { Team } from "@/api/team"; +import { ProjectAvatar } from "@/components/blocks/Avatars/ProjectAvatar"; +import { PaginationButtons } from "@/components/pagination-buttons"; +import { CopyTextButton } from "@/components/ui/CopyTextButton"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, +} from "@/components/ui/select"; +import { + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { cn } from "@/lib/utils"; +import { formatDate } from "date-fns"; +import { PlusIcon, SearchIcon } from "lucide-react"; +import Link from "next/link"; +import { useMemo, useState } from "react"; +import type { ThirdwebClient } from "thirdweb"; + +type SortById = "name" | "createdAt" | "monthlyActiveUsers"; + +export type ProjectWithAnalytics = Project & { + monthlyActiveUsers: number; +}; + +export function TeamProjectsTable(props: { + projects: ProjectWithAnalytics[]; + team: Team; + client: ThirdwebClient; + openCreateProjectModal: () => void; +}) { + const { projects } = props; + const [searchTerm, setSearchTerm] = useState(""); + const [sortBy, setSortBy] = useState("monthlyActiveUsers"); + + const sortedProjects = useMemo(() => { + let _projectsToShow = !searchTerm + ? projects + : projects.filter( + (project) => + project.name.toLowerCase().includes(searchTerm.toLowerCase()) || + project.publishableKey + .toLowerCase() + .includes(searchTerm.toLowerCase()), + ); + + if (sortBy === "name") { + _projectsToShow = _projectsToShow.sort((a, b) => + a.name.localeCompare(b.name), + ); + } else if (sortBy === "createdAt") { + _projectsToShow = _projectsToShow.sort( + (a, b) => + new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), + ); + } else if (sortBy === "monthlyActiveUsers") { + _projectsToShow = _projectsToShow.sort( + (a, b) => b.monthlyActiveUsers - a.monthlyActiveUsers, + ); + } + + return _projectsToShow; + }, [searchTerm, sortBy, projects]); + + const pageSize = 10; + const [page, setPage] = useState(1); + const paginatedProjects = sortedProjects.slice( + (page - 1) * pageSize, + page * pageSize, + ); + + const showPagination = sortedProjects.length > pageSize; + const totalPages = Math.ceil(sortedProjects.length / pageSize); + + return ( +
+
+

Projects

+ + {/* Filters + Add New */} +
+ { + setSearchTerm(v); + setPage(1); + }} + /> +
+ { + setSortBy(v); + setPage(1); + }} + /> + props.openCreateProjectModal()} + /> +
+
+
+ + {/* Projects Table */} + {paginatedProjects.length === 0 ? ( + <> + {searchTerm !== "" ? ( +
+
+

No projects found

+
+
+ ) : ( +
+
+

+ No projects created +

+ +
+
+ )} + + ) : ( +
+ + + + + Project + Monthly Active Users + Client ID + Created + + + + {paginatedProjects.map((project) => ( + + + + + + {project.name} + + + + + {project.monthlyActiveUsers === 0 ? ( + No Users + ) : ( + project.monthlyActiveUsers + )} + + + + + + {formatDate(new Date(project.createdAt), "MMM d, yyyy")} + + + ))} + +
+
+ + {showPagination && ( +
+ +
+ )} +
+ )} +
+ ); +} + +function SearchInput(props: { + value: string; + onValueChange: (value: string) => void; +}) { + return ( +
+ props.onValueChange(e.target.value)} + className="bg-background pl-9 lg:w-[400px]" + /> + +
+ ); +} + +function AddNewButton(props: { + createProject: () => void; +}) { + return ( + + ); +} + +function SelectBy(props: { + value: SortById; + onChange: (value: SortById) => void; +}) { + const values: SortById[] = ["name", "createdAt", "monthlyActiveUsers"]; + const valueToLabel: Record = { + name: "Name", + createdAt: "Creation Date", + monthlyActiveUsers: "Monthly Active Users", + }; + + return ( + + ); +} diff --git a/apps/dashboard/src/app/team/[team_slug]/layout.tsx b/apps/dashboard/src/app/team/[team_slug]/layout.tsx index 45464173d5c..6d44c407ee7 100644 --- a/apps/dashboard/src/app/team/[team_slug]/layout.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/layout.tsx @@ -1,3 +1,4 @@ +import { getProjects } from "@/api/projects"; import { getTeamBySlug } from "@/api/team"; import { AppFooter } from "@/components/blocks/app-footer"; import { redirect } from "next/navigation"; @@ -13,7 +14,10 @@ export default async function RootTeamLayout(props: { params: Promise<{ team_slug: string }>; }) { const { team_slug } = await props.params; - const team = await getTeamBySlug(team_slug).catch(() => null); + const [team, projects] = await Promise.all([ + getTeamBySlug(team_slug).catch(() => null), + getProjects(team_slug), + ]); if (!team) { redirect("/team"); @@ -23,6 +27,10 @@ export default async function RootTeamLayout(props: { redirect(`/get-started/team/${team.slug}`); } + if (projects.length === 0) { + redirect(`/get-started/team/${team.slug}/create-project`); + } + return (
diff --git a/apps/dashboard/src/app/team/components/TeamHeader/team-header-logged-in.client.tsx b/apps/dashboard/src/app/team/components/TeamHeader/team-header-logged-in.client.tsx index 24472c44080..8f6bad50b3f 100644 --- a/apps/dashboard/src/app/team/components/TeamHeader/team-header-logged-in.client.tsx +++ b/apps/dashboard/src/app/team/components/TeamHeader/team-header-logged-in.client.tsx @@ -83,7 +83,7 @@ export function TeamHeaderLoggedIn(props: { isOpen: false, }) } - onCreateAndComplete={() => { + onCreate={() => { // refresh projects router.refresh(); }} diff --git a/apps/dashboard/src/components/settings/ApiKeys/Create/index.tsx b/apps/dashboard/src/components/settings/ApiKeys/Create/index.tsx index be65c35a259..24d7e371530 100644 --- a/apps/dashboard/src/components/settings/ApiKeys/Create/index.tsx +++ b/apps/dashboard/src/components/settings/ApiKeys/Create/index.tsx @@ -5,14 +5,7 @@ import { Spinner } from "@/components/ui/Spinner/Spinner"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Button } from "@/components/ui/button"; import { Checkbox, CheckboxWithLabel } from "@/components/ui/checkbox"; -import { - Dialog, - DialogClose, - DialogContent, - DialogFooter, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; +import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog"; import { Form } from "@/components/ui/form"; import { FormControl, @@ -27,7 +20,6 @@ import { Textarea } from "@/components/ui/textarea"; import { useDashboardRouter } from "@/lib/DashboardRouter"; import { createProjectClient } from "@3rdweb-sdk/react/hooks/useApi"; import { zodResolver } from "@hookform/resolvers/zod"; -import { DialogDescription } from "@radix-ui/react-dialog"; import { useMutation } from "@tanstack/react-query"; import type { ProjectService } from "@thirdweb-dev/service-utils"; import { SERVICES } from "@thirdweb-dev/service-utils"; @@ -52,7 +44,7 @@ export type CreateProjectPrefillOptions = { export type CreateProjectDialogProps = { open: boolean; onOpenChange: (open: boolean) => void; - onCreateAndComplete?: () => void; + onCreate?: () => void; prefill?: CreateProjectPrefillOptions; enableNebulaServiceByDefault: boolean; teamId: string; @@ -79,7 +71,7 @@ export default CreateProjectDialog; export const CreateProjectDialogUI = (props: { open: boolean; onOpenChange: (open: boolean) => void; - onCreateAndComplete?: () => void; + onCreate?: () => void; createProject: (param: Partial) => Promise<{ project: Project; secret: string; @@ -88,61 +80,80 @@ export const CreateProjectDialogUI = (props: { enableNebulaServiceByDefault: boolean; teamSlug: string; }) => { - const [screen, setScreen] = useState< - { id: "create" } | { id: "api-details"; project: Project; secret: string } - >({ id: "create" }); - const { open, onOpenChange } = props; - return ( - { - // Prevent closing the dialog when the API key is created - to make sure user does not accidentally close the dialog without copying the secret key - if (screen.id === "api-details") { - return; - } - - onOpenChange(v); - }} - > + + Create Project - {screen.id === "create" && ( - { - setScreen({ - id: "api-details", - project: params.project, - secret: params.secret, - }); - }} - prefill={props.prefill} - enableNebulaServiceByDefault={props.enableNebulaServiceByDefault} - /> - )} - - {screen.id === "api-details" && ( - { - onOpenChange(false); - setScreen({ id: "create" }); - props.onCreateAndComplete?.(); - }} - /> - )} + props.onOpenChange(false)} + /> ); }; +function CreateProjectDialogUIContent(props: { + onCreate?: () => void; + createProject: (param: Partial) => Promise<{ + project: Project; + secret: string; + }>; + prefill?: CreateProjectPrefillOptions; + enableNebulaServiceByDefault: boolean; + teamSlug: string; + closeModal: () => void; +}) { + const [screen, setScreen] = useState< + { id: "create" } | { id: "api-details"; project: Project; secret: string } + >({ id: "create" }); + + if (screen.id === "create") { + return ( + { + setScreen({ + id: "api-details", + project: params.project, + secret: params.secret, + }); + props.onCreate?.(); + }} + prefill={props.prefill} + enableNebulaServiceByDefault={props.enableNebulaServiceByDefault} + /> + ); + } + + if (screen.id === "api-details") { + return ( + { + props.closeModal(); + }} + /> + ); + } + + return null; +} + const createProjectFormSchema = z.object({ name: projectNameSchema, domains: projectDomainsSchema, @@ -150,7 +161,9 @@ const createProjectFormSchema = z.object({ type CreateProjectFormSchema = z.infer; -function CreateProjectForm(props: { +// Note: Do not use any dialog components +// This is also used in create project onboarding flow +export function CreateProjectForm(props: { createProject: (param: Partial) => Promise<{ project: Project; secret: string; @@ -161,6 +174,8 @@ function CreateProjectForm(props: { project: Project; secret: string; }) => void; + closeModal: (() => void) | undefined; + showTitle: boolean; }) { const [showAlert, setShowAlert] = useState<"no-domain" | "any-domain">(); const trackEvent = useTrack(); @@ -272,9 +287,11 @@ function CreateProjectForm(props: {
- - Create Project - + {props.showTitle && ( +

+ Create Project +

+ )}
Project Name @@ -316,7 +333,7 @@ function CreateProjectForm(props: { Allowed Domains