Skip to content

Commit

Permalink
Request v3 access (from the app) and disable v2 projects by default (#…
Browse files Browse the repository at this point in the history
…1123)

* Added v2Enabled and hasRequestedV3 columns to Organization

* Don’t create a project when you create an org

* Form for requesting v3 access

* Reworked the new project form with the different version states. Refined copy on early access

* If the project isn’t in the org then redirect to the new project page

* Better message for existing users

* Tidy imports

* If it’s not the managed cloud then allow them to create v2 projects
  • Loading branch information
matt-aitken committed May 22, 2024
1 parent c815f28 commit 116766f
Show file tree
Hide file tree
Showing 9 changed files with 267 additions and 89 deletions.
4 changes: 4 additions & 0 deletions apps/webapp/app/assets/icons/v3.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 2 additions & 15 deletions apps/webapp/app/models/organization.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,12 @@ export async function createOrganization(
{
title,
userId,
projectName,
companySize,
projectVersion,
}: Pick<Organization, "title" | "companySize"> & {
userId: User["id"];
projectName: string;
projectVersion: "v2" | "v3";
},
attemptCount = 0
): Promise<Organization & { projects: Project[] }> {
): Promise<Organization> {
if (typeof process.env.BLOCKED_USERS === "string" && process.env.BLOCKED_USERS.includes(userId)) {
throw new Error("Organization could not be created.");
}
Expand All @@ -50,9 +46,7 @@ export async function createOrganization(
{
title,
userId,
projectName,
companySize,
projectVersion,
},
attemptCount + 1
);
Expand All @@ -76,14 +70,7 @@ export async function createOrganization(
},
});

const project = await createProject({
organizationSlug: organization.slug,
name: projectName,
userId,
version: projectVersion,
});

return { ...organization, projects: [project] };
return { ...organization };
}

export async function createEnvironment(
Expand Down
4 changes: 4 additions & 0 deletions apps/webapp/app/presenters/OrganizationsPresenter.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ export class OrganizationsPresenter {
);
}

if (project.organizationId !== organization.id) {
throw redirect(newProjectPath({ slug: organizationSlug }), request);
}

return { organizations, organization, project };
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,17 @@ import { FormTitle } from "~/components/primitives/FormTitle";
import { Input } from "~/components/primitives/Input";
import { InputGroup } from "~/components/primitives/InputGroup";
import { Label } from "~/components/primitives/Label";
import { Paragraph } from "~/components/primitives/Paragraph";
import { Select, SelectItem } from "~/components/primitives/Select";
import { TextLink } from "~/components/primitives/TextLink";
import { prisma } from "~/db.server";
import { useFeatures } from "~/hooks/useFeatures";
import { useUser } from "~/hooks/useUser";
import { redirectWithSuccessMessage } from "~/models/message.server";
import { createProject } from "~/models/project.server";
import { requireUserId } from "~/services/session.server";
import { OrganizationParamsSchema, organizationPath, projectPath } from "~/utils/pathBuilder";
import { RequestV3Access } from "../resources.orgs.$organizationSlug.v3-access";

export async function loader({ params, request }: LoaderFunctionArgs) {
const userId = await requireUserId(request);
Expand All @@ -34,10 +38,14 @@ export async function loader({ params, request }: LoaderFunctionArgs) {
id: true,
title: true,
v3Enabled: true,
v2Enabled: true,
hasRequestedV3: true,
_count: {
select: {
projects: {
where: { deletedAt: null },
where: {
deletedAt: null,
},
},
},
},
Expand All @@ -57,6 +65,8 @@ export async function loader({ params, request }: LoaderFunctionArgs) {
slug: organizationSlug,
projectsCount: organization._count.projects,
v3Enabled: organization.v3Enabled,
v2Enabled: organization.v2Enabled,
hasRequestedV3: organization.hasRequestedV3,
},
defaultVersion: url.searchParams.get("version") ?? "v2",
});
Expand Down Expand Up @@ -98,11 +108,23 @@ export const action: ActionFunction = async ({ request, params }) => {
};

