refactor: enforce frontend view model boundaries
Frontend verify / commits (pull_request) Successful in 28s
Frontend verify / security (pull_request) Successful in 10s
Frontend verify / verify (pull_request) Successful in 8m19s

This commit is contained in:
2026-09-28 18:48:23 +03:00
parent 5b69003516
commit ca0ad4f824
287 changed files with 8558 additions and 4538 deletions
@@ -2,24 +2,28 @@ import type { FC } from "react";
import { Select, Stack, Text, TextInput } from "@mantine/core";
import { DateInput } from "@mantine/dates";
import { Controller, useFormContext } from "react-hook-form";
import { useTranslation } from "react-i18next";
import {
type BankCardValues,
bankDateInputValue,
bankDateSchema,
bankToday,
} from "../../model/form-validation";
import { bankToday } from "../../model/form-validation";
import { useBankCardFieldsVm } from "./BankCardFields.vm";
type BankCardFieldsProps = { archived: boolean };
const BankCardFields: FC<BankCardFieldsProps> = (props) => {
const {
control,
formState: { errors },
dateParser,
errors,
onOpeningDateBlur,
onOpeningDateChange,
onOpeningDateInput,
onPaymentSystemChange,
openingDateField,
openingDateValue,
paymentSystemField,
paymentSystems,
register,
} = useFormContext<BankCardValues>();
const { t } = useTranslation();
t,
} = useBankCardFieldsVm();
return (
<Stack>
<TextInput
@@ -28,37 +32,36 @@ const BankCardFields: FC<BankCardFieldsProps> = (props) => {
{...register("name")}
error={errors.name?.message}
/>
<TextInput
label={t("bank.bankName")}
{...register("bankName")}
error={errors.bankName?.message}
/>
<TextInput
inputMode="numeric"
label={t("bank.lastFour")}
{...register("lastFour")}
error={errors.lastFour?.message}
/>
<Controller
control={control}
name="paymentSystem"
render={({ field }) => (
<Select
{...field}
clearable
comboboxProps={{ withinPortal: false }}
data={["mir", "visa", "mastercard"].map(value => ({
label: t(`bank.paymentSystems.${value}`),
value,
}))}
error={errors.paymentSystem?.message}
label={t("bank.paymentSystem")}
onChange={value => field.onChange(value ?? "")}
placeholder={t("bank.paymentSystemUnknown")}
value={field.value || /* eslint-disable-line unicorn/no-null -- Mantine represents an empty controlled field as null. */ null}
/>
)}
<Select
{...paymentSystemField}
clearable
comboboxProps={{ withinPortal: false }}
data={paymentSystems}
error={errors.paymentSystem?.message}
label={t("bank.paymentSystem")}
onChange={onPaymentSystemChange}
placeholder={t("bank.paymentSystemUnknown")}
value={
paymentSystemField.value
// eslint-disable-next-line unicorn/no-null -- Mantine represents an empty controlled paymentSystemField as null.
|| null
}
/>
<TextInput
inputMode="decimal"
label={t("bank.openingBalance")}
@@ -67,43 +70,31 @@ const BankCardFields: FC<BankCardFieldsProps> = (props) => {
{...register("openingBalance")}
error={errors.openingBalance?.message}
/>
<Controller
control={control}
name="openingDate"
render={({ field }) => (
<DateInput
{...field}
dateParser={value =>
bankDateSchema.safeParse(bankDateInputValue(value)).success
? bankDateInputValue(value)
: /* eslint-disable-line unicorn/no-null -- Mantine represents an empty controlled field as null. */ null}
error={errors.openingDate?.message}
fixOnBlur={false}
label={t("bank.openingDate")}
maxDate={bankToday()}
onBlur={(event) => {
field.onChange(bankDateInputValue(event.currentTarget.value));
field.onBlur();
}}
onChange={value => field.onChange(value ?? "")}
onInput={event =>
field.onChange(bankDateInputValue(event.currentTarget.value))}
popoverProps={{
middlewares: { shift: { crossAxis: true } },
withinPortal: false,
}}
readOnly={props.archived}
required
value={
(bankDateSchema.safeParse(field.value).success) ? (field.value) : undefined
}
valueFormat="DD.MM.YYYY"
/>
)}
<DateInput
{...openingDateField}
dateParser={dateParser}
error={errors.openingDate?.message}
fixOnBlur={false}
label={t("bank.openingDate")}
maxDate={bankToday()}
onBlur={onOpeningDateBlur}
onChange={onOpeningDateChange}
onInput={onOpeningDateInput}
popoverProps={{
middlewares: { shift: { crossAxis: true } },
withinPortal: false,
}}
readOnly={props.archived}
required
value={openingDateValue}
valueFormat="DD.MM.YYYY"
/>
<Text c="dimmed" size="sm">
{t("bank.openingHint")}
</Text>
{props.archived && <Text>{t("bank.archiveHint")}</Text>}
</Stack>
);