mirror of
https://github.com/stashapp/stash.git
synced 2025-12-06 16:34:02 +01:00
Fix crash on blank aliases/urls (#4344)
* Fix crash on blank alias/url * Fix StringListInput clear issue
This commit is contained in:
parent
eca5838ce0
commit
d37de0e49b
11 changed files with 163 additions and 76 deletions
|
|
@ -87,7 +87,7 @@ export const GalleryEditPanel: React.FC<IProps> = ({
|
||||||
const schema = yup.object({
|
const schema = yup.object({
|
||||||
title: titleRequired ? yup.string().required() : yup.string().ensure(),
|
title: titleRequired ? yup.string().required() : yup.string().ensure(),
|
||||||
code: yup.string().ensure(),
|
code: yup.string().ensure(),
|
||||||
urls: yupUniqueStringList("urls"),
|
urls: yupUniqueStringList(intl),
|
||||||
date: yupDateString(intl),
|
date: yupDateString(intl),
|
||||||
photographer: yup.string().ensure(),
|
photographer: yup.string().ensure(),
|
||||||
rating100: yup.number().integer().nullable().defined(),
|
rating100: yup.number().integer().nullable().defined(),
|
||||||
|
|
@ -504,12 +504,7 @@ export const GalleryEditPanel: React.FC<IProps> = ({
|
||||||
{renderInputField("title")}
|
{renderInputField("title")}
|
||||||
{renderInputField("code", "text", "scene_code")}
|
{renderInputField("code", "text", "scene_code")}
|
||||||
|
|
||||||
{renderURLListField(
|
{renderURLListField("urls", onScrapeGalleryURL, urlScrapable)}
|
||||||
"urls",
|
|
||||||
"validation.urls_must_be_unique",
|
|
||||||
onScrapeGalleryURL,
|
|
||||||
urlScrapable
|
|
||||||
)}
|
|
||||||
|
|
||||||
{renderDateField("date")}
|
{renderDateField("date")}
|
||||||
{renderInputField("photographer")}
|
{renderInputField("photographer")}
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ export const ImageEditPanel: React.FC<IProps> = ({
|
||||||
const schema = yup.object({
|
const schema = yup.object({
|
||||||
title: yup.string().ensure(),
|
title: yup.string().ensure(),
|
||||||
code: yup.string().ensure(),
|
code: yup.string().ensure(),
|
||||||
urls: yupUniqueStringList("urls"),
|
urls: yupUniqueStringList(intl),
|
||||||
date: yupDateString(intl),
|
date: yupDateString(intl),
|
||||||
details: yup.string().ensure(),
|
details: yup.string().ensure(),
|
||||||
photographer: yup.string().ensure(),
|
photographer: yup.string().ensure(),
|
||||||
|
|
@ -258,7 +258,7 @@ export const ImageEditPanel: React.FC<IProps> = ({
|
||||||
{renderInputField("title")}
|
{renderInputField("title")}
|
||||||
{renderInputField("code", "text", "scene_code")}
|
{renderInputField("code", "text", "scene_code")}
|
||||||
|
|
||||||
{renderURLListField("urls", "validation.urls_must_be_unique")}
|
{renderURLListField("urls")}
|
||||||
|
|
||||||
{renderDateField("date")}
|
{renderDateField("date")}
|
||||||
{renderInputField("photographer")}
|
{renderInputField("photographer")}
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ export const PerformerEditPanel: React.FC<IPerformerDetails> = ({
|
||||||
const schema = yup.object({
|
const schema = yup.object({
|
||||||
name: yup.string().required(),
|
name: yup.string().required(),
|
||||||
disambiguation: yup.string().ensure(),
|
disambiguation: yup.string().ensure(),
|
||||||
alias_list: yupUniqueAliases("alias_list", "name"),
|
alias_list: yupUniqueAliases(intl, "name"),
|
||||||
gender: yupInputEnum(GQL.GenderEnum).nullable().defined(),
|
gender: yupInputEnum(GQL.GenderEnum).nullable().defined(),
|
||||||
birthdate: yupDateString(intl),
|
birthdate: yupDateString(intl),
|
||||||
death_date: yupDateString(intl),
|
death_date: yupDateString(intl),
|
||||||
|
|
@ -755,11 +755,7 @@ export const PerformerEditPanel: React.FC<IPerformerDetails> = ({
|
||||||
{renderInputField("name")}
|
{renderInputField("name")}
|
||||||
{renderInputField("disambiguation")}
|
{renderInputField("disambiguation")}
|
||||||
|
|
||||||
{renderStringListField(
|
{renderStringListField("alias_list", "aliases")}
|
||||||
"alias_list",
|
|
||||||
"validation.aliases_must_be_unique",
|
|
||||||
"aliases"
|
|
||||||
)}
|
|
||||||
|
|
||||||
{renderSelectField("gender", stringGenderMap)}
|
{renderSelectField("gender", stringGenderMap)}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,7 @@ export const SceneEditPanel: React.FC<IProps> = ({
|
||||||
const schema = yup.object({
|
const schema = yup.object({
|
||||||
title: yup.string().ensure(),
|
title: yup.string().ensure(),
|
||||||
code: yup.string().ensure(),
|
code: yup.string().ensure(),
|
||||||
urls: yupUniqueStringList("urls"),
|
urls: yupUniqueStringList(intl),
|
||||||
date: yupDateString(intl),
|
date: yupDateString(intl),
|
||||||
director: yup.string().ensure(),
|
director: yup.string().ensure(),
|
||||||
rating100: yup.number().integer().nullable().defined(),
|
rating100: yup.number().integer().nullable().defined(),
|
||||||
|
|
@ -824,12 +824,7 @@ export const SceneEditPanel: React.FC<IProps> = ({
|
||||||
{renderInputField("title")}
|
{renderInputField("title")}
|
||||||
{renderInputField("code", "text", "scene_code")}
|
{renderInputField("code", "text", "scene_code")}
|
||||||
|
|
||||||
{renderURLListField(
|
{renderURLListField("urls", onScrapeSceneURL, urlScrapable)}
|
||||||
"urls",
|
|
||||||
"validation.urls_must_be_unique",
|
|
||||||
onScrapeSceneURL,
|
|
||||||
urlScrapable
|
|
||||||
)}
|
|
||||||
|
|
||||||
{renderDateField("date")}
|
{renderDateField("date")}
|
||||||
{renderInputField("director")}
|
{renderInputField("director")}
|
||||||
|
|
|
||||||
|
|
@ -53,12 +53,14 @@ export const StringListInput: React.FC<IStringListInputProps> = (props) => {
|
||||||
const values = props.value.concat("");
|
const values = props.value.concat("");
|
||||||
|
|
||||||
function valueChanged(idx: number, value: string) {
|
function valueChanged(idx: number, value: string) {
|
||||||
const newValues = values
|
const newValues = props.value.slice();
|
||||||
.map((v, i) => {
|
newValues[idx] = value;
|
||||||
const ret = idx !== i ? v : value;
|
|
||||||
return ret;
|
// if we cleared the last string, delete it from the array entirely
|
||||||
})
|
if (!value && idx === newValues.length - 1) {
|
||||||
.filter((v, i) => i < values.length - 2 || v);
|
newValues.splice(newValues.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
props.setValue(newValues);
|
props.setValue(newValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ export const StudioEditPanel: React.FC<IStudioEditPanel> = ({
|
||||||
url: yup.string().ensure(),
|
url: yup.string().ensure(),
|
||||||
details: yup.string().ensure(),
|
details: yup.string().ensure(),
|
||||||
parent_id: yup.string().required().nullable(),
|
parent_id: yup.string().required().nullable(),
|
||||||
aliases: yupUniqueAliases("aliases", "name"),
|
aliases: yupUniqueAliases(intl, "name"),
|
||||||
ignore_auto_tag: yup.boolean().defined(),
|
ignore_auto_tag: yup.boolean().defined(),
|
||||||
stash_ids: yup.mixed<GQL.StashIdInput[]>().defined(),
|
stash_ids: yup.mixed<GQL.StashIdInput[]>().defined(),
|
||||||
image: yup.string().nullable().optional(),
|
image: yup.string().nullable().optional(),
|
||||||
|
|
@ -158,7 +158,7 @@ export const StudioEditPanel: React.FC<IStudioEditPanel> = ({
|
||||||
|
|
||||||
<Form noValidate onSubmit={formik.handleSubmit} id="studio-edit">
|
<Form noValidate onSubmit={formik.handleSubmit} id="studio-edit">
|
||||||
{renderInputField("name")}
|
{renderInputField("name")}
|
||||||
{renderStringListField("aliases", "validation.aliases_must_be_unique")}
|
{renderStringListField("aliases")}
|
||||||
{renderInputField("url")}
|
{renderInputField("url")}
|
||||||
{renderInputField("details", "textarea")}
|
{renderInputField("details", "textarea")}
|
||||||
{renderParentStudioField()}
|
{renderParentStudioField()}
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ export const TagEditPanel: React.FC<ITagEditPanel> = ({
|
||||||
|
|
||||||
const schema = yup.object({
|
const schema = yup.object({
|
||||||
name: yup.string().required(),
|
name: yup.string().required(),
|
||||||
aliases: yupUniqueAliases("aliases", "name"),
|
aliases: yupUniqueAliases(intl, "name"),
|
||||||
description: yup.string().ensure(),
|
description: yup.string().ensure(),
|
||||||
parent_ids: yup.array(yup.string().required()).defined(),
|
parent_ids: yup.array(yup.string().required()).defined(),
|
||||||
child_ids: yup.array(yup.string().required()).defined(),
|
child_ids: yup.array(yup.string().required()).defined(),
|
||||||
|
|
@ -186,7 +186,7 @@ export const TagEditPanel: React.FC<ITagEditPanel> = ({
|
||||||
|
|
||||||
<Form noValidate onSubmit={formik.handleSubmit} id="tag-edit">
|
<Form noValidate onSubmit={formik.handleSubmit} id="tag-edit">
|
||||||
{renderInputField("name")}
|
{renderInputField("name")}
|
||||||
{renderStringListField("aliases", "validation.aliases_must_be_unique")}
|
{renderStringListField("aliases")}
|
||||||
{renderInputField("description", "textarea")}
|
{renderInputField("description", "textarea")}
|
||||||
{renderParentTagsField()}
|
{renderParentTagsField()}
|
||||||
{renderSubTagsField()}
|
{renderSubTagsField()}
|
||||||
|
|
|
||||||
|
|
@ -1346,6 +1346,7 @@ dl.details-list {
|
||||||
|
|
||||||
.invalid-feedback {
|
.invalid-feedback {
|
||||||
display: block;
|
display: block;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
|
||||||
&:empty {
|
&:empty {
|
||||||
display: none;
|
display: none;
|
||||||
|
|
|
||||||
|
|
@ -1390,11 +1390,10 @@
|
||||||
"url": "URL",
|
"url": "URL",
|
||||||
"urls": "URLs",
|
"urls": "URLs",
|
||||||
"validation": {
|
"validation": {
|
||||||
"aliases_must_be_unique": "aliases must be unique",
|
"blank": "${path} must not be blank",
|
||||||
"date_invalid_form": "${path} must be in YYYY-MM-DD form",
|
"date_invalid_form": "${path} must be in YYYY-MM-DD form",
|
||||||
"required": "${path} is a required field",
|
"required": "${path} is a required field",
|
||||||
"unique": "${path} must be unique",
|
"unique": "${path} must be unique"
|
||||||
"urls_must_be_unique": "URLs must be unique"
|
|
||||||
},
|
},
|
||||||
"videos": "Videos",
|
"videos": "Videos",
|
||||||
"video_codec": "Video Codec",
|
"video_codec": "Video Codec",
|
||||||
|
|
|
||||||
|
|
@ -64,10 +64,11 @@ export function formikUtils<V extends FormikValues>(
|
||||||
}: IProps = {}
|
}: IProps = {}
|
||||||
) {
|
) {
|
||||||
type Field = keyof V & string;
|
type Field = keyof V & string;
|
||||||
|
type ErrorMessage = string | undefined;
|
||||||
|
|
||||||
function renderFormControl(field: Field, type: string, placeholder: string) {
|
function renderFormControl(field: Field, type: string, placeholder: string) {
|
||||||
const formikProps = formik.getFieldProps({ name: field, type: type });
|
const formikProps = formik.getFieldProps({ name: field, type: type });
|
||||||
const { error } = formik.getFieldMeta(field);
|
const error = formik.errors[field] as ErrorMessage;
|
||||||
|
|
||||||
let { value } = formikProps;
|
let { value } = formikProps;
|
||||||
if (value === null) {
|
if (value === null) {
|
||||||
|
|
@ -181,7 +182,7 @@ export function formikUtils<V extends FormikValues>(
|
||||||
props?: IProps
|
props?: IProps
|
||||||
) {
|
) {
|
||||||
const value = formik.values[field] as string;
|
const value = formik.values[field] as string;
|
||||||
const { error } = formik.getFieldMeta(field);
|
const error = formik.errors[field] as ErrorMessage;
|
||||||
|
|
||||||
const title = intl.formatMessage({ id: messageID });
|
const title = intl.formatMessage({ id: messageID });
|
||||||
const control = (
|
const control = (
|
||||||
|
|
@ -201,7 +202,7 @@ export function formikUtils<V extends FormikValues>(
|
||||||
props?: IProps
|
props?: IProps
|
||||||
) {
|
) {
|
||||||
const value = formik.values[field] as number | null;
|
const value = formik.values[field] as number | null;
|
||||||
const { error } = formik.getFieldMeta(field);
|
const error = formik.errors[field] as ErrorMessage;
|
||||||
|
|
||||||
const title = intl.formatMessage({ id: messageID });
|
const title = intl.formatMessage({ id: messageID });
|
||||||
const control = (
|
const control = (
|
||||||
|
|
@ -233,24 +234,43 @@ export function formikUtils<V extends FormikValues>(
|
||||||
return renderField(field, title, control, props);
|
return renderField(field, title, control, props);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// flattens a potential list of errors into a [errorMsg, errorIdx] tuple
|
||||||
|
// error messages are joined with newlines, and duplicate messages are skipped
|
||||||
|
function flattenError(
|
||||||
|
error: ErrorMessage[] | ErrorMessage
|
||||||
|
): [string | undefined, number[] | undefined] {
|
||||||
|
if (Array.isArray(error)) {
|
||||||
|
let errors: string[] = [];
|
||||||
|
const errorIdx = [];
|
||||||
|
for (let i = 0; i < error.length; i++) {
|
||||||
|
const err = error[i];
|
||||||
|
if (err) {
|
||||||
|
if (!errors.includes(err)) {
|
||||||
|
errors.push(err);
|
||||||
|
}
|
||||||
|
errorIdx.push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [errors.join("\n"), errorIdx];
|
||||||
|
} else {
|
||||||
|
return [error, undefined];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function renderStringListField(
|
function renderStringListField(
|
||||||
field: Field,
|
field: Field,
|
||||||
errorMessageID: string,
|
|
||||||
messageID: string = field,
|
messageID: string = field,
|
||||||
props?: IProps
|
props?: IProps
|
||||||
) {
|
) {
|
||||||
const formikProps = formik.getFieldProps(field);
|
const value = formik.values[field] as string[];
|
||||||
const { error } = formik.getFieldMeta(field);
|
const error = formik.errors[field] as ErrorMessage[] | ErrorMessage;
|
||||||
|
|
||||||
const errorMsg = error
|
const [errorMsg, errorIdx] = flattenError(error);
|
||||||
? intl.formatMessage({ id: errorMessageID })
|
|
||||||
: undefined;
|
|
||||||
const errorIdx = error?.split(" ").map((e) => parseInt(e));
|
|
||||||
|
|
||||||
const title = intl.formatMessage({ id: messageID });
|
const title = intl.formatMessage({ id: messageID });
|
||||||
const control = (
|
const control = (
|
||||||
<StringListInput
|
<StringListInput
|
||||||
value={formikProps.value ?? []}
|
value={value}
|
||||||
setValue={(v) => formik.setFieldValue(field, v)}
|
setValue={(v) => formik.setFieldValue(field, v)}
|
||||||
errors={errorMsg}
|
errors={errorMsg}
|
||||||
errorIdx={errorIdx}
|
errorIdx={errorIdx}
|
||||||
|
|
@ -262,19 +282,15 @@ export function formikUtils<V extends FormikValues>(
|
||||||
|
|
||||||
function renderURLListField(
|
function renderURLListField(
|
||||||
field: Field,
|
field: Field,
|
||||||
errorMessageID: string,
|
|
||||||
onScrapeClick?: (url: string) => void,
|
onScrapeClick?: (url: string) => void,
|
||||||
urlScrapable?: (url: string) => boolean,
|
urlScrapable?: (url: string) => boolean,
|
||||||
messageID: string = field,
|
messageID: string = field,
|
||||||
props?: IProps
|
props?: IProps
|
||||||
) {
|
) {
|
||||||
const value = formik.values[field] as string[];
|
const value = formik.values[field] as string[];
|
||||||
const { error } = formik.getFieldMeta(field);
|
const error = formik.errors[field] as ErrorMessage[] | ErrorMessage;
|
||||||
|
|
||||||
const errorMsg = error
|
const [errorMsg, errorIdx] = flattenError(error);
|
||||||
? intl.formatMessage({ id: errorMessageID })
|
|
||||||
: undefined;
|
|
||||||
const errorIdx = error?.split(" ").map((e) => parseInt(e));
|
|
||||||
|
|
||||||
const title = intl.formatMessage({ id: messageID });
|
const title = intl.formatMessage({ id: messageID });
|
||||||
const control = (
|
const control = (
|
||||||
|
|
|
||||||
|
|
@ -2,48 +2,131 @@ import { FormikErrors, yupToFormErrors } from "formik";
|
||||||
import { IntlShape } from "react-intl";
|
import { IntlShape } from "react-intl";
|
||||||
import * as yup from "yup";
|
import * as yup from "yup";
|
||||||
|
|
||||||
export function yupUniqueStringList(fieldName: string) {
|
// equivalent to yup.array(yup.string().required())
|
||||||
|
// except that error messages will be e.g.
|
||||||
|
// 'urls must not be blank' instead of
|
||||||
|
// 'urls["0"] is a required field'
|
||||||
|
export function yupRequiredStringArray(intl: IntlShape) {
|
||||||
return yup
|
return yup
|
||||||
.array(yup.string().required())
|
.array(
|
||||||
.defined()
|
// we enforce that each string in the array is "required" in the outer test function
|
||||||
|
// so cast to avoid having to add a redundant `.required()` here
|
||||||
|
yup.string() as yup.StringSchema<string>
|
||||||
|
)
|
||||||
.test({
|
.test({
|
||||||
name: "unique",
|
name: "blank",
|
||||||
test: (value) => {
|
test(value) {
|
||||||
const values: string[] = [];
|
if (!value || !value.length) return true;
|
||||||
const dupes: number[] = [];
|
|
||||||
|
const blanks: number[] = [];
|
||||||
for (let i = 0; i < value.length; i++) {
|
for (let i = 0; i < value.length; i++) {
|
||||||
const a = value[i];
|
const s = value[i];
|
||||||
if (values.includes(a)) {
|
if (!s) {
|
||||||
dupes.push(i);
|
blanks.push(i);
|
||||||
} else {
|
|
||||||
values.push(a);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (dupes.length === 0) return true;
|
if (blanks.length === 0) return true;
|
||||||
return new yup.ValidationError(dupes.join(" "), value, fieldName);
|
|
||||||
|
// each error message is identical
|
||||||
|
const msg = yup.ValidationError.formatError(
|
||||||
|
intl.formatMessage({ id: "validation.blank" }),
|
||||||
|
{
|
||||||
|
label: this.schema.spec.label,
|
||||||
|
path: this.path,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// return multiple errors, one for each blank string
|
||||||
|
const errors = blanks.map(
|
||||||
|
(i) =>
|
||||||
|
new yup.ValidationError(
|
||||||
|
msg,
|
||||||
|
value[i],
|
||||||
|
// the path to this "sub-error": e.g. 'urls["0"]'
|
||||||
|
`${this.path}["${i}"]`,
|
||||||
|
"blank"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return new yup.ValidationError(errors, value, this.path, "blank");
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function yupUniqueAliases(fieldName: string, nameField: string) {
|
export function yupUniqueStringList(intl: IntlShape) {
|
||||||
return yup
|
return yupRequiredStringArray(intl)
|
||||||
.array(yup.string().required())
|
|
||||||
.defined()
|
.defined()
|
||||||
.test({
|
.test({
|
||||||
name: "unique",
|
name: "unique",
|
||||||
test: (value, context) => {
|
test(value) {
|
||||||
const aliases = [context.parent[nameField].toLowerCase()];
|
const values: string[] = [];
|
||||||
const dupes: number[] = [];
|
const dupes: number[] = [];
|
||||||
for (let i = 0; i < value.length; i++) {
|
for (let i = 0; i < value.length; i++) {
|
||||||
const a = value[i].toLowerCase();
|
const s = value[i];
|
||||||
if (aliases.includes(a)) {
|
if (values.includes(s)) {
|
||||||
dupes.push(i);
|
dupes.push(i);
|
||||||
} else {
|
} else {
|
||||||
aliases.push(a);
|
values.push(s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (dupes.length === 0) return true;
|
if (dupes.length === 0) return true;
|
||||||
return new yup.ValidationError(dupes.join(" "), value, fieldName);
|
|
||||||
|
const msg = yup.ValidationError.formatError(
|
||||||
|
intl.formatMessage({ id: "validation.unique" }),
|
||||||
|
{
|
||||||
|
label: this.schema.spec.label,
|
||||||
|
path: this.path,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const errors = dupes.map(
|
||||||
|
(i) =>
|
||||||
|
new yup.ValidationError(
|
||||||
|
msg,
|
||||||
|
value[i],
|
||||||
|
`${this.path}["${i}"]`,
|
||||||
|
"unique"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return new yup.ValidationError(errors, value, this.path, "unique");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function yupUniqueAliases(intl: IntlShape, nameField: string) {
|
||||||
|
return yupRequiredStringArray(intl)
|
||||||
|
.defined()
|
||||||
|
.test({
|
||||||
|
name: "unique",
|
||||||
|
test(value) {
|
||||||
|
const aliases = [this.parent[nameField].toLowerCase()];
|
||||||
|
const dupes: number[] = [];
|
||||||
|
for (let i = 0; i < value.length; i++) {
|
||||||
|
const s = value[i].toLowerCase();
|
||||||
|
if (aliases.includes(s)) {
|
||||||
|
dupes.push(i);
|
||||||
|
} else {
|
||||||
|
aliases.push(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dupes.length === 0) return true;
|
||||||
|
|
||||||
|
const msg = yup.ValidationError.formatError(
|
||||||
|
intl.formatMessage({ id: "validation.unique" }),
|
||||||
|
{
|
||||||
|
label: this.schema.spec.label,
|
||||||
|
path: this.path,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const errors = dupes.map(
|
||||||
|
(i) =>
|
||||||
|
new yup.ValidationError(
|
||||||
|
msg,
|
||||||
|
value[i],
|
||||||
|
`${this.path}["${i}"]`,
|
||||||
|
"unique"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return new yup.ValidationError(errors, value, this.path, "unique");
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -54,7 +137,7 @@ export function yupDateString(intl: IntlShape) {
|
||||||
.ensure()
|
.ensure()
|
||||||
.test({
|
.test({
|
||||||
name: "date",
|
name: "date",
|
||||||
test: (value) => {
|
test(value) {
|
||||||
if (!value) return true;
|
if (!value) return true;
|
||||||
if (!value.match(/^\d{4}-\d{2}-\d{2}$/)) return false;
|
if (!value.match(/^\d{4}-\d{2}-\d{2}$/)) return false;
|
||||||
if (Number.isNaN(Date.parse(value))) return false;
|
if (Number.isNaN(Date.parse(value))) return false;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue