import * as yup from 'yup'; import * as phoneUtil from '../phone.util'; import { ALPHA_NUMERIC_REGEX, COMMON_NAME_REGEX, EMAIL_REGEX, PASSWORD_REGEX, PERSON_NAME_REGEX, SIX_VERIFICATION_CODE_REGEX, USERNAME_REGEX, STRONG_APP_PASSWORD_REGEX, } from '../regex.util'; import { diffDateWithFormat, isValidAlpha2Code, isValidCurrencyCode } from '..'; import moment from 'moment'; declare module 'yup' { interface StringSchema { isPhone(msg: string): this; isPhoneWithCountryCode(code: string, msg: string): this; isBase64(msg: string): this; isCountryCode(msg: string): this; isCurrencyCode(msg: string): this; isValidDateRange( msg: string, fromDate: Date | string, maxRange: number, unit: string, format: string ): this; isValidDateWithFormat(msg: string, format: string): this; } interface NumberSchema { isDecimal( maxDigitsBeforeDecimal: number, maxDigitsAfterDecimal: number, errorMessage: string ): this; } interface DateSchema {} } yup.addMethod(yup.string, 'isPhone', function (msg, code) { return this.test(`phoneFormat`, msg, function (value: any) { const { path, createError } = this; try { phoneUtil.isPhoneValid(value, code); return true; } catch (error) { return createError({ path, message: msg }); } }); }); yup.addMethod( yup.number, 'isDecimal', function ( maxDigitsBeforeDecimal: number, maxDigitsAfterDecimal: number, errorMessage: string = '' ) { return this.test(`decimal`, errorMessage, function (value: any) { const { path, createError } = this; // If the value is empty, validation passes if (!value) { return true; } // If the value is not a number, throw an error if (!Number(value)) { return createError({ path, message: errorMessage }); } // Check if the number of digits before the decimal exceeds the limit if (`${value}`.replace('.', '').length > maxDigitsBeforeDecimal) { return createError({ path, message: errorMessage }); } // If the value does not contain a decimal point, add it if (!`${value}`.includes('.')) { value = `${value}.`; } // Check if the number of digits after the decimal exceeds the limit if (`${value}`.split('.')[1].length > maxDigitsAfterDecimal) { return createError({ path, message: errorMessage }); } // Validation passes if all conditions are met return true; }); } ); yup.addMethod(yup.string, 'isBase64', function (msg) { return this.test(`base64`, msg, function (value) { const { path, createError } = this; try { if (!value) return true; return Buffer.from(value, 'base64').toString('base64') === value; } catch (e) { return createError({ path, message: msg }); } }); }); yup.addMethod(yup.string, 'isCurrencyCode', function (msg = '') { return this.test(`currencyCode`, msg, function (value) { const { path, createError } = this; return isValidCurrencyCode(value || '') ? true : createError({ path, message: msg }); }); }); yup.addMethod(yup.string, 'isCountryCode', function (msg = '') { return this.test(`countryCode`, msg, function (value) { const { path, createError } = this; if (value === undefined) { return true; } return isValidAlpha2Code(value || '') ? true : createError({ path, message: msg }); }); }); yup.addMethod(yup.string, 'isPhoneWithCountryCode', function (code, msg = '') { return this.test(`phoneWithCountryCode`, msg, function (value) { const { path, createError } = this; const isValidCountryCode = isValidAlpha2Code(code || ''); if (!isValidCountryCode) return true; if (!value) return true; try { phoneUtil.isPhoneValid(value || '', code); return true; } catch (error) { return createError({ path, message: msg }); } }); }); yup.addMethod( yup.string, 'isValidDateRange', function (errorMessage, fromDate, maxRange, unit, format) { return this.test(`dateRange`, errorMessage, function (value) { console.debug( `Check is valid date range for fromDate: ${fromDate}, toDate: ${value}, format: ${format}, maxRange: ${maxRange}, unit: ${unit}, ` ); if (!value) { return true; } if (!moment(value, format, true).isValid()) { return true; } const diff = diffDateWithFormat(value, fromDate, format, unit); console.debug(`Got diff = ${diff}`); return diff <= maxRange && diff >= 0; }); } ); yup.addMethod( yup.string, 'isValidDateWithFormat', function (errorMessage, formatStr) { return this.test(`dateFormat`, errorMessage, function (value) { const { path, createError } = this; if (!value) { return true; } return moment(value, formatStr, true).isValid() ? true : createError({ path, message: errorMessage }); }); } ); // Set default message yup.setLocale({ mixed: { required({ label }) { if (label) { return `${label} is required`; } return 'This field is required'; }, }, string: { matches: 'This field is invalid format', email: 'Email is invalid', }, }); /* ========================================================= */ export const miYup = { ref: yup.ref, lazy: yup.lazy, object: () => yup.object(), array: () => yup.array(), string: () => yup.string(), number: () => yup.number(), boolean: () => yup.boolean(), requiredStr: (msg = '') => yup .string() .trim() .required(msg || undefined), requiredNumber: (msg = '') => yup.number().required(msg || undefined), requiredBoolean: (msg = '') => yup.boolean().required(msg || undefined), requiredObject: (msg = '') => yup.object().required(msg || undefined), requiredArr: (msg = '') => yup.array().required(msg || undefined), requiredDecimal: ( maxDigitsBeforeDecimal: number, maxDigitsAfterDecimal: number, msg = '' ) => yup .number() .isDecimal(maxDigitsBeforeDecimal, maxDigitsAfterDecimal, msg) .required(), optionalStr: () => yup.string().trim().optional().nullable().notRequired(), optionalNumber: () => yup.number().optional().nullable().notRequired(), optionalBoolean: () => yup.boolean().optional().nullable().notRequired(), optionalObject: () => yup.object().optional().nullable().notRequired(), optionalArr: () => yup.array().optional().nullable().notRequired(), optionalDecimal: ( maxDigitsBeforeDecimal: number, maxDigitsAfterDecimal: number, msg = '' ) => yup .number() .isDecimal(maxDigitsBeforeDecimal, maxDigitsAfterDecimal, msg) .optional() .nullable() .notRequired(), matchRegex: (reg: RegExp, msg = '') => yup .string() .trim() .matches(reg, { excludeEmptyString: true, message: msg || undefined, }), strongPassword: (msg = '') => yup .string() .trim() .matches(PASSWORD_REGEX, { excludeEmptyString: true, message: msg || undefined, }), strongMobilePassword: (msg = '') => yup .string() .trim() .matches(STRONG_APP_PASSWORD_REGEX, { excludeEmptyString: true, message: msg || 'Please enter a password between 6 and 20 characters with at least one uppercase letter, one lowercase letter, one number and one special character (@$!%*#?&)', }), sixVerificationCode: (msg = '') => yup .string() .trim() .matches(SIX_VERIFICATION_CODE_REGEX, { excludeEmptyString: true, message: msg || undefined, }), commonName: (msg = '') => yup.string().matches(COMMON_NAME_REGEX, { message: msg || undefined, excludeEmptyString: true, }), personName: (msg = '') => yup.string().matches(PERSON_NAME_REGEX, { message: msg || undefined, excludeEmptyString: true, }), username: (msg = 'Username is invalid') => yup.string().matches(USERNAME_REGEX, { message: msg || undefined, excludeEmptyString: true, }), isEmail: (msg = 'Email is invalid') => yup.string().matches(EMAIL_REGEX, { message: msg || undefined, excludeEmptyString: true, }), isUrl: (msg = '') => yup.string().url(msg || undefined), isPhone: (msg: string) => yup.string().isPhone(msg || 'Phone number is invalid format'), requiredDate: (msg = '') => yup.date().required(msg || undefined), optionalDate: () => yup.date().optional().nullable().nonNullable(), isValidDateWithFormat: (format: string, msg: string = '') => yup.string().trim().isValidDateWithFormat(msg, format), isValidDateRange: ({ msg, fromDate, maxRange, unit, format, }: { msg: string; fromDate: Date | string; maxRange: number; unit: string; format: string; }) => yup.string().trim().isValidDateRange(msg, fromDate, maxRange, unit, format), isAlphaNumeric: () => yup.string().matches(ALPHA_NUMERIC_REGEX, { excludeEmptyString: true }), isCountryCode: (msg = 'Country code is invalid') => yup.string().isCountryCode(msg), isCurrencyCode: (msg = 'Country code is invalid') => yup.string().isCurrencyCode(msg), isPhoneWithCountryCode: ( code: string, msg: string = 'Invalid phone number' ) => yup.string().isPhoneWithCountryCode(code, msg), }; export type FormSchema = yup.AnyObjectSchema | yup.AnySchema; export type StepperFormSchema = FormSchema | ((...params: any) => FormSchema);