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
Repair investment recovery, draft preservation, date validation, currency presentation, accessibility, and route error recovery. Correct Docker and dependency checks and consume explicit income roles.
110 lines
2.8 KiB
TypeScript
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,
|
|
};
|