sendou.ink/app/features/user-page/components/NewBuildForm.tsx
2026-01-18 18:21:19 +02:00

165 lines
3.9 KiB
TypeScript

import * as React from "react";
import { useTranslation } from "react-i18next";
import { AbilitiesSelector } from "~/components/AbilitiesSelector";
import { GearSelect } from "~/components/GearSelect";
import type { GearType } from "~/db/tables";
import type { CustomFieldRenderProps } from "~/form/FormField";
import { FormFieldWrapper } from "~/form/fields/FormFieldWrapper";
import { SendouForm, useFormFieldContext } from "~/form/SendouForm";
import { rankedModesShort } from "~/modules/in-game-lists/modes";
import type { BuildAbilitiesTupleWithUnknown } from "~/modules/in-game-lists/types";
import { newBuildSchema } from "../user-page-schemas";
interface NewBuildFormProps {
defaultValues?: Parameters<typeof SendouForm>[0]["defaultValues"];
gearIdToAbilities: Record<string, BuildAbilitiesTupleWithUnknown[number]>;
isEditing?: boolean;
}
export function NewBuildForm({
defaultValues,
gearIdToAbilities,
isEditing,
}: NewBuildFormProps) {
const { t } = useTranslation(["builds"]);
return (
<SendouForm
schema={newBuildSchema}
defaultValues={
defaultValues ?? {
modes: [...rankedModesShort],
}
}
title={t(
isEditing ? "builds:forms.title.edit" : "builds:forms.title.new",
)}
>
{({ FormField }) => (
<>
<FormField name="weapons" />
<FormField name="head">
{(props: CustomFieldRenderProps) => (
<GearFormField
type="HEAD"
gearIdToAbilities={gearIdToAbilities}
{...props}
/>
)}
</FormField>
<FormField name="clothes">
{(props: CustomFieldRenderProps) => (
<GearFormField
type="CLOTHES"
gearIdToAbilities={gearIdToAbilities}
{...props}
/>
)}
</FormField>
<FormField name="shoes">
{(props: CustomFieldRenderProps) => (
<GearFormField
type="SHOES"
gearIdToAbilities={gearIdToAbilities}
{...props}
/>
)}
</FormField>
<FormField name="abilities">
{(props: CustomFieldRenderProps) => (
<AbilitiesFormField {...props} />
)}
</FormField>
<FormField name="title" />
<FormField name="description" />
<FormField name="modes" />
<FormField name="private" />
</>
)}
</SendouForm>
);
}
function GearFormField({
type,
name,
value,
onChange,
error,
gearIdToAbilities,
}: {
type: GearType;
name: string;
value: unknown;
onChange: (value: unknown) => void;
error: string | undefined;
gearIdToAbilities: Record<string, BuildAbilitiesTupleWithUnknown[number]>;
}) {
const { t } = useTranslation("builds");
const { values, setValue } = useFormFieldContext();
const id = React.useId();
const handleChange = (gearId: number | null) => {
onChange(gearId);
if (!gearId) return;
const abilitiesFromExistingGear = gearIdToAbilities[`${type}_${gearId}`];
if (!abilitiesFromExistingGear) return;
const abilities = values.abilities as BuildAbilitiesTupleWithUnknown;
const gearIndex = type === "HEAD" ? 0 : type === "CLOTHES" ? 1 : 2;
const currentAbilities = abilities[gearIndex];
if (!currentAbilities.every((a) => a === "UNKNOWN")) return;
const newAbilities = structuredClone(abilities);
newAbilities[gearIndex] = abilitiesFromExistingGear;
setValue("abilities", newAbilities);
};
return (
<FormFieldWrapper
id={id}
name={name}
label={t(`forms.gear.${type}`)}
error={error}
>
<GearSelect
type={type}
value={value as number | null}
clearable
onChange={handleChange}
/>
</FormFieldWrapper>
);
}
function AbilitiesFormField({
name,
value,
onChange,
error,
}: {
name: string;
value: unknown;
onChange: (value: unknown) => void;
error: string | undefined;
}) {
const { t } = useTranslation("builds");
return (
<FormFieldWrapper
id={`${name}-abilities`}
name={name}
label={t("forms.abilities")}
error={error}
>
<AbilitiesSelector
selectedAbilities={value as BuildAbilitiesTupleWithUnknown}
onChange={onChange}
/>
</FormFieldWrapper>
);
}