Merge pull request #1572 from thebadking/refactor-show-build-form-and-prettier

build form optimization
This commit is contained in:
Mauricio Siu
2025-03-30 03:36:19 -06:00
committed by GitHub
2 changed files with 213 additions and 215 deletions

View File

@@ -20,7 +20,7 @@ import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import { z } from "zod"; import { z } from "zod";
enum BuildType { export enum BuildType {
dockerfile = "dockerfile", dockerfile = "dockerfile",
heroku_buildpacks = "heroku_buildpacks", heroku_buildpacks = "heroku_buildpacks",
paketo_buildpacks = "paketo_buildpacks", paketo_buildpacks = "paketo_buildpacks",
@@ -29,9 +29,18 @@ enum BuildType {
railpack = "railpack", railpack = "railpack",
} }
const buildTypeDisplayMap: Record<BuildType, string> = {
[BuildType.dockerfile]: "Dockerfile",
[BuildType.railpack]: "Railpack",
[BuildType.nixpacks]: "Nixpacks",
[BuildType.heroku_buildpacks]: "Heroku Buildpacks",
[BuildType.paketo_buildpacks]: "Paketo Buildpacks",
[BuildType.static]: "Static",
};
const mySchema = z.discriminatedUnion("buildType", [ const mySchema = z.discriminatedUnion("buildType", [
z.object({ z.object({
buildType: z.literal("dockerfile"), buildType: z.literal(BuildType.dockerfile),
dockerfile: z dockerfile: z
.string({ .string({
required_error: "Dockerfile path is required", required_error: "Dockerfile path is required",
@@ -42,39 +51,88 @@ const mySchema = z.discriminatedUnion("buildType", [
dockerBuildStage: z.string().nullable().default(""), dockerBuildStage: z.string().nullable().default(""),
}), }),
z.object({ z.object({
buildType: z.literal("heroku_buildpacks"), buildType: z.literal(BuildType.heroku_buildpacks),
herokuVersion: z.string().nullable().default(""), herokuVersion: z.string().nullable().default(""),
}), }),
z.object({ z.object({
buildType: z.literal("paketo_buildpacks"), buildType: z.literal(BuildType.paketo_buildpacks),
}), }),
z.object({ z.object({
buildType: z.literal("nixpacks"), buildType: z.literal(BuildType.nixpacks),
publishDirectory: z.string().optional(), publishDirectory: z.string().optional(),
}), }),
z.object({ z.object({
buildType: z.literal("static"), buildType: z.literal(BuildType.static),
}), }),
z.object({ z.object({
buildType: z.literal("railpack"), buildType: z.literal(BuildType.railpack),
}), }),
]); ]);
type AddTemplate = z.infer<typeof mySchema>; type AddTemplate = z.infer<typeof mySchema>;
interface Props { interface Props {
applicationId: string; applicationId: string;
} }
interface ApplicationData {
buildType: BuildType;
dockerfile?: string | null;
dockerContextPath?: string | null;
dockerBuildStage?: string | null;
herokuVersion?: string | null;
publishDirectory?: string | null;
}
function isValidBuildType(value: string): value is BuildType {
return Object.values(BuildType).includes(value as BuildType);
}
const resetData = (data: ApplicationData): AddTemplate => {
switch (data.buildType) {
case BuildType.dockerfile:
return {
buildType: BuildType.dockerfile,
dockerfile: data.dockerfile || "",
dockerContextPath: data.dockerContextPath || "",
dockerBuildStage: data.dockerBuildStage || "",
};
case BuildType.heroku_buildpacks:
return {
buildType: BuildType.heroku_buildpacks,
herokuVersion: data.herokuVersion || "",
};
case BuildType.nixpacks:
return {
buildType: BuildType.nixpacks,
publishDirectory: data.publishDirectory || undefined,
};
case BuildType.paketo_buildpacks:
return {
buildType: BuildType.paketo_buildpacks,
};
case BuildType.static:
return {
buildType: BuildType.static,
};
case BuildType.railpack:
return {
buildType: BuildType.railpack,
};
default:
const buildType = data.buildType as BuildType;
return {
buildType,
} as AddTemplate;
}
};
export const ShowBuildChooseForm = ({ applicationId }: Props) => { export const ShowBuildChooseForm = ({ applicationId }: Props) => {
const { mutateAsync, isLoading } = const { mutateAsync, isLoading } =
api.application.saveBuildType.useMutation(); api.application.saveBuildType.useMutation();
const { data, refetch } = api.application.one.useQuery( const { data, refetch } = api.application.one.useQuery(
{ { applicationId },
applicationId, { enabled: !!applicationId },
},
{
enabled: !!applicationId,
},
); );
const form = useForm<AddTemplate>({ const form = useForm<AddTemplate>({
@@ -85,46 +143,36 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => {
}); });
const buildType = form.watch("buildType"); const buildType = form.watch("buildType");
useEffect(() => { useEffect(() => {
if (data) { if (data) {
if (data.buildType === "dockerfile") { const typedData: ApplicationData = {
form.reset({ ...data,
buildType: data.buildType, buildType: isValidBuildType(data.buildType)
...(data.buildType && { ? (data.buildType as BuildType)
dockerfile: data.dockerfile || "", : BuildType.nixpacks, // fallback
dockerContextPath: data.dockerContextPath || "", };
dockerBuildStage: data.dockerBuildStage || "",
}), form.reset(resetData(typedData));
});
} else if (data.buildType === "heroku_buildpacks") {
form.reset({
buildType: data.buildType,
...(data.buildType && {
herokuVersion: data.herokuVersion || "",
}),
});
} else {
form.reset({
buildType: data.buildType,
publishDirectory: data.publishDirectory || undefined,
});
} }
} }, [data, form]);
}, [form.formState.isSubmitSuccessful, form.reset, data, form]);
const onSubmit = async (data: AddTemplate) => { const onSubmit = async (data: AddTemplate) => {
await mutateAsync({ await mutateAsync({
applicationId, applicationId,
buildType: data.buildType, buildType: data.buildType,
publishDirectory: publishDirectory:
data.buildType === "nixpacks" ? data.publishDirectory : null, data.buildType === BuildType.nixpacks ? data.publishDirectory : null,
dockerfile: data.buildType === "dockerfile" ? data.dockerfile : null, dockerfile:
data.buildType === BuildType.dockerfile ? data.dockerfile : null,
dockerContextPath: dockerContextPath:
data.buildType === "dockerfile" ? data.dockerContextPath : null, data.buildType === BuildType.dockerfile ? data.dockerContextPath : null,
dockerBuildStage: dockerBuildStage:
data.buildType === "dockerfile" ? data.dockerBuildStage : null, data.buildType === BuildType.dockerfile ? data.dockerBuildStage : null,
herokuVersion: herokuVersion:
data.buildType === "heroku_buildpacks" ? data.herokuVersion : null, data.buildType === BuildType.heroku_buildpacks
? data.herokuVersion
: null,
}) })
.then(async () => { .then(async () => {
toast.success("Build type saved"); toast.success("Build type saved");
@@ -160,8 +208,7 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => {
control={form.control} control={form.control}
name="buildType" name="buildType"
defaultValue={form.control._defaultValues.buildType} defaultValue={form.control._defaultValues.buildType}
render={({ field }) => { render={({ field }) => (
return (
<FormItem className="space-y-3"> <FormItem className="space-y-3">
<FormLabel>Build Type</FormLabel> <FormLabel>Build Type</FormLabel>
<FormControl> <FormControl>
@@ -170,183 +217,134 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => {
value={field.value} value={field.value}
className="flex flex-col space-y-1" className="flex flex-col space-y-1"
> >
<FormItem className="flex items-center space-x-3 space-y-0"> {Object.entries(buildTypeDisplayMap).map(
([value, label]) => (
<FormItem
key={value}
className="flex items-center space-x-3 space-y-0"
>
<FormControl> <FormControl>
<RadioGroupItem value="dockerfile" /> <RadioGroupItem value={value} />
</FormControl> </FormControl>
<FormLabel className="font-normal"> <FormLabel className="font-normal">
Dockerfile {label}
{value === BuildType.railpack && (
<Badge className="ml-2 px-1 text-xs">New</Badge>
)}
</FormLabel> </FormLabel>
</FormItem> </FormItem>
<FormItem className="flex items-center space-x-3 space-y-0"> ),
<FormControl> )}
<RadioGroupItem value="railpack" />
</FormControl>
<FormLabel className="font-normal">
Railpack{" "}
<Badge className="ml-1 text-xs px-1">New</Badge>
</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="nixpacks" />
</FormControl>
<FormLabel className="font-normal">
Nixpacks
</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="heroku_buildpacks" />
</FormControl>
<FormLabel className="font-normal">
Heroku Buildpacks
</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="paketo_buildpacks" />
</FormControl>
<FormLabel className="font-normal">
Paketo Buildpacks
</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="static" />
</FormControl>
<FormLabel className="font-normal">Static</FormLabel>
</FormItem>
</RadioGroup> </RadioGroup>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
); )}
}}
/> />
{buildType === "heroku_buildpacks" && ( {buildType === BuildType.heroku_buildpacks && (
<FormField <FormField
control={form.control} control={form.control}
name="herokuVersion" name="herokuVersion"
render={({ field }) => { render={({ field }) => (
return (
<FormItem> <FormItem>
<FormLabel>Heroku Version (Optional)</FormLabel> <FormLabel>Heroku Version (Optional)</FormLabel>
<FormControl> <FormControl>
<Input <Input
placeholder={"Heroku Version (Default: 24)"} placeholder="Heroku Version (Default: 24)"
{...field} {...field}
value={field.value ?? ""} value={field.value ?? ""}
/> />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
); )}
}}
/> />
)} )}
{buildType === "dockerfile" && ( {buildType === BuildType.dockerfile && (
<> <>
<FormField <FormField
control={form.control} control={form.control}
name="dockerfile" name="dockerfile"
render={({ field }) => { render={({ field }) => (
return (
<FormItem> <FormItem>
<FormLabel>Docker File</FormLabel> <FormLabel>Docker File</FormLabel>
<FormControl> <FormControl>
<Input <Input
placeholder={"Path of your docker file"} placeholder="Path of your docker file"
{...field} {...field}
value={field.value ?? ""} value={field.value ?? ""}
/> />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
); )}
}}
/> />
<FormField <FormField
control={form.control} control={form.control}
name="dockerContextPath" name="dockerContextPath"
render={({ field }) => { render={({ field }) => (
return (
<FormItem> <FormItem>
<FormLabel>Docker Context Path</FormLabel> <FormLabel>Docker Context Path</FormLabel>
<FormControl> <FormControl>
<Input <Input
placeholder={ placeholder="Path of your docker context (default: .)"
"Path of your docker context default: ."
}
{...field} {...field}
value={field.value ?? ""} value={field.value ?? ""}
/> />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
); )}
}}
/> />
<FormField <FormField
control={form.control} control={form.control}
name="dockerBuildStage" name="dockerBuildStage"
render={({ field }) => { render={({ field }) => (
return (
<FormItem> <FormItem>
<div className="space-y-0.5"> <div className="space-y-0.5">
<FormLabel>Docker Build Stage</FormLabel> <FormLabel>Docker Build Stage</FormLabel>
<FormDescription> <FormDescription>
Allows you to target a specific stage in a Allows you to target a specific stage in a Multi-stage
Multi-stage Dockerfile. If empty, Docker defaults to Dockerfile. If empty, Docker defaults to build the
build the last defined stage. last defined stage.
</FormDescription> </FormDescription>
</div> </div>
<FormControl> <FormControl>
<Input <Input
placeholder={"E.g. production"} placeholder="E.g. production"
{...field} {...field}
value={field.value ?? ""} value={field.value ?? ""}
/> />
</FormControl> </FormControl>
</FormItem> </FormItem>
); )}
}}
/> />
</> </>
)} )}
{buildType === BuildType.nixpacks && (
{buildType === "nixpacks" && (
<FormField <FormField
control={form.control} control={form.control}
name="publishDirectory" name="publishDirectory"
render={({ field }) => { render={({ field }) => (
return (
<FormItem> <FormItem>
<div className="space-y-0.5"> <div className="space-y-0.5">
<FormLabel>Publish Directory</FormLabel> <FormLabel>Publish Directory</FormLabel>
<FormDescription> <FormDescription>
Allows you to serve a single directory via NGINX after Allows you to serve a single directory via NGINX after
the build phase. Useful if the final build assets the build phase. Useful if the final build assets should
should be served as a static site. be served as a static site.
</FormDescription> </FormDescription>
</div> </div>
<FormControl> <FormControl>
<Input <Input
placeholder={"Publish Directory"} placeholder="Publish Directory"
{...field} {...field}
value={field.value ?? ""} value={field.value ?? ""}
/> />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
); )}
}}
/> />
)} )}
<div className="flex w-full justify-end"> <div className="flex w-full justify-end">