diff --git a/src/features/contact/contactverzoek/formulier/ContactverzoekFormulier.vue b/src/features/contact/contactverzoek/formulier/ContactverzoekFormulier.vue index e923d5184..e51cbe50c 100644 --- a/src/features/contact/contactverzoek/formulier/ContactverzoekFormulier.vue +++ b/src/features/contact/contactverzoek/formulier/ContactverzoekFormulier.vue @@ -250,7 +250,7 @@ type="tel" name="Telefoonnummer 1" class="utrecht-textbox utrecht-textbox--html-input" - @input="setActive" + @input="handleTelefoonInput" /> @@ -301,7 +300,7 @@ import type { } from "@/stores/contactmoment"; import { ActorType } from "@/stores/contactmoment"; -import { ref, watch } from "vue"; +import { computed, ref, watch } from "vue"; import { FormFieldsetLegend, FormFieldset, @@ -319,6 +318,7 @@ import AfdelingenSearch from "../../components/AfdelingenSearch.vue"; import GroepenSearch from "./components/GroepenSearch.vue"; import { fetchAfdelingen } from "@/features/contact/components/afdelingen"; import { fetchGroepen } from "./components/groepen"; +import { TELEFOON_PATTERN, EMAIL_PATTERN } from "@/helpers/validation"; const props = defineProps<{ modelValue: ContactmomentContactVerzoek; @@ -472,21 +472,47 @@ watch( ////////////////////////////////////////////////////// +const hasContact = computed( + () => + !!form.value.telefoonnummer1 || + !!form.value.telefoonnummer2 || + !!form.value.emailadres, +); + +const noContactMessage = + "Vul minimaal een telefoonnummer of een e-mailadres in"; + watch( - [ - telEl, - () => - !!form.value.telefoonnummer1 || - !!form.value.telefoonnummer2 || - !!form.value.emailadres, - ], - ([el, hasContact]) => { - if (!el) return; + [telEl, hasContact], + ([el, bool]) => el && el.setCustomValidity(!bool ? noContactMessage : ""), +); + +const handleTelefoonInput = (event: Event) => { + const el = event.target as HTMLInputElement; + + setActive(); + + if (!el.value || TELEFOON_PATTERN.test(el.value)) { + // telEl: back to custom noContactMessage if applicable, otherwise clear el.setCustomValidity( - hasContact ? "" : "Vul minimaal een telefoonnummer of een e-mailadres in", + el === telEl.value && !hasContact.value ? noContactMessage : "", ); - }, -); + } else { + el.setCustomValidity("Vul een geldig telefoonnummer in."); + } +}; + +const handleEmailInput = (event: Event) => { + const el = event.target as HTMLInputElement; + + setActive(); + + el.setCustomValidity( + !el.value || EMAIL_PATTERN.test(el.value) + ? "" + : "Vul een geldig emailadres in.", + ); +}; //als de afdeling wijzigt, dan moet de medewerker gereset worden watch( diff --git a/src/helpers/validation.ts b/src/helpers/validation.ts index 330df1ed0..29b02a593 100644 --- a/src/helpers/validation.ts +++ b/src/helpers/validation.ts @@ -199,3 +199,19 @@ export const vValidate: Directive = { (el as any).removeValidatorSetup?.(); }, }; + +// https://github.com/maykinmedia/open-klant/blob/f231f368c48276ffe429fb7e3105b0ce9f0eb444/src/openklant/utils/validators.py#L26 +export const TELEFOON_PATTERN = + /^(0[8-9]00[0-9]{4,7}|0[1-9][0-9]{8}|\+[0-9]{9,20}|1400|140[0-9]{2,3})$/; + +// https://github.com/django/django/blob/4.2/django/core/validators.py#L174 +export const EMAIL_PATTERN = new RegExp( + "^" + + // user_regex (without quoted string) + "([-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*)" + + "@" + + // domain_regex (without literal_regex) + "((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\\.)+(?:[A-Z0-9-]{2,63}(? { test("should allow dd/MM/yyyy", async () => { @@ -52,7 +56,7 @@ describe("parseDutchDate", () => { expect(parsed).toBeInstanceOf(Error); const error = parsed as Error; expect(error.message).toBe( - "Voer een valide datum in, bijvoorbeeld 17-09-2022 of 17092022." + "Voer een valide datum in, bijvoorbeeld 17-09-2022 of 17092022.", ); }); @@ -61,7 +65,7 @@ describe("parseDutchDate", () => { expect(parsed).toBeInstanceOf(Error); const error = parsed as Error; expect(error.message).toBe( - "Voer een valide datum in, bijvoorbeeld 17-09-2022 of 17092022." + "Voer een valide datum in, bijvoorbeeld 17-09-2022 of 17092022.", ); }); }); @@ -84,3 +88,76 @@ describe("parseBsn", () => { expect(parsedError.message).toBe("Voer een BSN in van negen cijfers."); }); }); + +describe("TELEFOON_PATTERN", () => { + test("validates phone numbers correctly", () => { + const testCases = [ + { number: "+31612345678", isValid: true }, + { number: "+441134960000", isValid: true }, + { number: "+12065550100", isValid: true }, + { number: "0612345678", isValid: true }, + { number: "09001234567", isValid: true }, + { number: "1400", isValid: true }, + { number: "14012", isValid: true }, + { number: "14079", isValid: true }, + { number: "0695azerty", isValid: false }, + { number: "azerty0545", isValid: false }, + { number: "@4566544++8", isValid: false }, + { number: "onetwothreefour", isValid: false }, + { number: "020 753 0523", isValid: false }, + { number: "+311234", isValid: false }, + { number: "0800852", isValid: false }, + { number: "080085285212", isValid: false }, + ]; + + testCases.forEach(({ number, isValid }) => + expect(TELEFOON_PATTERN.test(number)).toBe(isValid), + ); + }); +}); + +describe("EMAIL_PATTERN", () => { + test("validates email addresses correctly", () => { + const testCases = [ + { email: "email@here.com", isValid: true }, + { email: "weirder-email@here.and.there.com", isValid: true }, + { email: "example@valid-----hyphens.com", isValid: true }, + { email: "example@valid-with-hyphens.com", isValid: true }, + { email: `example@atm.${"a".repeat(63)}`, isValid: true }, + { email: `example@${"a".repeat(63)}.atm`, isValid: true }, + { + email: `example@${"a".repeat(63)}.${"b".repeat(10)}.atm`, + isValid: true, + }, + { email: `example@atm.${"a".repeat(64)}`, isValid: false }, + { + email: `example@${"b".repeat(64)}.atm.${"a".repeat(63)}.atm`, + isValid: false, + }, + { email: "", isValid: false }, + { email: "abc", isValid: false }, + { email: "abc@", isValid: false }, + { email: "abc@bar", isValid: false }, + { email: "a @x.cz", isValid: false }, + { email: "abc@.com", isValid: false }, + { email: `"test@test"@example.com`, isValid: false }, + { email: "something@@somewhere.com", isValid: false }, + { email: "email@127.0.0.1", isValid: false }, + { email: "example@invalid-.com", isValid: false }, + { email: "example@-invalid.com", isValid: false }, + { email: "example@invalid.com-", isValid: false }, + { email: "example@inv-.alid-.com", isValid: false }, + { email: "example@inv-.-alid.com", isValid: false }, + { email: `test@example.com\n\n