feat: exclude emails and domains (#17591)
* feat: add constants * feat: add excludeEmails field in baseSchema * feat: add ExcludeEmails Input field in FieldEditDialog * feat: add excludedEmails check in FormBuilderField * chore: move validations in getBookingResponsesSchema * Update i18n string to be more generic * Update apps/web/public/static/locales/en/common.json * feat: add validation on excludeEmails field * feat: add fieldSchema as resolver * chore: remove comment * chore: format comments * feat: add tests for excluded email or domains --------- Co-authored-by: Alex van Andel <me@alexvanandel.com> Co-authored-by: Peer Richelsen <peer@cal.com> Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
This commit is contained in:
co-authored by
Alex van Andel
Peer Richelsen
Anik Dhabal Babu
parent
e0713d78b3
commit
c59e733b5a
@@ -2770,5 +2770,7 @@
|
||||
"salesforce_route_to_custom_lookup_field": "Route to a user that matches a lookup field on an account",
|
||||
"salesforce_option": "Salesforce Option",
|
||||
"lookup_field_name": "Lookup Field Name",
|
||||
"exclude_emails_that_contain" : "Exclude emails that contain ...",
|
||||
"exclude_emails_match_found_error_message" : "Please enter a valid work email address",
|
||||
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ const CUSTOM_REQUIRED_FIELD_ERROR_MSG = "error_required_field";
|
||||
const CUSTOM_PHONE_VALIDATION_ERROR_MSG = "invalid_number";
|
||||
const CUSTOM_EMAIL_VALIDATION_ERROR_MSG = "email_validation_error";
|
||||
const CUSTOM_URL_VALIDATION_ERROR_MSG = "url_validation_error";
|
||||
const CUSTOM_EMAIL_EXCLUDED_ERROR_MSG = "exclude_emails_match_found_error_message";
|
||||
const ZOD_REQUIRED_FIELD_ERROR_MSG = "Required";
|
||||
|
||||
describe("getBookingResponsesSchema", () => {
|
||||
@@ -1241,3 +1242,79 @@ describe("getBookingResponsesPartialSchema - Prefill validation", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("excluded email/domain validation", () => {
|
||||
test("should fail if the email is present in excluded emails", async () => {
|
||||
const excludedEmails = "spammer@cal.com, hotmail.com, yahoo, @gmail.com";
|
||||
|
||||
const schema = getBookingResponsesSchema({
|
||||
bookingFields: [
|
||||
{
|
||||
name: "name",
|
||||
type: "name",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "email",
|
||||
type: "email",
|
||||
required: true,
|
||||
excludeEmails: excludedEmails,
|
||||
},
|
||||
] as z.infer<typeof eventTypeBookingFields> & z.BRAND<"HAS_SYSTEM_FIELDS">,
|
||||
view: "ALL_VIEWS",
|
||||
});
|
||||
|
||||
const parsedResponses = await schema.safeParseAsync({
|
||||
name: "test",
|
||||
email: "harry@gmail.com",
|
||||
});
|
||||
expect(parsedResponses.success).toBe(false);
|
||||
|
||||
if (parsedResponses.success) {
|
||||
throw new Error("Should not reach here");
|
||||
}
|
||||
expect(parsedResponses.error.issues[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
code: "custom",
|
||||
message: `{email}${CUSTOM_EMAIL_EXCLUDED_ERROR_MSG}`,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test("should pass if the email is not present in excluded emails", async () => {
|
||||
const excludedEmails = "spammer@cal.com, hotmail.com, yahoo, @gmail.com";
|
||||
|
||||
const schema = getBookingResponsesSchema({
|
||||
bookingFields: [
|
||||
{
|
||||
name: "name",
|
||||
type: "name",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "email",
|
||||
type: "email",
|
||||
required: true,
|
||||
excludeEmails: excludedEmails,
|
||||
},
|
||||
] as z.infer<typeof eventTypeBookingFields> & z.BRAND<"HAS_SYSTEM_FIELDS">,
|
||||
view: "ALL_VIEWS",
|
||||
});
|
||||
|
||||
const parsedResponses = await schema.safeParseAsync({
|
||||
name: "test",
|
||||
email: "harry@workmail.com",
|
||||
});
|
||||
|
||||
expect(parsedResponses.success).toBe(true);
|
||||
|
||||
if (!parsedResponses.success) {
|
||||
throw new Error("Should not reach here");
|
||||
}
|
||||
|
||||
expect(parsedResponses.data).toEqual({
|
||||
name: "test",
|
||||
email: "harry@workmail.com",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -162,13 +162,29 @@ function preprocess<T extends z.ZodType>({
|
||||
}
|
||||
|
||||
if (bookingField.type === "email") {
|
||||
// Email RegExp to validate if the input is a valid email
|
||||
if (!bookingField.hidden && !emailSchema.safeParse(value).success) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: m("email_validation_error"),
|
||||
});
|
||||
if (!bookingField.hidden) {
|
||||
// Email RegExp to validate if the input is a valid email
|
||||
if (!emailSchema.safeParse(value).success) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: m("email_validation_error"),
|
||||
});
|
||||
}
|
||||
|
||||
// validate the excluded emails
|
||||
const bookerEmail = value;
|
||||
const excludedEmails =
|
||||
bookingField.excludeEmails?.split(",").map((domain) => domain.trim()) || [];
|
||||
|
||||
const match = excludedEmails.find((email) => bookerEmail.includes(email));
|
||||
if (match) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: m("exclude_emails_match_found_error_message"),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useEffect, useState } from "react";
|
||||
import type { SubmitHandler, UseFormReturn } from "react-hook-form";
|
||||
import { Controller, useFieldArray, useForm, useFormContext } from "react-hook-form";
|
||||
@@ -32,7 +33,7 @@ import {
|
||||
|
||||
import { fieldTypesConfigMap } from "./fieldTypes";
|
||||
import { fieldsThatSupportLabelAsSafeHtml } from "./fieldsThatSupportLabelAsSafeHtml";
|
||||
import type { fieldsSchema } from "./schema";
|
||||
import { fieldSchema, type fieldsSchema } from "./schema";
|
||||
import { getFieldIdentifier } from "./utils/getFieldIdentifier";
|
||||
import { getConfig as getVariantsConfig } from "./utils/variantsConfig";
|
||||
|
||||
@@ -459,7 +460,7 @@ function FieldEditDialog({
|
||||
const { t } = useLocale();
|
||||
const fieldForm = useForm<RhfFormField>({
|
||||
defaultValues: dialog.data || {},
|
||||
// resolver: zodResolver(fieldSchema),
|
||||
resolver: zodResolver(fieldSchema),
|
||||
});
|
||||
const formFieldType = fieldForm.getValues("type");
|
||||
|
||||
@@ -573,6 +574,15 @@ function FieldEditDialog({
|
||||
<FieldWithLengthCheckSupport containerClassName="mt-6" fieldForm={fieldForm} />
|
||||
) : null}
|
||||
|
||||
{fieldType.value === "email" && fieldForm.getValues("name") === "email" && (
|
||||
<InputField
|
||||
{...fieldForm.register("excludeEmails")}
|
||||
containerClassName="mt-6"
|
||||
label={t("exclude_emails_that_contain")}
|
||||
placeholder="gmail.com, hotmail.com, ..."
|
||||
/>
|
||||
)}
|
||||
|
||||
<Controller
|
||||
name="required"
|
||||
control={fieldForm.control}
|
||||
|
||||
@@ -99,6 +99,30 @@ const baseFieldSchema = z.object({
|
||||
* @requires supportsLengthCheck = true
|
||||
*/
|
||||
maxLength: z.number().optional(),
|
||||
|
||||
// Emails that needs to be excluded
|
||||
excludeEmails: z
|
||||
.string()
|
||||
.superRefine((val, ctx) => {
|
||||
const allDomains = val.split(",").map((dom) => dom.trim());
|
||||
|
||||
const regex = /^(?:@?[a-z0-9-]+(?:\.[a-z]{2,})?)?(?:@[a-z0-9-]+\.[a-z]{2,})?$/;
|
||||
|
||||
/*
|
||||
Valid patterns - [ example, example.anything, anyone@example.anything ]
|
||||
Invalid patterns - Patterns involving capital letter [ Example, Example.anything, Anyone@example.anything ]
|
||||
*/
|
||||
|
||||
const isValid = !allDomains.some((domain) => !regex.test(domain));
|
||||
|
||||
if (!isValid) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "Enter valid domain or email",
|
||||
});
|
||||
}
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export const variantsConfigSchema = z.object({
|
||||
|
||||
Reference in New Issue
Block a user