sendou.ink/app/features/scrims/components/WithFormField.tsx
Kalle 841e4ab28a
Some checks are pending
E2E Tests / e2e (push) Waiting to run
Tests and checks on push / run-checks-and-tests (push) Waiting to run
Updates translation progress / update-translation-progress-issue (push) Waiting to run
Try to stabilize UserSearch onChange identity
These changes are not too scientific, can't reproduce this locally. Just trying something that seems like a reasonable bet.
2026-03-22 17:07:35 +02:00

149 lines
3.6 KiB
TypeScript

import * as React from "react";
import { useTranslation } from "react-i18next";
import type { z } from "zod";
import { UserSearch } from "~/components/elements/UserSearch";
import { FormMessage } from "~/components/FormMessage";
import { useUser } from "~/features/auth/core/user";
import { SCRIM } from "~/features/scrims/scrims-constants";
import {
FormFieldWrapper,
useTranslatedTexts,
} from "~/form/fields/FormFieldWrapper";
import { errorMessageId } from "~/form/utils";
import { nullFilledArray } from "~/utils/arrays";
import type { CommonUser } from "~/utils/kysely.server";
import type { fromSchema } from "../scrims-schemas";
type FromValue = z.infer<typeof fromSchema>;
interface WithFormFieldProps {
usersTeams: Array<{
id: number;
name: string;
members: Array<CommonUser>;
}>;
name: string;
value: unknown;
onChange: (value: unknown) => void;
error: string | undefined;
}
export function WithFormField({
usersTeams,
name,
value,
onChange,
error,
}: WithFormFieldProps) {
const { t } = useTranslation(["scrims"]);
const user = useUser();
const id = React.useId();
const { translatedError } = useTranslatedTexts({ error });
const fromValue = value as FromValue | null;
const handleSelectChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
if (e.target.value === "PICKUP") {
onChange({
mode: "PICKUP",
users: nullFilledArray(SCRIM.MAX_PICKUP_SIZE_EXCLUDING_OWNER),
});
return;
}
onChange({ teamId: Number(e.target.value), mode: "TEAM" });
};
const handleUserChange = React.useCallback(
(selectedUser: { id: number } | null, index: number) => {
if (!fromValue || fromValue.mode !== "PICKUP") return;
onChange({
mode: "PICKUP",
users: fromValue.users.map((u, j) =>
j === index ? selectedUser?.id : u,
),
});
},
[fromValue, onChange],
);
const selectValue = fromValue?.mode === "TEAM" ? fromValue.teamId : "PICKUP";
return (
<FormFieldWrapper
id={id}
name={name}
label={t("scrims:forms.with.title")}
error={fromValue?.mode === "TEAM" ? error : undefined}
>
<select id={id} value={selectValue} onChange={handleSelectChange}>
{usersTeams.map((team) => (
<option key={team.id} value={team.id}>
{team.name}
</option>
))}
<option value="PICKUP">{t("scrims:forms.with.pick-up")}</option>
</select>
{fromValue?.mode === "PICKUP" ? (
<div className="stack md mt-4">
<UserSearch
initialUserId={user!.id}
isDisabled
label={t("scrims:forms.with.user", { nth: 1 })}
/>
{fromValue.users.map((userId, i) => (
<PickupUserSearch
key={i}
index={i}
initialUserId={userId}
isRequired={i < 3}
label={t("scrims:forms.with.user", { nth: i + 2 })}
onUserChange={handleUserChange}
/>
))}
{translatedError ? (
<FormMessage type="error" id={errorMessageId(name)}>
{translatedError}
</FormMessage>
) : (
<FormMessage type="info">
{t("scrims:forms.with.explanation")}
</FormMessage>
)}
</div>
) : null}
</FormFieldWrapper>
);
}
function PickupUserSearch({
index,
initialUserId,
isRequired,
label,
onUserChange,
}: {
index: number;
initialUserId: number | null | undefined;
isRequired: boolean;
label: string;
onUserChange: (selectedUser: { id: number } | null, index: number) => void;
}) {
const handleChange = React.useCallback(
(selectedUser: { id: number } | null) => {
onUserChange(selectedUser, index);
},
[index, onUserChange],
);
return (
<UserSearch
initialUserId={initialUserId ?? undefined}
onChange={handleChange}
isRequired={isRequired}
label={label}
/>
);
}