1 import type { MutableRefObject, RefObject } from 'react';
3 import { type FormikErrors } from 'formik';
5 import { validateVaultValues } from '@proton/pass/lib/validation/vault';
6 import type { InviteFormValues } from '@proton/pass/types';
7 import { validateEmailAddress } from '@proton/shared/lib/helpers/email';
9 export enum InviteEmailsError {
10 DUPLICATE = 'DUPLICATE' /* duplicate members */,
11 EMPTY = 'EMPTY' /* empty members */,
12 INVALID_EMAIL = 'INVALID_EMAIL' /* invalid email string */,
13 INVALID_ORG = 'INVALID_ORG' /* invalid organization member */,
16 type ValidateShareInviteOptions = {
17 emailField: RefObject<HTMLInputElement>;
18 validateAddresses: boolean;
19 validationMap: MutableRefObject<Map<string, boolean>>;
22 export const validateShareInviteValues =
23 ({ emailField, validateAddresses, validationMap }: ValidateShareInviteOptions) =>
24 (values: InviteFormValues) => {
25 if (values.step === 'vault' && values.withVaultCreation) return validateVaultValues(values);
27 let errors: FormikErrors<InviteFormValues> = {};
29 if (values.step === 'members') {
30 const emails = values.members.reduce<{
36 if (acc.seen.has(value.email)) {
37 acc.errors.push(InviteEmailsError.DUPLICATE);
39 } else if (!validateEmailAddress(value.email)) {
41 acc.errors.push(InviteEmailsError.INVALID_EMAIL);
42 } else if (validateAddresses && validationMap.current.get(value.email) === false) {
43 acc.errors.push(InviteEmailsError.INVALID_ORG);
45 } else acc.errors.push('');
47 acc.seen.add(value.email);
51 { errors: [], pass: true, seen: new Set() }
54 errors.members = emails.errors;
56 /** Determine conditions for displaying trailing input
57 * value errors: If the field isn't focused, only show
58 * an error if the field is empty and there are no other
59 * members in the form. Adapt validation accordingly. */
60 const trailingOnly = values.members.length === 0;
61 const trailingFocused = emailField.current === document.activeElement;
62 const trailingValue = emailField.current?.value?.trim() ?? '';
63 const trailingEmpty = trailingValue.length === 0;
64 const trailingValid = !trailingEmpty && validateEmailAddress(trailingValue);
66 /* If the trailing input is focused, trigger errors if the trailing
67 * value is not a valid email address. If it's not focused, flag errors
68 * only when the trailing value is invalid and either the field is
69 * empty or there are no other members in the form. */
70 if (trailingFocused) {
71 emails.pass = emails.pass && (trailingOnly ? trailingValid : trailingValid || trailingEmpty);
72 } else if (!trailingValid) {
73 if (trailingEmpty && trailingOnly) {
75 errors.members.push(InviteEmailsError.EMPTY);
76 } else if (!trailingEmpty) {
78 errors.members.push(InviteEmailsError.INVALID_EMAIL);
82 /* If no errors are found, delete any existing error
83 * messages related to members. */
84 if (emails.pass) delete errors.members;