Display validation errors returned by the server
This commit is contained in:
@@ -13,6 +13,7 @@ All notable changes to this project will be documented in this file. This projec
|
||||
### Changed
|
||||
|
||||
### Fixed
|
||||
- Display validation errors returned by the server.
|
||||
|
||||
## [1.0.1] - 2026-04-25
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import { toast } from '@/hooks/use-toast';
|
||||
import { resolveObject, resolveSchema, resolveForm, buildCreateDefaults, deepMerge } from '@/lib/schemaResolver';
|
||||
import { calculateJmapPatch } from '@/lib/jmapPatch';
|
||||
import { jmapGet, jmapSet, getAccountId } from '@/services/jmap/client';
|
||||
import { friendlySetError } from '@/lib/jmapErrors';
|
||||
import { friendlySetError, validationErrorMessage } from '@/lib/jmapErrors';
|
||||
|
||||
import type { Field, Fields, Form, FormField } from '@/types/schema';
|
||||
import type { JmapSetError, JmapSetResponse } from '@/types/jmap';
|
||||
@@ -181,19 +181,7 @@ export function BootstrapWizard() {
|
||||
for (const ve of error.validationErrors) {
|
||||
const top = ve.property?.split('/')[0] ?? '';
|
||||
if (!top) continue;
|
||||
const msg =
|
||||
ve.type === 'Required'
|
||||
? t('form.required', 'This field is required.')
|
||||
: ve.type === 'MaxLength'
|
||||
? t('form.maxLengthIs', 'Maximum length is {{max}}.', { max: ve.required })
|
||||
: ve.type === 'MinLength'
|
||||
? t('form.minLengthIs', 'Minimum length is {{min}}.', { min: ve.required })
|
||||
: ve.type === 'MaxValue'
|
||||
? t('form.maxValueIs', 'Maximum value is {{max}}.', { max: ve.required })
|
||||
: ve.type === 'MinValue'
|
||||
? t('form.minValueIs', 'Minimum value is {{min}}.', { min: ve.required })
|
||||
: t('form.invalidValue', 'Invalid value.');
|
||||
record(top, msg);
|
||||
record(top, validationErrorMessage(ve));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ import {
|
||||
} from '@/lib/schemaResolver';
|
||||
import { jmapGet, jmapSet, jmapRequest, getAccountId } from '@/services/jmap/client';
|
||||
import { calculateJmapPatch } from '@/lib/jmapPatch';
|
||||
import { friendlySetError } from '@/lib/jmapErrors';
|
||||
import { friendlySetError, validationErrorMessage } from '@/lib/jmapErrors';
|
||||
import { coerceLabel } from '@/lib/objectOptions';
|
||||
import { SECRET_MASK } from '@/lib/jmapUtils';
|
||||
import { toast } from '@/hooks/use-toast';
|
||||
@@ -347,21 +347,12 @@ export function DynamicForm({ viewName, objectId }: DynamicFormProps) {
|
||||
if (error.validationErrors && error.validationErrors.length > 0) {
|
||||
for (const ve of error.validationErrors) {
|
||||
if (ve.property && currentFields?.properties[ve.property]) {
|
||||
const msg =
|
||||
ve.type === 'Required'
|
||||
? t('form.required', 'This field is required.')
|
||||
: ve.type === 'MaxLength'
|
||||
? t('form.maxLengthIs', 'Maximum length is {{max}}.', { max: ve.required })
|
||||
: ve.type === 'MinLength'
|
||||
? t('form.minLengthIs', 'Minimum length is {{min}}.', { min: ve.required })
|
||||
: ve.type === 'MaxValue'
|
||||
? t('form.maxValueIs', 'Maximum value is {{max}}.', { max: ve.required })
|
||||
: ve.type === 'MinValue'
|
||||
? t('form.minValueIs', 'Minimum value is {{min}}.', { min: ve.required })
|
||||
: t('form.invalidValue', 'Invalid value.');
|
||||
newFieldErrors[ve.property] = msg;
|
||||
newFieldErrors[ve.property] = validationErrorMessage(ve);
|
||||
} else if (ve.property) {
|
||||
setGeneralError((prev) => (prev ? `${prev}\n${ve.property}: ${ve.type}` : `${ve.property}: ${ve.type}`));
|
||||
const detail = ve.value && ve.value.length > 0 ? ve.value : ve.type;
|
||||
setGeneralError((prev) =>
|
||||
prev ? `${prev}\n${ve.property}: ${detail}` : `${ve.property}: ${detail}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+19
-1
@@ -4,9 +4,27 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
import type { JmapSetError } from '@/types/jmap';
|
||||
import type { JmapSetError, ValidationError } from '@/types/jmap';
|
||||
import i18n from '@/i18n';
|
||||
|
||||
export function validationErrorMessage(ve: ValidationError): string {
|
||||
switch (ve.type) {
|
||||
case 'Required':
|
||||
return i18n.t('form.required', 'This field is required.');
|
||||
case 'MaxLength':
|
||||
return i18n.t('form.maxLengthIs', 'Maximum length is {{max}}.', { max: ve.required });
|
||||
case 'MinLength':
|
||||
return i18n.t('form.minLengthIs', 'Minimum length is {{min}}.', { min: ve.required });
|
||||
case 'MaxValue':
|
||||
return i18n.t('form.maxValueIs', 'Maximum value is {{max}}.', { max: ve.required });
|
||||
case 'MinValue':
|
||||
return i18n.t('form.minValueIs', 'Minimum value is {{min}}.', { min: ve.required });
|
||||
default:
|
||||
if (ve.value && ve.value.length > 0) return ve.value;
|
||||
return i18n.t('form.invalidValue', 'Invalid value.');
|
||||
}
|
||||
}
|
||||
|
||||
export function friendlySetError(err: JmapSetError): string {
|
||||
if (err.description) return err.description;
|
||||
switch (err.type) {
|
||||
|
||||
+1
-1
@@ -60,6 +60,6 @@ export interface JmapSetError {
|
||||
export interface ValidationError {
|
||||
type: 'Invalid' | 'Required' | 'MaxLength' | 'MinLength' | 'MaxValue' | 'MinValue';
|
||||
property: string;
|
||||
value?: unknown;
|
||||
value?: string;
|
||||
required?: number;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user