From 26445ca367cd2907933b4d6d1904b5af239b6582 Mon Sep 17 00:00:00 2001 From: James Ritchie Date: Tue, 28 Nov 2023 15:33:53 +0000 Subject: [PATCH] New onboarding choose plan page --- .../primitives/SegmentedControl.tsx | 2 +- .../app/routes/_app.orgs.plan/route.tsx | 116 ++++++++++++++++++ 2 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 apps/webapp/app/routes/_app.orgs.plan/route.tsx diff --git a/apps/webapp/app/components/primitives/SegmentedControl.tsx b/apps/webapp/app/components/primitives/SegmentedControl.tsx index da12092fa..cfb04d338 100644 --- a/apps/webapp/app/components/primitives/SegmentedControl.tsx +++ b/apps/webapp/app/components/primitives/SegmentedControl.tsx @@ -41,7 +41,7 @@ export default function SegmentedControl({ value={option.value} className={({ active, checked }) => cn( - "relative flex h-full grow cursor-pointer rounded-[2px] font-normal focus:outline-none", + "relative flex h-full grow cursor-pointer rounded-[2px] text-center font-normal focus:outline-none", active ? "ring-offset-2 focus-visible:ring focus-visible:ring-indigo-500 focus-visible:ring-opacity-60" : "", diff --git a/apps/webapp/app/routes/_app.orgs.plan/route.tsx b/apps/webapp/app/routes/_app.orgs.plan/route.tsx new file mode 100644 index 000000000..d2f490f43 --- /dev/null +++ b/apps/webapp/app/routes/_app.orgs.plan/route.tsx @@ -0,0 +1,116 @@ +import { useForm } from "@conform-to/react"; +import { parse } from "@conform-to/zod"; +import { ChartBarIcon } from "@heroicons/react/20/solid"; +import type { ActionFunction, LoaderFunctionArgs } from "@remix-run/node"; +import { json, redirect } from "@remix-run/node"; +import { Form, useActionData } from "@remix-run/react"; +import { typedjson, useTypedLoaderData } from "remix-typedjson"; +import { z } from "zod"; +import { PricingCalculator } from "~/components/billing/PricingCalculator"; +import { PricingTiers, TierEnterprise, TierFree, TierPro } from "~/components/billing/PricingTiers"; +import { RunsVolumeDiscountTable } from "~/components/billing/RunsVolumeDiscountTable"; +import { Button } from "~/components/primitives/Buttons"; +import { Header1 } from "~/components/primitives/Headers"; +import { + Sheet, + SheetBody, + SheetContent, + SheetHeader, + SheetTrigger, +} from "~/components/primitives/Sheet"; +import { createOrganization } from "~/models/organization.server"; +import { NewOrganizationPresenter } from "~/presenters/NewOrganizationPresenter.server"; +import { commitCurrentProjectSession, setCurrentProjectId } from "~/services/currentProject.server"; +import { requireUserId } from "~/services/session.server"; +import { projectPath } from "~/utils/pathBuilder"; + +const schema = z.object({ + orgName: z.string().min(3).max(50), + projectName: z.string().min(3).max(50), +}); + +export const loader = async ({ request }: LoaderFunctionArgs) => { + const userId = await requireUserId(request); + const presenter = new NewOrganizationPresenter(); + const { hasOrganizations } = await presenter.call({ userId }); + + return typedjson({ + hasOrganizations, + }); +}; + +export const action: ActionFunction = async ({ request }) => { + const userId = await requireUserId(request); + + const formData = await request.formData(); + const submission = parse(formData, { schema }); + + if (!submission.value || submission.intent !== "submit") { + return json(submission); + } + + try { + const organization = await createOrganization({ + title: submission.value.orgName, + userId, + projectName: submission.value.projectName, + }); + + const project = organization.projects[0]; + const session = await setCurrentProjectId(project.id, request); + + return redirect(projectPath(organization, project), { + headers: { + "Set-Cookie": await commitCurrentProjectSession(session), + }, + }); + } catch (error: any) { + return json({ errors: { body: error.message } }, { status: 400 }); + } +}; + +export default function ChoosePlanPage() { + const { hasOrganizations } = useTypedLoaderData(); + const lastSubmission = useActionData(); + + const [form, { orgName, projectName }] = useForm({ + id: "create-organization", + // TODO: type this + lastSubmission: lastSubmission as any, + onValidate({ formData }) { + return parse(formData, { schema }); + }, + }); + + return ( +
+ Subscribe for full access + + + + + + + + + + + + +
+ Estimate your usage +
+
+ + +
+ +
+
+
+
+
+ ); +}