feat: ability to require an email (#18057)

* feat: ability to require an email

* fix: requireEmails empty bug

* Update apps/web/public/static/locales/en/common.json

* Update apps/web/public/static/locales/en/common.json

* add tests

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
This commit is contained in:
Kartik Saini
2024-12-10 17:27:32 +05:30
committed by GitHub
co-authored by Keith Williams
parent 351ab0790f
commit a1fc6668d5
5 changed files with 119 additions and 4 deletions
@@ -2846,7 +2846,9 @@
"rr_distribution_method_balanced_title": "Load balancing",
"rr_distribution_method_balanced_description": "We will monitor how many bookings have been made with each host and compare this with others, disabling some hosts that are too far ahead so bookings are evenly distributed.",
"exclude_emails_that_contain": "Exclude emails that contain ...",
"require_emails_that_contain": "Require emails that contain ...",
"exclude_emails_match_found_error_message": "Please enter a valid work email address",
"require_emails_no_match_found_error_message": "You are not allowed to book with this email domain. Please contact the owner of this link.",
"search_columns": "Search columns",
"search_options": "Search options",
"disable_org_url_label": "Disable public organization profile and redirect",
@@ -12,6 +12,7 @@ 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 CUSTOM_EMAIL_REQUIRED_ERROR_MSG = "require_emails_no_match_found_error_message";
const ZOD_REQUIRED_FIELD_ERROR_MSG = "Required";
describe("getBookingResponsesSchema", () => {
@@ -1318,3 +1319,80 @@ describe("excluded email/domain validation", () => {
});
});
});
describe("require email/domain validation", () => {
test("should fail if the requried email/domain is not present", async () => {
const requiredEmails = "gmail.com, user@hotmail.com, @yahoo.com";
const schema = getBookingResponsesSchema({
bookingFields: [
{
name: "name",
type: "name",
required: true,
},
{
name: "email",
type: "email",
required: true,
requireEmails: requiredEmails,
},
] as z.infer<typeof eventTypeBookingFields> & z.BRAND<"HAS_SYSTEM_FIELDS">,
view: "ALL_VIEWS",
});
const parsedResponses = await schema.safeParseAsync({
name: "test",
email: "test@test.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_REQUIRED_ERROR_MSG}`,
})
);
});
test("should pass if the required email/domain is present", async () => {
const requiredEmails = "gmail.com, user@hotmail.com, @yahoo.com";
const schema = getBookingResponsesSchema({
bookingFields: [
{
name: "name",
type: "name",
required: true,
},
{
name: "email",
type: "email",
required: true,
requireEmails: requiredEmails,
},
] as z.infer<typeof eventTypeBookingFields> & z.BRAND<"HAS_SYSTEM_FIELDS">,
view: "ALL_VIEWS",
});
const parsedResponses = await schema.safeParseAsync({
name: "test",
email: "test@gmail.com",
});
expect(parsedResponses.success).toBe(true);
if (!parsedResponses.success) {
throw new Error("Should not reach here");
}
expect(parsedResponses.data).toEqual({
name: "test",
email: "test@gmail.com",
});
});
});
@@ -183,6 +183,18 @@ function preprocess<T extends z.ZodType>({
message: m("exclude_emails_match_found_error_message"),
});
}
const requiredEmails =
bookingField.requireEmails
?.split(",")
.map((domain) => domain.trim())
.filter(Boolean) || [];
const requiredEmailsMatch = requiredEmails.find((email) => bookerEmail.includes(email));
if (requiredEmails.length > 0 && !requiredEmailsMatch) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: m("require_emails_no_match_found_error_message"),
});
}
}
continue;
+23 -2
View File
@@ -33,7 +33,7 @@ import {
import { fieldTypesConfigMap } from "./fieldTypes";
import { fieldsThatSupportLabelAsSafeHtml } from "./fieldsThatSupportLabelAsSafeHtml";
import { type fieldsSchema, excludeEmailSchema } from "./schema";
import { type fieldsSchema, excludeOrRequireEmailSchema } from "./schema";
import { getFieldIdentifier } from "./utils/getFieldIdentifier";
import { getConfig as getVariantsConfig } from "./utils/variantsConfig";
@@ -574,13 +574,34 @@ function FieldEditDialog({
<FieldWithLengthCheckSupport containerClassName="mt-6" fieldForm={fieldForm} />
) : null}
{formFieldType === "email" && (
<InputField
{...fieldForm.register("requireEmails")}
containerClassName="mt-6"
onChange={(e) => {
try {
excludeOrRequireEmailSchema.parse(e.target.value);
fieldForm.clearErrors("requireEmails");
} catch (err) {
if (err instanceof ZodError) {
fieldForm.setError("requireEmails", {
message: err.errors[0]?.message || "Invalid input",
});
}
}
}}
label={t("require_emails_that_contain")}
placeholder="gmail.com, hotmail.com, ..."
/>
)}
{formFieldType === "email" && (
<InputField
{...fieldForm.register("excludeEmails")}
containerClassName="mt-6"
onChange={(e) => {
try {
excludeEmailSchema.parse(e.target.value);
excludeOrRequireEmailSchema.parse(e.target.value);
fieldForm.clearErrors("excludeEmails");
} catch (err) {
if (err instanceof ZodError) {
+4 -2
View File
@@ -36,7 +36,7 @@ export const EditableSchema = z.enum([
"user-readonly", // All fields are readOnly.
]);
export const excludeEmailSchema = z.string().superRefine((val, ctx) => {
export const excludeOrRequireEmailSchema = 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,})?$/;
@@ -121,7 +121,9 @@ const baseFieldSchema = z.object({
maxLength: z.number().optional(),
// Emails that needs to be excluded
excludeEmails: excludeEmailSchema.optional(),
excludeEmails: excludeOrRequireEmailSchema.optional(),
// Emails that need to be required
requireEmails: excludeOrRequireEmailSchema.optional(),
});
export const variantsConfigSchema = z.object({