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