Files
frontend/src/entities/bank-card/model/form-validation.ts
T
ksv741 f6db28defa
Frontend security / security (push) Successful in 10s
Frontend verify / verify (push) Successful in 4m57s
Conventional commits / commits (pull_request) Successful in 28s
Frontend security / security (pull_request) Successful in 10s
Frontend verify / verify (pull_request) Successful in 5m10s
fix: resolve frontend audit findings
Repair investment recovery, draft preservation, date validation,
currency presentation, accessibility, and route error recovery.
Correct Docker and dependency checks and consume explicit income roles.
2026-09-27 11:38:20 +03:00

110 lines
2.8 KiB
TypeScript

import { z } from "zod";
import { validationMessage } from "@/shared/i18n";
const ISO_MONTH_LENGTH = 7;
const MAX_CARD_NAME_LENGTH = 100;
function bankToday() {
return new Intl.DateTimeFormat("en-CA", {
timeZone: "Europe/Moscow",
year: "numeric",
month: "2-digit",
day: "2-digit",
}).format(new Date());
}
function bankDateInputValue(value: string) {
return value.replace(/^(\d{2})\.(\d{2})\.(\d{4})$/, "$3-$2-$1");
}
function bankMonth(value: string | null) {
return value && value >= "0001-01" && /^\d{4}-(0[1-9]|1[0-2])$/.test(value)
? value
: bankToday().slice(0, ISO_MONTH_LENGTH);
}
function bankMoneyValue(value: string) {
return value
.trim()
.replace(",", ".")
.replace(/^(-?)0+(?=\d)/, "$1");
}
function moneyCents(value: string): bigint | null {
const normalized = value.trim().replace(",", ".");
if (!/^-?\d+(\.\d{1,2})?$/.test(normalized)) return null;
const [whole, fraction = ""] = normalized.replace("-", "").split(".");
return (
BigInt(`${whole}${fraction.padEnd(2, "0")}`) *
(normalized.startsWith("-") ? -1n : 1n)
);
}
const MIN_BALANCE_KOPECKS = -9_223_372_036_854_775_808n;
const MAX_BALANCE_KOPECKS = 9_223_372_036_854_775_807n;
const moneySchema = z.string().refine(
(value) => {
const cents = moneyCents(value);
return (
cents !== null &&
cents >= MIN_BALANCE_KOPECKS &&
cents <= MAX_BALANCE_KOPECKS
);
},
{ error: validationMessage("bank.validation.money") },
);
const positiveMoneySchema = moneySchema.refine(
(value) => {
const cents = moneyCents(value);
return cents === null || cents > 0n;
},
{ error: validationMessage("bank.validation.positive") },
);
const bankDateSchema = z
.string()
.refine(
(value) =>
/^\d{4}-\d{2}-\d{2}$/.test(value) &&
value >= "0001-01-01" &&
!Number.isNaN(Date.parse(`${value}T00:00:00Z`)) &&
new Date(`${value}T00:00:00Z`).toISOString().slice(0, 10) === value &&
value <= bankToday(),
{ error: validationMessage("bank.validation.date") },
);
const bankCardSchema = z.object({
name: z
.string()
.trim()
.min(1, validationMessage("bank.validation.name")(undefined))
.max(
MAX_CARD_NAME_LENGTH,
validationMessage("bank.validation.name")(undefined),
),
bankName: z
.string()
.trim()
.max(
MAX_CARD_NAME_LENGTH,
validationMessage("bank.validation.bankName")(undefined),
),
lastFour: z.string().refine((value) => !value || /^\d{4}$/.test(value), {
error: validationMessage("bank.validation.lastFour"),
}),
paymentSystem: z
.enum(["", "mir", "visa", "mastercard"], {
error: validationMessage("bank.validation.paymentSystem"),
})
.optional(),
openingBalance: moneySchema,
openingDate: bankDateSchema,
});
type BankCardValues = z.infer<typeof bankCardSchema>;
export {
type BankCardValues,
bankCardSchema,
bankDateInputValue,
bankDateSchema,
bankMoneyValue,
bankMonth,
bankToday,
moneyCents,
positiveMoneySchema,
};