Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions changelog/8282-fix-phone-validation-custom-fields.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
type: Fixed
description: Fixed custom privacy request fields named "phone" or "email" incorrectly receiving E.164/email format validation when those identity inputs were not configured; also improved phone validation error message to specify expected format
pr: 8282
labels: []
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const generateFormRulesFromAction = (

const phonePattern = /^\+?[1-9]\d{1,14}$/;
const phoneMessage =
"Phone number must be formatted correctly (e.g. 15555555555)";
"Phone must be in E.164 format (e.g. +15551234567 or 15551234567)";

if (action.identity_inputs?.phone === "required") {
rules["identity.phone_number"] = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,33 +64,42 @@ const useConsentRequestForm = ({

const initialValues = useMemo(() => getInitialValues(), [getInitialValues]);

// Build the static portion of the validation schema (identity fields)
const identityValidationSchema = useMemo(
() =>
Yup.object().shape({
email: emailValidation(identityInputs?.email!).test(
"one of email or phone entered",
"You must enter an email",
(_value, context) => {
if (identityInputs?.email === "required") {
return Boolean(context.parent.email);
}
return true;
},
),
phone: phoneValidation(identityInputs?.phone!).test(
"one of email or phone entered",
"You must enter a phone number",
(_value, context) => {
if (identityInputs?.phone === "required") {
return Boolean(context.parent.phone);
}
return true;
},
),
}),
[identityInputs?.email, identityInputs?.phone],
);
// Build the static portion of the validation schema (identity fields).
// Only include validation rules for identity fields that are actually configured,
// to avoid conflicts with custom_privacy_request_fields that may use the same keys.
const identityValidationSchema = useMemo(() => {
const schemaFields: Record<string, Yup.StringSchema> = {};

// Only add email validation if email is configured in identity_inputs
if (identityInputs?.email) {
schemaFields.email = emailValidation(identityInputs.email).test(
"one of email or phone entered",
"You must enter an email",
(_value, context) => {
if (identityInputs.email === "required") {
return Boolean(context.parent.email);
}
return true;
},
);
}

// Only add phone validation if phone is configured in identity_inputs
if (identityInputs?.phone) {
schemaFields.phone = phoneValidation(identityInputs.phone).test(
"one of email or phone entered",
"You must enter a phone number",
(_value, context) => {
if (identityInputs.phone === "required") {
return Boolean(context.parent.phone);
}
return true;
},
);
}

return Yup.object().shape(schemaFields);
}, [identityInputs?.email, identityInputs?.phone]);

const { validate, applicableFieldsRef, validationError } =
useConditionalValidate({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,54 +108,63 @@ const usePrivacyRequestForm = ({

const initialValues = useMemo(() => getInitialValues(), [getInitialValues]);

// Build the static portion of the validation schema (identity fields)
const identityValidationSchema = useMemo(
() =>
Yup.object().shape({
name: nameValidation(nameInput),
email: emailValidation(emailInput).test(
"one of email or phone entered",
"You must enter either email or phone",
(_value, context) => {
if (emailInput === "optional" && phoneInput === "optional") {
return Boolean(context.parent.phone || context.parent.email);
}
return true;
},
),
phone: phoneValidation(phoneInput).test(
"one of email or phone entered",
"You must enter either email or phone",
(_value, context) => {
if (emailInput === "optional" && phoneInput === "optional") {
return Boolean(context.parent.phone || context.parent.email);
}
return true;
},
),
...Object.fromEntries(
Object.entries(customIdentityFields).flatMap(([key, value]) => {
if (!value) {
return [];
}
if (value.field_type === "date") {
return [
[
key,
dateFieldValidation(
value,
value.label,
value.required !== false,
),
],
];
}
return [[key, Yup.string().required(`${value.label} is required`)]];
}),
),
}),
[emailInput, phoneInput, nameInput, customIdentityFields],
);
// Build the static portion of the validation schema (identity fields).
// Only include validation rules for identity fields that are actually configured,
// to avoid conflicts with custom_privacy_request_fields that may use the same keys.
const identityValidationSchema = useMemo(() => {
const schemaFields: Record<string, Yup.StringSchema> = {};

// Only add name validation if name is configured in identity_inputs
if (nameInput) {
schemaFields.name = nameValidation(nameInput);
}

// Only add email validation if email is configured in identity_inputs
if (emailInput) {
schemaFields.email = emailValidation(emailInput).test(
"one of email or phone entered",
"You must enter either email or phone",
(_value, context) => {
if (emailInput === "optional" && phoneInput === "optional") {
return Boolean(context.parent.phone || context.parent.email);
}
return true;
},
);
}

// Only add phone validation if phone is configured in identity_inputs
if (phoneInput) {
schemaFields.phone = phoneValidation(phoneInput).test(
"one of email or phone entered",
"You must enter either email or phone",
(_value, context) => {
if (emailInput === "optional" && phoneInput === "optional") {
return Boolean(context.parent.phone || context.parent.email);
}
return true;
},
);
}

// Add custom identity field validations
Object.entries(customIdentityFields).forEach(([key, value]) => {
if (!value) {
return;
}
if (value.field_type === "date") {
schemaFields[key] = dateFieldValidation(
value,
value.label,
value.required !== false,
);
} else {
schemaFields[key] = Yup.string().required(`${value.label} is required`);
}
});

return Yup.object().shape(schemaFields);
}, [emailInput, phoneInput, nameInput, customIdentityFields]);

const { validate, applicableFieldsRef, validationError } =
useConditionalValidate({
Expand Down
2 changes: 1 addition & 1 deletion clients/privacy-center/components/modals/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export const emailValidation = (option?: string | null) => {
export const phoneValidation = (option?: string | null) => {
// E.164 international standard format
let validation = Yup.string().matches(/^\+[1-9]\d{1,14}$/, {
message: "Phone is invalid",
message: "Phone must be in E.164 format (e.g. +15551234567)",
excludeEmptyString: true,
});
if (option === "required") {
Expand Down
Loading