chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
import { getFormProps, getInputProps, useForm } from "@conform-to/react";
|
||||
import { parseWithZod } from "@conform-to/zod";
|
||||
import { DialogClose } from "@radix-ui/react-dialog";
|
||||
import { useFetcher, useLocation, useSearchParams } from "@remix-run/react";
|
||||
import { type ActionFunctionArgs, json } from "@remix-run/server-runtime";
|
||||
import { useEffect, useState } from "react";
|
||||
import { InlineCode } from "~/components/code/InlineCode";
|
||||
import { Button } from "~/components/primitives/Buttons";
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTrigger } from "~/components/primitives/Dialog";
|
||||
import { Fieldset } from "~/components/primitives/Fieldset";
|
||||
import { FormButtons } from "~/components/primitives/FormButtons";
|
||||
import { FormError } from "~/components/primitives/FormError";
|
||||
import { Hint } from "~/components/primitives/Hint";
|
||||
import { Input } from "~/components/primitives/Input";
|
||||
import { InputGroup } from "~/components/primitives/InputGroup";
|
||||
import { Label } from "~/components/primitives/Label";
|
||||
import { useProject } from "~/hooks/useProject";
|
||||
import { redirectWithErrorMessage, redirectWithSuccessMessage } from "~/models/message.server";
|
||||
import { requireUserId } from "~/services/session.server";
|
||||
import { UpsertBranchService } from "~/services/upsertBranch.server";
|
||||
import { type BranchableEnvironmentToken } from "~/utils/branchableEnvironment";
|
||||
import { CreateBranchFormSchema } from "~/utils/branches";
|
||||
import { branchesDevPath, branchesPath } from "~/utils/pathBuilder";
|
||||
|
||||
export async function action({ request }: ActionFunctionArgs) {
|
||||
const userId = await requireUserId(request);
|
||||
|
||||
const formData = await request.formData();
|
||||
const submission = parseWithZod(formData, { schema: CreateBranchFormSchema });
|
||||
|
||||
if (submission.status !== "success") {
|
||||
return redirectWithErrorMessage("/", request, "Invalid form data");
|
||||
}
|
||||
|
||||
const upsertBranchService = new UpsertBranchService();
|
||||
const result = await upsertBranchService.call(
|
||||
{ type: "userMembership", userId },
|
||||
submission.value
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
if (result.alreadyExisted) {
|
||||
return json(
|
||||
submission.reply({
|
||||
fieldErrors: {
|
||||
branchName: [
|
||||
`Branch "${result.branch.branchName}" already exists. You can archive it and create a new one with the same name.`,
|
||||
],
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Branches of both types are created through here; route the success
|
||||
// redirect to the matching list page based on the created branch's type.
|
||||
const path =
|
||||
result.branch.type === "DEVELOPMENT"
|
||||
? branchesDevPath(result.organization, result.project, result.branch)
|
||||
: branchesPath(result.organization, result.project, result.branch);
|
||||
|
||||
return redirectWithSuccessMessage(
|
||||
`${path}?dialogClosed=true`,
|
||||
request,
|
||||
`Branch "${result.branch.branchName}" created`
|
||||
);
|
||||
}
|
||||
|
||||
return json(submission.reply({ fieldErrors: { branchName: [result.error] } }));
|
||||
}
|
||||
|
||||
export function NewBranchPanel({
|
||||
button,
|
||||
env,
|
||||
}: {
|
||||
button: React.ReactNode;
|
||||
env: BranchableEnvironmentToken;
|
||||
}) {
|
||||
const project = useProject();
|
||||
// Posts to this resource route (not the host page), so read the submission
|
||||
// result off the fetcher rather than the page's `useActionData`.
|
||||
const fetcher = useFetcher<typeof action>();
|
||||
const lastSubmission = fetcher.data;
|
||||
const location = useLocation();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const [form, { projectId, env: envField, branchName, failurePath }] = useForm({
|
||||
id: "create-branch",
|
||||
lastResult: lastSubmission as any,
|
||||
onValidate({ formData }) {
|
||||
return parseWithZod(formData, { schema: CreateBranchFormSchema });
|
||||
},
|
||||
shouldRevalidate: "onInput",
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (searchParams.has("dialogClosed")) {
|
||||
setSearchParams((s) => {
|
||||
s.delete("dialogClosed");
|
||||
return s;
|
||||
});
|
||||
setIsOpen(false);
|
||||
}
|
||||
}, [searchParams, setSearchParams]);
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogTrigger asChild>{button}</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>New branch</DialogHeader>
|
||||
<div className="mt-2 flex flex-col gap-4">
|
||||
<fetcher.Form
|
||||
method="post"
|
||||
action="/resources/branches/create"
|
||||
{...getFormProps(form)}
|
||||
className="w-full"
|
||||
>
|
||||
<Fieldset className="max-w-full gap-y-3">
|
||||
<input value={project.id} {...getInputProps(projectId, { type: "hidden" })} />
|
||||
<input value={env} {...getInputProps(envField, { type: "hidden" })} />
|
||||
<input
|
||||
value={location.pathname}
|
||||
{...getInputProps(failurePath, { type: "hidden" })}
|
||||
/>
|
||||
<InputGroup className="max-w-full">
|
||||
<Label>Branch name</Label>
|
||||
<Input {...getInputProps(branchName, { type: "text" })} />
|
||||
<Hint>
|
||||
Must not contain: spaces <InlineCode variant="extra-small">~</InlineCode>{" "}
|
||||
<InlineCode variant="extra-small">^</InlineCode>{" "}
|
||||
<InlineCode variant="extra-small">:</InlineCode>{" "}
|
||||
<InlineCode variant="extra-small">?</InlineCode>{" "}
|
||||
<InlineCode variant="extra-small">*</InlineCode>{" "}
|
||||
<InlineCode variant="extra-small">{"["}</InlineCode>{" "}
|
||||
<InlineCode variant="extra-small">\</InlineCode>{" "}
|
||||
<InlineCode variant="extra-small">//</InlineCode>{" "}
|
||||
<InlineCode variant="extra-small">..</InlineCode>{" "}
|
||||
<InlineCode variant="extra-small">{"@{"}</InlineCode>{" "}
|
||||
<InlineCode variant="extra-small">.lock</InlineCode>
|
||||
</Hint>
|
||||
<FormError id={branchName.errorId}>{branchName.errors}</FormError>
|
||||
</InputGroup>
|
||||
<FormError>{form.errors}</FormError>
|
||||
<FormButtons
|
||||
confirmButton={
|
||||
<Button type="submit" variant="primary/medium">
|
||||
Create branch
|
||||
</Button>
|
||||
}
|
||||
cancelButton={
|
||||
<DialogClose asChild>
|
||||
<Button variant="tertiary/medium">Cancel</Button>
|
||||
</DialogClose>
|
||||
}
|
||||
/>
|
||||
</Fieldset>
|
||||
</fetcher.Form>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user