export default function NewOrganizationPage() {
const { organization, defaultVersion } = useTypedLoaderData<typeof loader>();
const { organization } = useTypedLoaderData<typeof loader>();
const lastSubmission = useActionData();
const { v3Enabled } = useFeatures();
const { v3Enabled, isManagedCloud } = useFeatures();

const canCreateV3Projects = organization.v3Enabled && v3Enabled;
const canCreateV2Projects = organization.v2Enabled || !isManagedCloud;
const canCreateProjects = canCreateV2Projects || canCreateV3Projects;

if (!canCreateProjects) {
return (
<RequestV3Access
hasRequestedV3={organization.hasRequestedV3}
organizationSlug={organization.slug}
projectsCount={organization.projectsCount}
/>
);
}

const [form, { projectName, projectVersion }] = useForm({
id: "create-project",
Expand All @@ -119,7 +141,7 @@ export default function NewOrganizationPage() {
<FormTitle
LeadingIcon="folder"
title="Create a new project"
description={`This will create a new project in your "${organization.title}" organization. `}
description={`This will create a new project in your "${organization.title}" organization.`}
/>
<Form method="post" {...form.props}>
{organization.projectsCount === 0 && (
Expand All @@ -138,7 +160,7 @@ export default function NewOrganizationPage() {
/>
<FormError id={projectName.errorId}>{projectName.error}</FormError>
</InputGroup>
{canCreateV3Projects ? (
{canCreateV2Projects && canCreateV3Projects ? (
<InputGroup>
<Label htmlFor={projectVersion.id}>Project version</Label>
<Select
Expand All @@ -161,8 +183,16 @@ export default function NewOrganizationPage() {
</Select>
<FormError id={projectVersion.errorId}>{projectVersion.error}</FormError>
</InputGroup>
) : canCreateV3Projects ? (
<>
<Callout variant="info">This will be a v3 project</Callout>
<input {...conform.input(projectVersion, { type: "hidden" })} value={"v3"} />
</>
) : (
<input {...conform.input(projectVersion, { type: "hidden" })} value="v2" />
<>
<Callout variant="info">This will be a v2 project</Callout>
<input {...conform.input(projectVersion, { type: "hidden" })} value={"v2"} />
</>
)}
<FormButtons
confirmButton={
Expand Down
71 changes: 4 additions & 67 deletions apps/webapp/app/routes/_app.orgs.new/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,14 @@ import { Input } from "~/components/primitives/Input";
import { InputGroup } from "~/components/primitives/InputGroup";
import { Label } from "~/components/primitives/Label";
import { RadioGroupItem } from "~/components/primitives/RadioButton";
import { Select, SelectItem } from "~/components/primitives/Select";
import { featuresForRequest } from "~/features.server";
import { useFeatures } from "~/hooks/useFeatures";
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, rootPath, selectPlanPath } from "~/utils/pathBuilder";
import { organizationPath, rootPath } from "~/utils/pathBuilder";

const schema = z.object({
orgName: z.string().min(3).max(50),
projectName: z.string().min(3).max(50),
projectVersion: z.enum(["v2", "v3"]),
companySize: z.string().optional(),
});

Expand Down Expand Up @@ -57,29 +52,10 @@ export const action: ActionFunction = async ({ request }) => {
const organization = await createOrganization({
title: submission.value.orgName,
userId,
projectName: submission.value.projectName,
companySize: submission.value.companySize ?? null,
projectVersion: submission.value.projectVersion,
});

const project = organization.projects[0];
const session = await setCurrentProjectId(project.id, request);

const { isManagedCloud } = featuresForRequest(request);

const headers = {
"Set-Cookie": await commitCurrentProjectSession(session),
};

if (isManagedCloud && submission.value.projectVersion === "v2") {
return redirect(selectPlanPath(organization), {
headers,
});
}

return redirect(projectPath(organization, project), {
headers,
});
return redirect(organizationPath(organization));
} catch (error: any) {
return json({ errors: { body: error.message } }, { status: 400 });
}
Expand All @@ -91,10 +67,7 @@ export default function NewOrganizationPage() {
const { isManagedCloud } = useFeatures();
const navigation = useNavigation();

//this is temporary whilst v3 is invite-only. Switch to the useFeatures value when v3 is generally available.
const v3Enabled = false;

const [form, { orgName, projectName, projectVersion }] = useForm({
const [form, { orgName }] = useForm({
id: "create-organization",
// TODO: type this
lastSubmission: lastSubmission as any,
Expand Down Expand Up @@ -123,45 +96,9 @@ export default function NewOrganizationPage() {
<Hint>E.g. your company name or your workspace name.</Hint>
<FormError id={orgName.errorId}>{orgName.error}</FormError>
</InputGroup>
<InputGroup>
<Label htmlFor={projectName.id}>Project name</Label>
<Input
{...conform.input(projectName, { type: "text" })}
placeholder="Your Project name"
icon="folder"
/>
<Hint>Your Jobs will live inside this Project.</Hint>
<FormError id={projectName.errorId}>{projectName.error}</FormError>
</InputGroup>
{v3Enabled ? (
<InputGroup>
<Label htmlFor={projectVersion.id}>Project version</Label>
<Select
{...conform.select(projectVersion)}
defaultValue={undefined}
variant="tertiary/medium"
placeholder="Select version"
dropdownIcon
text={(value) => {
switch (value) {
case "v2":
return "Version 2";
case "v3":
return "Version 3";
}
}}
>
<SelectItem value="v2">Version 2</SelectItem>
<SelectItem value="v3">Version 3 (Developer Preview)</SelectItem>
</Select>
<FormError id={projectVersion.errorId}>{projectVersion.error}</FormError>
</InputGroup>
) : (
<input {...conform.input(projectVersion, { type: "hidden" })} value="v2" />
)}
{isManagedCloud && (
<InputGroup>
<Label htmlFor={projectName.id}>Number of employees</Label>
<Label htmlFor={"companySize"}>Number of employees</Label>
<RadioGroup name="companySize" className="flex items-center justify-between gap-2">
<RadioGroupItem
id="employees-1-5"
Expand Down

0 comments on commit 116766f

Please sign in to comment.