fix(admin-ui): top-5 critical review bugs (auth + AJV + TZ + idempotency + storage)
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user