Files
2026-07-13 13:32:57 +08:00

138 lines
4.7 KiB
TypeScript

import { getFormProps, getInputProps, useForm } from "@conform-to/react";
import { parseWithZod } from "@conform-to/zod";
import { DialogClose } from "@radix-ui/react-dialog";
import { Form, useActionData, useLocation } from "@remix-run/react";
import { type ActionFunctionArgs } from "@remix-run/server-runtime";
import { z } from "zod";
import { ArchiveIcon } from "~/assets/icons/ArchiveIcon";
import { Button } from "~/components/primitives/Buttons";
import { Dialog, DialogContent, DialogHeader, DialogTrigger } from "~/components/primitives/Dialog";
import { FormButtons } from "~/components/primitives/FormButtons";
import { FormError } from "~/components/primitives/FormError";
import { Paragraph } from "~/components/primitives/Paragraph";
import { redirectWithErrorMessage, redirectWithSuccessMessage } from "~/models/message.server";
import { ArchiveBranchService } from "~/services/archiveBranch.server";
import { requireUserId } from "~/services/session.server";
import { branchesDevPath, branchesPath } from "~/utils/pathBuilder";
const ArchiveBranchOptions = z.object({
environmentId: z.string(),
});
const schema = ArchiveBranchOptions.and(
z.object({
redirectPath: z.string(),
})
);
export async function action({ request }: ActionFunctionArgs) {
const userId = await requireUserId(request);
const formData = await request.formData();
const submission = parseWithZod(formData, { schema });
if (submission.status !== "success") {
return redirectWithErrorMessage("/", request, "Invalid form data");
}
const archiveBranchService = new ArchiveBranchService();
const result = await archiveBranchService.call(
{ type: "userMembership", userId },
{
environmentId: submission.value.environmentId,
}
);
if (result.success) {
return redirectWithSuccessMessage(
result.branch.type === "DEVELOPMENT"
? branchesDevPath(result.organization, result.project, result.branch)
: branchesPath(result.organization, result.project, result.branch),
request,
`Branch "${result.branch.branchName}" archived`
);
}
return redirectWithErrorMessage(submission.value.redirectPath, request, result.error);
}
export function ArchiveButton({
environment,
disabled,
}: {
environment: { id: string; branchName: string };
disabled?: boolean;
}) {
const lastSubmission = useActionData<typeof action>();
const location = useLocation();
const [form, { environmentId, redirectPath }] = useForm({
id: "archive-branch",
lastResult: lastSubmission as any,
onValidate({ formData }) {
return parseWithZod(formData, { schema });
},
shouldRevalidate: "onInput",
});
return (
<Dialog>
<DialogTrigger asChild>
<Button
variant="small-menu-item"
LeadingIcon={ArchiveIcon}
leadingIconClassName="text-error"
fullWidth
textAlignLeft
className="w-full px-1.5 py-[0.9rem]"
disabled={disabled}
>
Archive branch
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>Archive "{environment.branchName}"</DialogHeader>
<div className="mt-2 flex flex-col gap-4">
<Form
method="post"
action="/resources/branches/archive"
{...getFormProps(form)}
className="w-full"
>
<input value={environment.id} {...getInputProps(environmentId, { type: "hidden" })} />
<input
value={`${location.pathname}${location.search}`}
{...getInputProps(redirectPath, { type: "hidden" })}
/>
<Paragraph spacing>
This will <span className="text-text-bright">permanently</span> make this branch{" "}
<span className="text-text-bright">read-only</span>. You won't be able to trigger
runs, execute runs, or use the API for this branch.
</Paragraph>
<Paragraph spacing>
You will still be able to view the branch and its associated runs.
</Paragraph>
<Paragraph spacing>
Once archived you can create a new branch with the same name.
</Paragraph>
<FormError>{form.errors}</FormError>
<FormButtons
confirmButton={
<Button LeadingIcon={ArchiveIcon} type="submit" variant="danger/medium">
Archive branch
</Button>
}
cancelButton={
<DialogClose asChild>
<Button variant="secondary/medium">Cancel</Button>
</DialogClose>
}
/>
</Form>
</div>
</DialogContent>
</Dialog>
);
}