fix(admin-ui): top-5 critical review bugs (auth + AJV + TZ + idempotency + storage)

This commit is contained in:
Александр Зимин
2026-05-10 16:22:35 +00:00
parent f2614244bf
commit fc2fb35717
8 changed files with 292 additions and 72 deletions
@@ -1,5 +1,5 @@
import { useMemo, useState } from 'react'
import { useForm, Controller, type SubmitHandler } from 'react-hook-form'
import { useForm, Controller, type Path, type SubmitHandler } from 'react-hook-form'
import { useTranslation } from 'react-i18next'
import Ajv, { type ErrorObject } from 'ajv'
import addFormats from 'ajv-formats'
@@ -652,19 +652,46 @@ const applyAjvErrors = (
setError: ReturnType<typeof useForm<FormValues>>['setError'],
) => {
for (const err of errors) {
const path = err.instancePath.replace(/^\//, '').replace(/\//g, '.')
const fieldPath = path ? (`data.${path}` as `data.${string}`) : 'data'
setError(fieldPath as 'data', { type: 'ajv', message: err.message ?? 'invalid' })
// AJV instancePath: "/foo/bar" → RHF path: "data.foo.bar".
// Раньше было "data" (root) для всех ошибок из-за {@code as 'data'} cast'а
// RHF складывал все ошибки в один ключ {@code formState.errors.data},
// tab badge со счётчиком стабильно показывал 0/1, поле не подсвечивалось.
const instancePath = err.instancePath.replace(/^\//, '').replace(/\//g, '.')
// {@code required} keyword: instancePath пустой, имя missing field в
// params.missingProperty (root уровень) или params.missingProperty
// относительно instancePath (nested objects).
let path = instancePath
if (
err.keyword === 'required' &&
err.params &&
typeof err.params === 'object' &&
'missingProperty' in err.params
) {
const missing = String((err.params as { missingProperty: string }).missingProperty)
path = path ? `${path}.${missing}` : missing
}
const fieldPath = (path ? `data.${path}` : 'data') as Path<FormValues>
setError(fieldPath, { type: 'ajv', message: err.message ?? 'invalid' })
}
}
const firstFieldFromErrors = (errors: ErrorObject[]): string | null => {
for (const err of errors) {
const path = err.instancePath.replace(/^\//, '')
if (path) return path.split('/')[0]
if (err.params && typeof err.params === 'object' && 'missingProperty' in err.params) {
return String((err.params as { missingProperty: string }).missingProperty)
// required keyword: instancePath relative to parent object, missing
// property name в params.missingProperty. Combine, take top-level segment
// — это то, что нужно для tab routing.
if (
err.keyword === 'required' &&
err.params &&
typeof err.params === 'object' &&
'missingProperty' in err.params
) {
const missing = String((err.params as { missingProperty: string }).missingProperty)
const full = path ? `${path}/${missing}` : missing
return full.split('/')[0]
}
if (path) return path.split('/')[0]
}
return null
}