@defra/fcp-sfd-frontend-engine 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +265 -5
- package/dist/index.js +265 -5
- package/package.json +1 -1
- package/src/constants/index.js +16 -0
- package/src/constants/month-map.js +26 -0
- package/src/constants/patterns.js +1 -0
- package/src/constants/validation-fields-export.js +17 -0
- package/src/constants/validation-fields.js +14 -0
- package/src/schemas/address-schema.js +61 -0
- package/src/schemas/business/business-schemas.js +3 -1
- package/src/schemas/personal/personal-dob-schema.js +178 -0
- package/src/schemas/personal/personal-email-schema.js +20 -0
- package/src/schemas/personal/personal-name-schema.js +31 -0
- package/src/schemas/personal/personal-phone-schema.js +33 -0
- package/src/schemas/personal/personal-schemas.js +13 -0
- package/src/schemas/schemas.js +3 -1
package/dist/index.cjs
CHANGED
|
@@ -102,15 +102,66 @@ var businessSbiSchema = import_joi.default.object({
|
|
|
102
102
|
})
|
|
103
103
|
});
|
|
104
104
|
|
|
105
|
+
// src/schemas/address-schema.js
|
|
106
|
+
var import_joi2 = __toESM(require("joi"), 1);
|
|
107
|
+
|
|
108
|
+
// src/constants/validation-fields.js
|
|
109
|
+
var FIRST_NAME_MAX = 100;
|
|
110
|
+
var LAST_NAME_MAX = 100;
|
|
111
|
+
var MIDDLE_NAMES_MAX = 100;
|
|
112
|
+
var EMAIL_MAX = 254;
|
|
113
|
+
var PHONE_NUMBER_MIN = 10;
|
|
114
|
+
var PHONE_NUMBER_MAX = 50;
|
|
115
|
+
var MAX_AGE_YEARS = 120;
|
|
116
|
+
var ADDRESS_LINE_MAX = 100;
|
|
117
|
+
var TOWN_CITY_MAX = 60;
|
|
118
|
+
var COUNTY_MAX = 60;
|
|
119
|
+
var COUNTRY_MAX = 60;
|
|
120
|
+
var POSTCODE_MAX = 8;
|
|
121
|
+
|
|
122
|
+
// src/schemas/address-schema.js
|
|
123
|
+
var addressSchema = import_joi2.default.object({
|
|
124
|
+
address1: import_joi2.default.string().required().max(ADDRESS_LINE_MAX).messages({
|
|
125
|
+
"string.empty": "Enter address line 1, typically the building and street",
|
|
126
|
+
"string.max": `Address line 1 must be ${ADDRESS_LINE_MAX} characters or less`,
|
|
127
|
+
"any.required": "Enter address line 1, typically the building and street"
|
|
128
|
+
}),
|
|
129
|
+
address2: import_joi2.default.string().allow("").max(ADDRESS_LINE_MAX).messages({
|
|
130
|
+
"string.max": `Address line 2 must be ${ADDRESS_LINE_MAX} characters or less`
|
|
131
|
+
}),
|
|
132
|
+
address3: import_joi2.default.string().allow("").max(ADDRESS_LINE_MAX).messages({
|
|
133
|
+
"string.max": `Address line 3 must be ${ADDRESS_LINE_MAX} characters or less`
|
|
134
|
+
}),
|
|
135
|
+
city: import_joi2.default.string().required().max(TOWN_CITY_MAX).messages({
|
|
136
|
+
"string.empty": "Enter town or city",
|
|
137
|
+
"string.max": `Town or city must be ${TOWN_CITY_MAX} characters or less`,
|
|
138
|
+
"any.required": "Enter town or city"
|
|
139
|
+
}),
|
|
140
|
+
county: import_joi2.default.string().allow("").max(COUNTY_MAX).messages({
|
|
141
|
+
"string.max": `County must be ${COUNTY_MAX} characters or less`
|
|
142
|
+
}),
|
|
143
|
+
postcode: import_joi2.default.string().required().max(POSTCODE_MAX).messages({
|
|
144
|
+
"any.required": "Enter a postal code or zip code",
|
|
145
|
+
"string.empty": "Enter a postal code or zip code",
|
|
146
|
+
"string.max": `Postal code or zip code must be ${POSTCODE_MAX} characters or less`
|
|
147
|
+
}),
|
|
148
|
+
country: import_joi2.default.string().required().max(COUNTRY_MAX).messages({
|
|
149
|
+
"string.empty": "Enter a country",
|
|
150
|
+
"string.max": `Country must be ${COUNTRY_MAX} characters or less`,
|
|
151
|
+
"any.required": "Enter a country"
|
|
152
|
+
})
|
|
153
|
+
});
|
|
154
|
+
|
|
105
155
|
// src/schemas/business/business-schemas.js
|
|
106
156
|
var businessSchemas = {
|
|
107
|
-
sbi: businessSbiSchema
|
|
157
|
+
sbi: businessSbiSchema,
|
|
158
|
+
address: addressSchema
|
|
108
159
|
};
|
|
109
160
|
|
|
110
161
|
// src/schemas/customer/customer-crn-schema.js
|
|
111
|
-
var
|
|
112
|
-
var customerCrnSchema =
|
|
113
|
-
crn:
|
|
162
|
+
var import_joi3 = __toESM(require("joi"), 1);
|
|
163
|
+
var customerCrnSchema = import_joi3.default.object({
|
|
164
|
+
crn: import_joi3.default.string().pattern(/^\d{10}$/).allow("").optional().messages({
|
|
114
165
|
"string.pattern.base": "Enter the full CRN"
|
|
115
166
|
})
|
|
116
167
|
});
|
|
@@ -120,10 +171,219 @@ var customerSchemas = {
|
|
|
120
171
|
crn: customerCrnSchema
|
|
121
172
|
};
|
|
122
173
|
|
|
174
|
+
// src/schemas/personal/personal-name-schema.js
|
|
175
|
+
var import_joi4 = __toESM(require("joi"), 1);
|
|
176
|
+
var personalNameSchema = import_joi4.default.object({
|
|
177
|
+
first: import_joi4.default.string().required().max(FIRST_NAME_MAX).messages({
|
|
178
|
+
"string.empty": "Enter first name",
|
|
179
|
+
"string.max": `First name must be ${FIRST_NAME_MAX} characters or less`,
|
|
180
|
+
"any.required": "Enter first name"
|
|
181
|
+
}),
|
|
182
|
+
last: import_joi4.default.string().required().max(LAST_NAME_MAX).messages({
|
|
183
|
+
"string.empty": "Enter last name",
|
|
184
|
+
"string.max": `Last name must be ${LAST_NAME_MAX} characters or less`,
|
|
185
|
+
"any.required": "Enter last name"
|
|
186
|
+
}),
|
|
187
|
+
middle: import_joi4.default.string().allow("").max(MIDDLE_NAMES_MAX).messages({
|
|
188
|
+
"string.max": `Middle names must be ${MIDDLE_NAMES_MAX} characters or less`
|
|
189
|
+
})
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// src/schemas/personal/personal-dob-schema.js
|
|
193
|
+
var import_joi5 = __toESM(require("joi"), 1);
|
|
194
|
+
|
|
195
|
+
// src/constants/patterns.js
|
|
196
|
+
var PHONE_NUMBER_PATTERN = /^\+?[0-9 ()]+$/;
|
|
197
|
+
|
|
198
|
+
// src/constants/month-map.js
|
|
199
|
+
var MONTH_MAP = {
|
|
200
|
+
january: 1,
|
|
201
|
+
jan: 1,
|
|
202
|
+
february: 2,
|
|
203
|
+
feb: 2,
|
|
204
|
+
march: 3,
|
|
205
|
+
mar: 3,
|
|
206
|
+
april: 4,
|
|
207
|
+
apr: 4,
|
|
208
|
+
may: 5,
|
|
209
|
+
june: 6,
|
|
210
|
+
jun: 6,
|
|
211
|
+
july: 7,
|
|
212
|
+
jul: 7,
|
|
213
|
+
august: 8,
|
|
214
|
+
aug: 8,
|
|
215
|
+
september: 9,
|
|
216
|
+
sep: 9,
|
|
217
|
+
sept: 9,
|
|
218
|
+
october: 10,
|
|
219
|
+
oct: 10,
|
|
220
|
+
november: 11,
|
|
221
|
+
nov: 11,
|
|
222
|
+
december: 12,
|
|
223
|
+
dec: 12
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
// src/schemas/personal/personal-dob-schema.js
|
|
227
|
+
var personalDobSchema = import_joi5.default.object({
|
|
228
|
+
day: import_joi5.default.string().allow(""),
|
|
229
|
+
month: import_joi5.default.string().allow(""),
|
|
230
|
+
year: import_joi5.default.string().allow("")
|
|
231
|
+
}).custom((value, helpers) => {
|
|
232
|
+
const { day, month, year } = value;
|
|
233
|
+
const missingFieldsError = checkMissingFields(day, month, year, helpers);
|
|
234
|
+
if (missingFieldsError) {
|
|
235
|
+
return missingFieldsError;
|
|
236
|
+
}
|
|
237
|
+
if (year && year.length !== 4) {
|
|
238
|
+
return makeError(helpers, "dob.yearLength", ["year"]);
|
|
239
|
+
}
|
|
240
|
+
const monthValue = getMonthNumber(month, helpers);
|
|
241
|
+
if (monthValue.isJoiError) {
|
|
242
|
+
return monthValue;
|
|
243
|
+
}
|
|
244
|
+
const fullDate = getFullDate(day, monthValue, year, helpers);
|
|
245
|
+
if (fullDate.isJoiError) {
|
|
246
|
+
return fullDate;
|
|
247
|
+
}
|
|
248
|
+
if (fullDate > /* @__PURE__ */ new Date()) {
|
|
249
|
+
return makeError(helpers, "dob.future", ["day", "month", "year"]);
|
|
250
|
+
}
|
|
251
|
+
const tooOldError = checkNotTooOld(fullDate, helpers);
|
|
252
|
+
if (tooOldError) {
|
|
253
|
+
return tooOldError;
|
|
254
|
+
}
|
|
255
|
+
return value;
|
|
256
|
+
}).messages({
|
|
257
|
+
"dob.missingAll": "Enter your date of birth",
|
|
258
|
+
"dob.missingDay": "Date of birth must include a day",
|
|
259
|
+
"dob.missingMonth": "Date of birth must include a month",
|
|
260
|
+
"dob.missingYear": "Date of birth must include a year",
|
|
261
|
+
"dob.missingDayMonth": "Date of birth must include a day and month",
|
|
262
|
+
"dob.missingDayYear": "Date of birth must include a day and year",
|
|
263
|
+
"dob.missingMonthYear": "Date of birth must include a month and year",
|
|
264
|
+
"dob.yearLength": "Enter a year with 4 numbers, like 1975",
|
|
265
|
+
"dob.invalid": "Date of birth must be a real date",
|
|
266
|
+
"dob.future": "Date of birth must be in the past",
|
|
267
|
+
"dob.tooOld": "Date of birth must be on or after {{#oldest}}"
|
|
268
|
+
});
|
|
269
|
+
var checkNotTooOld = (fullDate, helpers) => {
|
|
270
|
+
const oldestDateAllowed = getOldestAllowedDate();
|
|
271
|
+
if (fullDate < oldestDateAllowed) {
|
|
272
|
+
const oldestDateString = oldestDateAllowed.toLocaleDateString("en-GB", {
|
|
273
|
+
day: "numeric",
|
|
274
|
+
month: "long",
|
|
275
|
+
year: "numeric"
|
|
276
|
+
});
|
|
277
|
+
const error = helpers.error("dob.tooOld", { oldest: oldestDateString });
|
|
278
|
+
error.path = ["day", "month", "year"];
|
|
279
|
+
return error;
|
|
280
|
+
}
|
|
281
|
+
return null;
|
|
282
|
+
};
|
|
283
|
+
var getOldestAllowedDate = () => {
|
|
284
|
+
const date = /* @__PURE__ */ new Date();
|
|
285
|
+
date.setUTCHours(0, 0, 0, 0);
|
|
286
|
+
date.setUTCFullYear(date.getUTCFullYear() - MAX_AGE_YEARS);
|
|
287
|
+
return date;
|
|
288
|
+
};
|
|
289
|
+
var getFullDate = (day, monthValue, year, helpers) => {
|
|
290
|
+
const dayValue = Number.parseInt(day, 10);
|
|
291
|
+
const yearValue = Number.parseInt(year, 10);
|
|
292
|
+
const date = new Date(Date.UTC(yearValue, monthValue - 1, dayValue));
|
|
293
|
+
if (date.getUTCFullYear() !== yearValue || date.getUTCMonth() + 1 !== monthValue || date.getUTCDate() !== dayValue) {
|
|
294
|
+
return makeError(helpers, "dob.invalid", ["day", "month", "year"]);
|
|
295
|
+
}
|
|
296
|
+
return date;
|
|
297
|
+
};
|
|
298
|
+
var getMonthNumber = (month, helpers) => {
|
|
299
|
+
if (Number.isNaN(Number(month))) {
|
|
300
|
+
const lower = month.toLowerCase();
|
|
301
|
+
const mapped = MONTH_MAP[lower];
|
|
302
|
+
if (!mapped) {
|
|
303
|
+
return makeError(helpers, "dob.invalid", ["month"]);
|
|
304
|
+
}
|
|
305
|
+
return mapped;
|
|
306
|
+
}
|
|
307
|
+
return Number.parseInt(month, 10);
|
|
308
|
+
};
|
|
309
|
+
var checkMissingFields = (day, month, year, helpers) => {
|
|
310
|
+
if (!day && !month && !year) {
|
|
311
|
+
return makeError(helpers, "dob.missingAll", ["day", "month", "year"]);
|
|
312
|
+
}
|
|
313
|
+
if (!day && month && year) {
|
|
314
|
+
return makeError(helpers, "dob.missingDay", ["day"]);
|
|
315
|
+
}
|
|
316
|
+
if (day && !month && year) {
|
|
317
|
+
return makeError(helpers, "dob.missingMonth", ["month"]);
|
|
318
|
+
}
|
|
319
|
+
if (day && month && !year) {
|
|
320
|
+
return makeError(helpers, "dob.missingYear", ["year"]);
|
|
321
|
+
}
|
|
322
|
+
if (!day && !month && year) {
|
|
323
|
+
return makeError(helpers, "dob.missingDayMonth", ["day", "month"]);
|
|
324
|
+
}
|
|
325
|
+
if (!day && month && !year) {
|
|
326
|
+
return makeError(helpers, "dob.missingDayYear", ["day", "year"]);
|
|
327
|
+
}
|
|
328
|
+
if (day && !month && !year) {
|
|
329
|
+
return makeError(helpers, "dob.missingMonthYear", ["month", "year"]);
|
|
330
|
+
}
|
|
331
|
+
return null;
|
|
332
|
+
};
|
|
333
|
+
var makeError = (helpers, code, fields) => {
|
|
334
|
+
const error = helpers.error(code);
|
|
335
|
+
error.path = fields;
|
|
336
|
+
error.isJoiError = true;
|
|
337
|
+
return error;
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
// src/schemas/personal/personal-email-schema.js
|
|
341
|
+
var import_joi6 = __toESM(require("joi"), 1);
|
|
342
|
+
var personalEmailSchema = import_joi6.default.object({
|
|
343
|
+
personalEmail: import_joi6.default.string().required().max(EMAIL_MAX).email({
|
|
344
|
+
minDomainSegments: 2,
|
|
345
|
+
tlds: {
|
|
346
|
+
allow: true,
|
|
347
|
+
min: 2
|
|
348
|
+
}
|
|
349
|
+
}).messages({
|
|
350
|
+
"string.max": `Email address must be ${EMAIL_MAX} characters or less`,
|
|
351
|
+
"string.empty": "Enter a personal email address",
|
|
352
|
+
"string.email": "Enter an email address, like name@example.com"
|
|
353
|
+
})
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
// src/schemas/personal/personal-phone-schema.js
|
|
357
|
+
var import_joi7 = __toESM(require("joi"), 1);
|
|
358
|
+
var personalPhoneSchema = import_joi7.default.object({
|
|
359
|
+
personalTelephone: import_joi7.default.string().empty("").min(PHONE_NUMBER_MIN).max(PHONE_NUMBER_MAX).pattern(PHONE_NUMBER_PATTERN).messages({
|
|
360
|
+
"string.min": `Personal telephone number must be ${PHONE_NUMBER_MIN} characters or more`,
|
|
361
|
+
"string.max": `Personal telephone number must be ${PHONE_NUMBER_MAX} characters or less`,
|
|
362
|
+
"string.pattern.base": "Personal telephone number must only include numbers 0 to 9 and special characters such as spaces, brackets and +"
|
|
363
|
+
}),
|
|
364
|
+
personalMobile: import_joi7.default.string().empty("").min(PHONE_NUMBER_MIN).max(PHONE_NUMBER_MAX).pattern(PHONE_NUMBER_PATTERN).messages({
|
|
365
|
+
"string.min": `Personal mobile phone number must be ${PHONE_NUMBER_MIN} characters or more`,
|
|
366
|
+
"string.max": `Personal mobile phone number must be ${PHONE_NUMBER_MAX} characters or less`,
|
|
367
|
+
"string.pattern.base": "Personal mobile phone number must only include numbers 0 to 9 and special characters such as spaces, brackets and +"
|
|
368
|
+
})
|
|
369
|
+
}).or("personalTelephone", "personalMobile").messages({
|
|
370
|
+
"object.missing": "Enter at least one phone number"
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
// src/schemas/personal/personal-schemas.js
|
|
374
|
+
var personalSchemas = {
|
|
375
|
+
name: personalNameSchema,
|
|
376
|
+
dob: personalDobSchema,
|
|
377
|
+
address: addressSchema,
|
|
378
|
+
phone: personalPhoneSchema,
|
|
379
|
+
email: personalEmailSchema
|
|
380
|
+
};
|
|
381
|
+
|
|
123
382
|
// src/schemas/schemas.js
|
|
124
383
|
var schemas = {
|
|
125
384
|
business: businessSchemas,
|
|
126
|
-
customer: customerSchemas
|
|
385
|
+
customer: customerSchemas,
|
|
386
|
+
personal: personalSchemas
|
|
127
387
|
};
|
|
128
388
|
|
|
129
389
|
// src/utils/format-validation-errors.js
|
package/dist/index.js
CHANGED
|
@@ -64,15 +64,66 @@ var businessSbiSchema = Joi.object({
|
|
|
64
64
|
})
|
|
65
65
|
});
|
|
66
66
|
|
|
67
|
+
// src/schemas/address-schema.js
|
|
68
|
+
import Joi2 from "joi";
|
|
69
|
+
|
|
70
|
+
// src/constants/validation-fields.js
|
|
71
|
+
var FIRST_NAME_MAX = 100;
|
|
72
|
+
var LAST_NAME_MAX = 100;
|
|
73
|
+
var MIDDLE_NAMES_MAX = 100;
|
|
74
|
+
var EMAIL_MAX = 254;
|
|
75
|
+
var PHONE_NUMBER_MIN = 10;
|
|
76
|
+
var PHONE_NUMBER_MAX = 50;
|
|
77
|
+
var MAX_AGE_YEARS = 120;
|
|
78
|
+
var ADDRESS_LINE_MAX = 100;
|
|
79
|
+
var TOWN_CITY_MAX = 60;
|
|
80
|
+
var COUNTY_MAX = 60;
|
|
81
|
+
var COUNTRY_MAX = 60;
|
|
82
|
+
var POSTCODE_MAX = 8;
|
|
83
|
+
|
|
84
|
+
// src/schemas/address-schema.js
|
|
85
|
+
var addressSchema = Joi2.object({
|
|
86
|
+
address1: Joi2.string().required().max(ADDRESS_LINE_MAX).messages({
|
|
87
|
+
"string.empty": "Enter address line 1, typically the building and street",
|
|
88
|
+
"string.max": `Address line 1 must be ${ADDRESS_LINE_MAX} characters or less`,
|
|
89
|
+
"any.required": "Enter address line 1, typically the building and street"
|
|
90
|
+
}),
|
|
91
|
+
address2: Joi2.string().allow("").max(ADDRESS_LINE_MAX).messages({
|
|
92
|
+
"string.max": `Address line 2 must be ${ADDRESS_LINE_MAX} characters or less`
|
|
93
|
+
}),
|
|
94
|
+
address3: Joi2.string().allow("").max(ADDRESS_LINE_MAX).messages({
|
|
95
|
+
"string.max": `Address line 3 must be ${ADDRESS_LINE_MAX} characters or less`
|
|
96
|
+
}),
|
|
97
|
+
city: Joi2.string().required().max(TOWN_CITY_MAX).messages({
|
|
98
|
+
"string.empty": "Enter town or city",
|
|
99
|
+
"string.max": `Town or city must be ${TOWN_CITY_MAX} characters or less`,
|
|
100
|
+
"any.required": "Enter town or city"
|
|
101
|
+
}),
|
|
102
|
+
county: Joi2.string().allow("").max(COUNTY_MAX).messages({
|
|
103
|
+
"string.max": `County must be ${COUNTY_MAX} characters or less`
|
|
104
|
+
}),
|
|
105
|
+
postcode: Joi2.string().required().max(POSTCODE_MAX).messages({
|
|
106
|
+
"any.required": "Enter a postal code or zip code",
|
|
107
|
+
"string.empty": "Enter a postal code or zip code",
|
|
108
|
+
"string.max": `Postal code or zip code must be ${POSTCODE_MAX} characters or less`
|
|
109
|
+
}),
|
|
110
|
+
country: Joi2.string().required().max(COUNTRY_MAX).messages({
|
|
111
|
+
"string.empty": "Enter a country",
|
|
112
|
+
"string.max": `Country must be ${COUNTRY_MAX} characters or less`,
|
|
113
|
+
"any.required": "Enter a country"
|
|
114
|
+
})
|
|
115
|
+
});
|
|
116
|
+
|
|
67
117
|
// src/schemas/business/business-schemas.js
|
|
68
118
|
var businessSchemas = {
|
|
69
|
-
sbi: businessSbiSchema
|
|
119
|
+
sbi: businessSbiSchema,
|
|
120
|
+
address: addressSchema
|
|
70
121
|
};
|
|
71
122
|
|
|
72
123
|
// src/schemas/customer/customer-crn-schema.js
|
|
73
|
-
import
|
|
74
|
-
var customerCrnSchema =
|
|
75
|
-
crn:
|
|
124
|
+
import Joi3 from "joi";
|
|
125
|
+
var customerCrnSchema = Joi3.object({
|
|
126
|
+
crn: Joi3.string().pattern(/^\d{10}$/).allow("").optional().messages({
|
|
76
127
|
"string.pattern.base": "Enter the full CRN"
|
|
77
128
|
})
|
|
78
129
|
});
|
|
@@ -82,10 +133,219 @@ var customerSchemas = {
|
|
|
82
133
|
crn: customerCrnSchema
|
|
83
134
|
};
|
|
84
135
|
|
|
136
|
+
// src/schemas/personal/personal-name-schema.js
|
|
137
|
+
import Joi4 from "joi";
|
|
138
|
+
var personalNameSchema = Joi4.object({
|
|
139
|
+
first: Joi4.string().required().max(FIRST_NAME_MAX).messages({
|
|
140
|
+
"string.empty": "Enter first name",
|
|
141
|
+
"string.max": `First name must be ${FIRST_NAME_MAX} characters or less`,
|
|
142
|
+
"any.required": "Enter first name"
|
|
143
|
+
}),
|
|
144
|
+
last: Joi4.string().required().max(LAST_NAME_MAX).messages({
|
|
145
|
+
"string.empty": "Enter last name",
|
|
146
|
+
"string.max": `Last name must be ${LAST_NAME_MAX} characters or less`,
|
|
147
|
+
"any.required": "Enter last name"
|
|
148
|
+
}),
|
|
149
|
+
middle: Joi4.string().allow("").max(MIDDLE_NAMES_MAX).messages({
|
|
150
|
+
"string.max": `Middle names must be ${MIDDLE_NAMES_MAX} characters or less`
|
|
151
|
+
})
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// src/schemas/personal/personal-dob-schema.js
|
|
155
|
+
import Joi5 from "joi";
|
|
156
|
+
|
|
157
|
+
// src/constants/patterns.js
|
|
158
|
+
var PHONE_NUMBER_PATTERN = /^\+?[0-9 ()]+$/;
|
|
159
|
+
|
|
160
|
+
// src/constants/month-map.js
|
|
161
|
+
var MONTH_MAP = {
|
|
162
|
+
january: 1,
|
|
163
|
+
jan: 1,
|
|
164
|
+
february: 2,
|
|
165
|
+
feb: 2,
|
|
166
|
+
march: 3,
|
|
167
|
+
mar: 3,
|
|
168
|
+
april: 4,
|
|
169
|
+
apr: 4,
|
|
170
|
+
may: 5,
|
|
171
|
+
june: 6,
|
|
172
|
+
jun: 6,
|
|
173
|
+
july: 7,
|
|
174
|
+
jul: 7,
|
|
175
|
+
august: 8,
|
|
176
|
+
aug: 8,
|
|
177
|
+
september: 9,
|
|
178
|
+
sep: 9,
|
|
179
|
+
sept: 9,
|
|
180
|
+
october: 10,
|
|
181
|
+
oct: 10,
|
|
182
|
+
november: 11,
|
|
183
|
+
nov: 11,
|
|
184
|
+
december: 12,
|
|
185
|
+
dec: 12
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
// src/schemas/personal/personal-dob-schema.js
|
|
189
|
+
var personalDobSchema = Joi5.object({
|
|
190
|
+
day: Joi5.string().allow(""),
|
|
191
|
+
month: Joi5.string().allow(""),
|
|
192
|
+
year: Joi5.string().allow("")
|
|
193
|
+
}).custom((value, helpers) => {
|
|
194
|
+
const { day, month, year } = value;
|
|
195
|
+
const missingFieldsError = checkMissingFields(day, month, year, helpers);
|
|
196
|
+
if (missingFieldsError) {
|
|
197
|
+
return missingFieldsError;
|
|
198
|
+
}
|
|
199
|
+
if (year && year.length !== 4) {
|
|
200
|
+
return makeError(helpers, "dob.yearLength", ["year"]);
|
|
201
|
+
}
|
|
202
|
+
const monthValue = getMonthNumber(month, helpers);
|
|
203
|
+
if (monthValue.isJoiError) {
|
|
204
|
+
return monthValue;
|
|
205
|
+
}
|
|
206
|
+
const fullDate = getFullDate(day, monthValue, year, helpers);
|
|
207
|
+
if (fullDate.isJoiError) {
|
|
208
|
+
return fullDate;
|
|
209
|
+
}
|
|
210
|
+
if (fullDate > /* @__PURE__ */ new Date()) {
|
|
211
|
+
return makeError(helpers, "dob.future", ["day", "month", "year"]);
|
|
212
|
+
}
|
|
213
|
+
const tooOldError = checkNotTooOld(fullDate, helpers);
|
|
214
|
+
if (tooOldError) {
|
|
215
|
+
return tooOldError;
|
|
216
|
+
}
|
|
217
|
+
return value;
|
|
218
|
+
}).messages({
|
|
219
|
+
"dob.missingAll": "Enter your date of birth",
|
|
220
|
+
"dob.missingDay": "Date of birth must include a day",
|
|
221
|
+
"dob.missingMonth": "Date of birth must include a month",
|
|
222
|
+
"dob.missingYear": "Date of birth must include a year",
|
|
223
|
+
"dob.missingDayMonth": "Date of birth must include a day and month",
|
|
224
|
+
"dob.missingDayYear": "Date of birth must include a day and year",
|
|
225
|
+
"dob.missingMonthYear": "Date of birth must include a month and year",
|
|
226
|
+
"dob.yearLength": "Enter a year with 4 numbers, like 1975",
|
|
227
|
+
"dob.invalid": "Date of birth must be a real date",
|
|
228
|
+
"dob.future": "Date of birth must be in the past",
|
|
229
|
+
"dob.tooOld": "Date of birth must be on or after {{#oldest}}"
|
|
230
|
+
});
|
|
231
|
+
var checkNotTooOld = (fullDate, helpers) => {
|
|
232
|
+
const oldestDateAllowed = getOldestAllowedDate();
|
|
233
|
+
if (fullDate < oldestDateAllowed) {
|
|
234
|
+
const oldestDateString = oldestDateAllowed.toLocaleDateString("en-GB", {
|
|
235
|
+
day: "numeric",
|
|
236
|
+
month: "long",
|
|
237
|
+
year: "numeric"
|
|
238
|
+
});
|
|
239
|
+
const error = helpers.error("dob.tooOld", { oldest: oldestDateString });
|
|
240
|
+
error.path = ["day", "month", "year"];
|
|
241
|
+
return error;
|
|
242
|
+
}
|
|
243
|
+
return null;
|
|
244
|
+
};
|
|
245
|
+
var getOldestAllowedDate = () => {
|
|
246
|
+
const date = /* @__PURE__ */ new Date();
|
|
247
|
+
date.setUTCHours(0, 0, 0, 0);
|
|
248
|
+
date.setUTCFullYear(date.getUTCFullYear() - MAX_AGE_YEARS);
|
|
249
|
+
return date;
|
|
250
|
+
};
|
|
251
|
+
var getFullDate = (day, monthValue, year, helpers) => {
|
|
252
|
+
const dayValue = Number.parseInt(day, 10);
|
|
253
|
+
const yearValue = Number.parseInt(year, 10);
|
|
254
|
+
const date = new Date(Date.UTC(yearValue, monthValue - 1, dayValue));
|
|
255
|
+
if (date.getUTCFullYear() !== yearValue || date.getUTCMonth() + 1 !== monthValue || date.getUTCDate() !== dayValue) {
|
|
256
|
+
return makeError(helpers, "dob.invalid", ["day", "month", "year"]);
|
|
257
|
+
}
|
|
258
|
+
return date;
|
|
259
|
+
};
|
|
260
|
+
var getMonthNumber = (month, helpers) => {
|
|
261
|
+
if (Number.isNaN(Number(month))) {
|
|
262
|
+
const lower = month.toLowerCase();
|
|
263
|
+
const mapped = MONTH_MAP[lower];
|
|
264
|
+
if (!mapped) {
|
|
265
|
+
return makeError(helpers, "dob.invalid", ["month"]);
|
|
266
|
+
}
|
|
267
|
+
return mapped;
|
|
268
|
+
}
|
|
269
|
+
return Number.parseInt(month, 10);
|
|
270
|
+
};
|
|
271
|
+
var checkMissingFields = (day, month, year, helpers) => {
|
|
272
|
+
if (!day && !month && !year) {
|
|
273
|
+
return makeError(helpers, "dob.missingAll", ["day", "month", "year"]);
|
|
274
|
+
}
|
|
275
|
+
if (!day && month && year) {
|
|
276
|
+
return makeError(helpers, "dob.missingDay", ["day"]);
|
|
277
|
+
}
|
|
278
|
+
if (day && !month && year) {
|
|
279
|
+
return makeError(helpers, "dob.missingMonth", ["month"]);
|
|
280
|
+
}
|
|
281
|
+
if (day && month && !year) {
|
|
282
|
+
return makeError(helpers, "dob.missingYear", ["year"]);
|
|
283
|
+
}
|
|
284
|
+
if (!day && !month && year) {
|
|
285
|
+
return makeError(helpers, "dob.missingDayMonth", ["day", "month"]);
|
|
286
|
+
}
|
|
287
|
+
if (!day && month && !year) {
|
|
288
|
+
return makeError(helpers, "dob.missingDayYear", ["day", "year"]);
|
|
289
|
+
}
|
|
290
|
+
if (day && !month && !year) {
|
|
291
|
+
return makeError(helpers, "dob.missingMonthYear", ["month", "year"]);
|
|
292
|
+
}
|
|
293
|
+
return null;
|
|
294
|
+
};
|
|
295
|
+
var makeError = (helpers, code, fields) => {
|
|
296
|
+
const error = helpers.error(code);
|
|
297
|
+
error.path = fields;
|
|
298
|
+
error.isJoiError = true;
|
|
299
|
+
return error;
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
// src/schemas/personal/personal-email-schema.js
|
|
303
|
+
import Joi6 from "joi";
|
|
304
|
+
var personalEmailSchema = Joi6.object({
|
|
305
|
+
personalEmail: Joi6.string().required().max(EMAIL_MAX).email({
|
|
306
|
+
minDomainSegments: 2,
|
|
307
|
+
tlds: {
|
|
308
|
+
allow: true,
|
|
309
|
+
min: 2
|
|
310
|
+
}
|
|
311
|
+
}).messages({
|
|
312
|
+
"string.max": `Email address must be ${EMAIL_MAX} characters or less`,
|
|
313
|
+
"string.empty": "Enter a personal email address",
|
|
314
|
+
"string.email": "Enter an email address, like name@example.com"
|
|
315
|
+
})
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
// src/schemas/personal/personal-phone-schema.js
|
|
319
|
+
import Joi7 from "joi";
|
|
320
|
+
var personalPhoneSchema = Joi7.object({
|
|
321
|
+
personalTelephone: Joi7.string().empty("").min(PHONE_NUMBER_MIN).max(PHONE_NUMBER_MAX).pattern(PHONE_NUMBER_PATTERN).messages({
|
|
322
|
+
"string.min": `Personal telephone number must be ${PHONE_NUMBER_MIN} characters or more`,
|
|
323
|
+
"string.max": `Personal telephone number must be ${PHONE_NUMBER_MAX} characters or less`,
|
|
324
|
+
"string.pattern.base": "Personal telephone number must only include numbers 0 to 9 and special characters such as spaces, brackets and +"
|
|
325
|
+
}),
|
|
326
|
+
personalMobile: Joi7.string().empty("").min(PHONE_NUMBER_MIN).max(PHONE_NUMBER_MAX).pattern(PHONE_NUMBER_PATTERN).messages({
|
|
327
|
+
"string.min": `Personal mobile phone number must be ${PHONE_NUMBER_MIN} characters or more`,
|
|
328
|
+
"string.max": `Personal mobile phone number must be ${PHONE_NUMBER_MAX} characters or less`,
|
|
329
|
+
"string.pattern.base": "Personal mobile phone number must only include numbers 0 to 9 and special characters such as spaces, brackets and +"
|
|
330
|
+
})
|
|
331
|
+
}).or("personalTelephone", "personalMobile").messages({
|
|
332
|
+
"object.missing": "Enter at least one phone number"
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// src/schemas/personal/personal-schemas.js
|
|
336
|
+
var personalSchemas = {
|
|
337
|
+
name: personalNameSchema,
|
|
338
|
+
dob: personalDobSchema,
|
|
339
|
+
address: addressSchema,
|
|
340
|
+
phone: personalPhoneSchema,
|
|
341
|
+
email: personalEmailSchema
|
|
342
|
+
};
|
|
343
|
+
|
|
85
344
|
// src/schemas/schemas.js
|
|
86
345
|
var schemas = {
|
|
87
346
|
business: businessSchemas,
|
|
88
|
-
customer: customerSchemas
|
|
347
|
+
customer: customerSchemas,
|
|
348
|
+
personal: personalSchemas
|
|
89
349
|
};
|
|
90
350
|
|
|
91
351
|
// src/utils/format-validation-errors.js
|
package/package.json
CHANGED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export {
|
|
2
|
+
FIRST_NAME_MAX,
|
|
3
|
+
LAST_NAME_MAX,
|
|
4
|
+
MIDDLE_NAMES_MAX,
|
|
5
|
+
EMAIL_MAX,
|
|
6
|
+
PHONE_NUMBER_MIN,
|
|
7
|
+
PHONE_NUMBER_MAX,
|
|
8
|
+
MAX_AGE_YEARS,
|
|
9
|
+
ADDRESS_LINE_MAX,
|
|
10
|
+
TOWN_CITY_MAX,
|
|
11
|
+
COUNTY_MAX,
|
|
12
|
+
COUNTRY_MAX,
|
|
13
|
+
POSTCODE_MAX,
|
|
14
|
+
PHONE_NUMBER_PATTERN,
|
|
15
|
+
MONTH_MAP
|
|
16
|
+
} from './validation-fields-export.js'
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const MONTH_MAP = {
|
|
2
|
+
january: 1,
|
|
3
|
+
jan: 1,
|
|
4
|
+
february: 2,
|
|
5
|
+
feb: 2,
|
|
6
|
+
march: 3,
|
|
7
|
+
mar: 3,
|
|
8
|
+
april: 4,
|
|
9
|
+
apr: 4,
|
|
10
|
+
may: 5,
|
|
11
|
+
june: 6,
|
|
12
|
+
jun: 6,
|
|
13
|
+
july: 7,
|
|
14
|
+
jul: 7,
|
|
15
|
+
august: 8,
|
|
16
|
+
aug: 8,
|
|
17
|
+
september: 9,
|
|
18
|
+
sep: 9,
|
|
19
|
+
sept: 9,
|
|
20
|
+
october: 10,
|
|
21
|
+
oct: 10,
|
|
22
|
+
november: 11,
|
|
23
|
+
nov: 11,
|
|
24
|
+
december: 12,
|
|
25
|
+
dec: 12
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const PHONE_NUMBER_PATTERN = /^\+?[0-9 ()]+$/
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export {
|
|
2
|
+
FIRST_NAME_MAX,
|
|
3
|
+
LAST_NAME_MAX,
|
|
4
|
+
MIDDLE_NAMES_MAX,
|
|
5
|
+
EMAIL_MAX,
|
|
6
|
+
PHONE_NUMBER_MIN,
|
|
7
|
+
PHONE_NUMBER_MAX,
|
|
8
|
+
MAX_AGE_YEARS,
|
|
9
|
+
ADDRESS_LINE_MAX,
|
|
10
|
+
TOWN_CITY_MAX,
|
|
11
|
+
COUNTY_MAX,
|
|
12
|
+
COUNTRY_MAX,
|
|
13
|
+
POSTCODE_MAX
|
|
14
|
+
} from './validation-fields.js'
|
|
15
|
+
|
|
16
|
+
export { PHONE_NUMBER_PATTERN } from './patterns.js'
|
|
17
|
+
export { MONTH_MAP } from './month-map.js'
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const FIRST_NAME_MAX = 100
|
|
2
|
+
export const LAST_NAME_MAX = 100
|
|
3
|
+
export const MIDDLE_NAMES_MAX = 100
|
|
4
|
+
export const EMAIL_MAX = 254
|
|
5
|
+
export const PHONE_NUMBER_MIN = 10
|
|
6
|
+
export const PHONE_NUMBER_MAX = 50
|
|
7
|
+
export const MAX_AGE_YEARS = 120
|
|
8
|
+
|
|
9
|
+
// Address
|
|
10
|
+
export const ADDRESS_LINE_MAX = 100
|
|
11
|
+
export const TOWN_CITY_MAX = 60
|
|
12
|
+
export const COUNTY_MAX = 60
|
|
13
|
+
export const COUNTRY_MAX = 60
|
|
14
|
+
export const POSTCODE_MAX = 8
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import Joi from 'joi'
|
|
2
|
+
import {
|
|
3
|
+
ADDRESS_LINE_MAX,
|
|
4
|
+
TOWN_CITY_MAX,
|
|
5
|
+
COUNTY_MAX,
|
|
6
|
+
POSTCODE_MAX,
|
|
7
|
+
COUNTRY_MAX
|
|
8
|
+
} from '../constants/validation-fields.js'
|
|
9
|
+
|
|
10
|
+
export const addressSchema = Joi.object({
|
|
11
|
+
address1: Joi.string()
|
|
12
|
+
.required()
|
|
13
|
+
.max(ADDRESS_LINE_MAX)
|
|
14
|
+
.messages({
|
|
15
|
+
'string.empty': 'Enter address line 1, typically the building and street',
|
|
16
|
+
'string.max': `Address line 1 must be ${ADDRESS_LINE_MAX} characters or less`,
|
|
17
|
+
'any.required': 'Enter address line 1, typically the building and street'
|
|
18
|
+
}),
|
|
19
|
+
address2: Joi.string()
|
|
20
|
+
.allow('')
|
|
21
|
+
.max(ADDRESS_LINE_MAX)
|
|
22
|
+
.messages({
|
|
23
|
+
'string.max': `Address line 2 must be ${ADDRESS_LINE_MAX} characters or less`
|
|
24
|
+
}),
|
|
25
|
+
address3: Joi.string()
|
|
26
|
+
.allow('')
|
|
27
|
+
.max(ADDRESS_LINE_MAX)
|
|
28
|
+
.messages({
|
|
29
|
+
'string.max': `Address line 3 must be ${ADDRESS_LINE_MAX} characters or less`
|
|
30
|
+
}),
|
|
31
|
+
city: Joi.string()
|
|
32
|
+
.required()
|
|
33
|
+
.max(TOWN_CITY_MAX)
|
|
34
|
+
.messages({
|
|
35
|
+
'string.empty': 'Enter town or city',
|
|
36
|
+
'string.max': `Town or city must be ${TOWN_CITY_MAX} characters or less`,
|
|
37
|
+
'any.required': 'Enter town or city'
|
|
38
|
+
}),
|
|
39
|
+
county: Joi.string()
|
|
40
|
+
.allow('')
|
|
41
|
+
.max(COUNTY_MAX)
|
|
42
|
+
.messages({
|
|
43
|
+
'string.max': `County must be ${COUNTY_MAX} characters or less`
|
|
44
|
+
}),
|
|
45
|
+
postcode: Joi.string()
|
|
46
|
+
.required()
|
|
47
|
+
.max(POSTCODE_MAX)
|
|
48
|
+
.messages({
|
|
49
|
+
'any.required': 'Enter a postal code or zip code',
|
|
50
|
+
'string.empty': 'Enter a postal code or zip code',
|
|
51
|
+
'string.max': `Postal code or zip code must be ${POSTCODE_MAX} characters or less`
|
|
52
|
+
}),
|
|
53
|
+
country: Joi.string()
|
|
54
|
+
.required()
|
|
55
|
+
.max(COUNTRY_MAX)
|
|
56
|
+
.messages({
|
|
57
|
+
'string.empty': 'Enter a country',
|
|
58
|
+
'string.max': `Country must be ${COUNTRY_MAX} characters or less`,
|
|
59
|
+
'any.required': 'Enter a country'
|
|
60
|
+
})
|
|
61
|
+
})
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import Joi from 'joi'
|
|
2
|
+
import { MONTH_MAP, MAX_AGE_YEARS } from '../../constants/validation-fields-export.js'
|
|
3
|
+
|
|
4
|
+
export const personalDobSchema = Joi.object({
|
|
5
|
+
day: Joi.string().allow(''),
|
|
6
|
+
month: Joi.string().allow(''),
|
|
7
|
+
year: Joi.string().allow('')
|
|
8
|
+
}).custom((value, helpers) => {
|
|
9
|
+
const { day, month, year } = value
|
|
10
|
+
|
|
11
|
+
// Check for missing fields or combinations
|
|
12
|
+
const missingFieldsError = checkMissingFields(day, month, year, helpers)
|
|
13
|
+
if (missingFieldsError) {
|
|
14
|
+
return missingFieldsError
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Year format check (must be 4 digits)
|
|
18
|
+
if (year && year.length !== 4) {
|
|
19
|
+
return makeError(helpers, 'dob.yearLength', ['year'])
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Normalise and validate month input (can be name, abbreviation, or number)
|
|
23
|
+
const monthValue = getMonthNumber(month, helpers)
|
|
24
|
+
if (monthValue.isJoiError) {
|
|
25
|
+
return monthValue
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Validate that a real, valid date exists
|
|
29
|
+
const fullDate = getFullDate(day, monthValue, year, helpers)
|
|
30
|
+
if (fullDate.isJoiError) {
|
|
31
|
+
return fullDate
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Date must be in the past
|
|
35
|
+
if (fullDate > new Date()) {
|
|
36
|
+
return makeError(helpers, 'dob.future', ['day', 'month', 'year'])
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Date must not be more than 120 years ago
|
|
40
|
+
const tooOldError = checkNotTooOld(fullDate, helpers)
|
|
41
|
+
if (tooOldError) {
|
|
42
|
+
return tooOldError
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return value
|
|
46
|
+
}).messages({
|
|
47
|
+
'dob.missingAll': 'Enter your date of birth',
|
|
48
|
+
'dob.missingDay': 'Date of birth must include a day',
|
|
49
|
+
'dob.missingMonth': 'Date of birth must include a month',
|
|
50
|
+
'dob.missingYear': 'Date of birth must include a year',
|
|
51
|
+
'dob.missingDayMonth': 'Date of birth must include a day and month',
|
|
52
|
+
'dob.missingDayYear': 'Date of birth must include a day and year',
|
|
53
|
+
'dob.missingMonthYear': 'Date of birth must include a month and year',
|
|
54
|
+
'dob.yearLength': 'Enter a year with 4 numbers, like 1975',
|
|
55
|
+
'dob.invalid': 'Date of birth must be a real date',
|
|
56
|
+
'dob.future': 'Date of birth must be in the past',
|
|
57
|
+
'dob.tooOld': 'Date of birth must be on or after {{#oldest}}'
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
const checkNotTooOld = (fullDate, helpers) => {
|
|
61
|
+
const oldestDateAllowed = getOldestAllowedDate()
|
|
62
|
+
|
|
63
|
+
if (fullDate < oldestDateAllowed) {
|
|
64
|
+
// Create date string from 120 years ago (to use for the error)
|
|
65
|
+
const oldestDateString = oldestDateAllowed.toLocaleDateString('en-GB', {
|
|
66
|
+
day: 'numeric',
|
|
67
|
+
month: 'long',
|
|
68
|
+
year: 'numeric'
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
// Pass the date string as context so the message template can use it
|
|
72
|
+
const error = helpers.error('dob.tooOld', { oldest: oldestDateString })
|
|
73
|
+
error.path = ['day', 'month', 'year']
|
|
74
|
+
|
|
75
|
+
return error
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return null
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const getOldestAllowedDate = () => {
|
|
82
|
+
const date = new Date()
|
|
83
|
+
date.setUTCHours(0, 0, 0, 0)
|
|
84
|
+
date.setUTCFullYear(date.getUTCFullYear() - MAX_AGE_YEARS)
|
|
85
|
+
|
|
86
|
+
return date
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Gets and validates a full date (day, month, year) to ensure it is a real and valid date.
|
|
91
|
+
*/
|
|
92
|
+
const getFullDate = (day, monthValue, year, helpers) => {
|
|
93
|
+
const dayValue = Number.parseInt(day, 10)
|
|
94
|
+
const yearValue = Number.parseInt(year, 10)
|
|
95
|
+
const date = new Date(Date.UTC(yearValue, monthValue - 1, dayValue))
|
|
96
|
+
|
|
97
|
+
if (
|
|
98
|
+
date.getUTCFullYear() !== yearValue ||
|
|
99
|
+
date.getUTCMonth() + 1 !== monthValue ||
|
|
100
|
+
date.getUTCDate() !== dayValue
|
|
101
|
+
) {
|
|
102
|
+
return makeError(helpers, 'dob.invalid', ['day', 'month', 'year'])
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return date
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Normalises a month value entered by the user.
|
|
110
|
+
*
|
|
111
|
+
* A user can enter either:
|
|
112
|
+
* - a month number (e.g. "10")
|
|
113
|
+
* - a full month name (e.g. "October")
|
|
114
|
+
* - or a common abbreviation (e.g. "Oct")
|
|
115
|
+
*
|
|
116
|
+
* If the value is not numeric, it is converted to lowercase and looked up in the MONTH_MAP,
|
|
117
|
+
* which maps all valid month names and abbreviations to their corresponding number values
|
|
118
|
+
* (e.g. "january" → 1, "feb" → 2).
|
|
119
|
+
*
|
|
120
|
+
* If a valid mapping is found, the mapped number is returned.
|
|
121
|
+
* If no mapping exists, an error is returned via `makeError`, as the input is likely invalid.
|
|
122
|
+
* If the month is already numeric, it is simply parsed and returned as a number.
|
|
123
|
+
*/
|
|
124
|
+
const getMonthNumber = (month, helpers) => {
|
|
125
|
+
if (Number.isNaN(Number(month))) {
|
|
126
|
+
const lower = month.toLowerCase()
|
|
127
|
+
const mapped = MONTH_MAP[lower]
|
|
128
|
+
|
|
129
|
+
if (!mapped) {
|
|
130
|
+
return makeError(helpers, 'dob.invalid', ['month'])
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return mapped
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return Number.parseInt(month, 10)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Validates combinations of missing date fields
|
|
141
|
+
* (e.g. day missing, month+year entered, etc.)
|
|
142
|
+
*/
|
|
143
|
+
const checkMissingFields = (day, month, year, helpers) => {
|
|
144
|
+
if (!day && !month && !year) {
|
|
145
|
+
return makeError(helpers, 'dob.missingAll', ['day', 'month', 'year'])
|
|
146
|
+
}
|
|
147
|
+
if (!day && month && year) {
|
|
148
|
+
return makeError(helpers, 'dob.missingDay', ['day'])
|
|
149
|
+
}
|
|
150
|
+
if (day && !month && year) {
|
|
151
|
+
return makeError(helpers, 'dob.missingMonth', ['month'])
|
|
152
|
+
}
|
|
153
|
+
if (day && month && !year) {
|
|
154
|
+
return makeError(helpers, 'dob.missingYear', ['year'])
|
|
155
|
+
}
|
|
156
|
+
if (!day && !month && year) {
|
|
157
|
+
return makeError(helpers, 'dob.missingDayMonth', ['day', 'month'])
|
|
158
|
+
}
|
|
159
|
+
if (!day && month && !year) {
|
|
160
|
+
return makeError(helpers, 'dob.missingDayYear', ['day', 'year'])
|
|
161
|
+
}
|
|
162
|
+
if (day && !month && !year) {
|
|
163
|
+
return makeError(helpers, 'dob.missingMonthYear', ['month', 'year'])
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return null
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Creates a Joi error and manually sets its path so the right input fields highlight.
|
|
171
|
+
*/
|
|
172
|
+
const makeError = (helpers, code, fields) => {
|
|
173
|
+
const error = helpers.error(code)
|
|
174
|
+
error.path = fields
|
|
175
|
+
error.isJoiError = true
|
|
176
|
+
|
|
177
|
+
return error
|
|
178
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import Joi from 'joi'
|
|
2
|
+
import { EMAIL_MAX } from '../../constants/validation-fields.js'
|
|
3
|
+
|
|
4
|
+
export const personalEmailSchema = Joi.object({
|
|
5
|
+
personalEmail: Joi.string()
|
|
6
|
+
.required()
|
|
7
|
+
.max(EMAIL_MAX)
|
|
8
|
+
.email({
|
|
9
|
+
minDomainSegments: 2,
|
|
10
|
+
tlds: {
|
|
11
|
+
allow: true,
|
|
12
|
+
min: 2
|
|
13
|
+
}
|
|
14
|
+
})
|
|
15
|
+
.messages({
|
|
16
|
+
'string.max': `Email address must be ${EMAIL_MAX} characters or less`,
|
|
17
|
+
'string.empty': 'Enter a personal email address',
|
|
18
|
+
'string.email': 'Enter an email address, like name@example.com'
|
|
19
|
+
})
|
|
20
|
+
})
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import Joi from 'joi'
|
|
2
|
+
import {
|
|
3
|
+
FIRST_NAME_MAX,
|
|
4
|
+
LAST_NAME_MAX,
|
|
5
|
+
MIDDLE_NAMES_MAX
|
|
6
|
+
} from '../../constants/validation-fields.js'
|
|
7
|
+
|
|
8
|
+
export const personalNameSchema = Joi.object({
|
|
9
|
+
first: Joi.string()
|
|
10
|
+
.required()
|
|
11
|
+
.max(FIRST_NAME_MAX)
|
|
12
|
+
.messages({
|
|
13
|
+
'string.empty': 'Enter first name',
|
|
14
|
+
'string.max': `First name must be ${FIRST_NAME_MAX} characters or less`,
|
|
15
|
+
'any.required': 'Enter first name'
|
|
16
|
+
}),
|
|
17
|
+
last: Joi.string()
|
|
18
|
+
.required()
|
|
19
|
+
.max(LAST_NAME_MAX)
|
|
20
|
+
.messages({
|
|
21
|
+
'string.empty': 'Enter last name',
|
|
22
|
+
'string.max': `Last name must be ${LAST_NAME_MAX} characters or less`,
|
|
23
|
+
'any.required': 'Enter last name'
|
|
24
|
+
}),
|
|
25
|
+
middle: Joi.string()
|
|
26
|
+
.allow('')
|
|
27
|
+
.max(MIDDLE_NAMES_MAX)
|
|
28
|
+
.messages({
|
|
29
|
+
'string.max': `Middle names must be ${MIDDLE_NAMES_MAX} characters or less`
|
|
30
|
+
})
|
|
31
|
+
})
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import Joi from 'joi'
|
|
2
|
+
import {
|
|
3
|
+
PHONE_NUMBER_MIN,
|
|
4
|
+
PHONE_NUMBER_MAX,
|
|
5
|
+
PHONE_NUMBER_PATTERN
|
|
6
|
+
} from '../../constants/validation-fields-export.js'
|
|
7
|
+
|
|
8
|
+
export const personalPhoneSchema = Joi.object({
|
|
9
|
+
personalTelephone: Joi.string()
|
|
10
|
+
.empty('')
|
|
11
|
+
.min(PHONE_NUMBER_MIN)
|
|
12
|
+
.max(PHONE_NUMBER_MAX)
|
|
13
|
+
.pattern(PHONE_NUMBER_PATTERN)
|
|
14
|
+
.messages({
|
|
15
|
+
'string.min': `Personal telephone number must be ${PHONE_NUMBER_MIN} characters or more`,
|
|
16
|
+
'string.max': `Personal telephone number must be ${PHONE_NUMBER_MAX} characters or less`,
|
|
17
|
+
'string.pattern.base': 'Personal telephone number must only include numbers 0 to 9 and special characters such as spaces, brackets and +'
|
|
18
|
+
}),
|
|
19
|
+
personalMobile: Joi.string()
|
|
20
|
+
.empty('')
|
|
21
|
+
.min(PHONE_NUMBER_MIN)
|
|
22
|
+
.max(PHONE_NUMBER_MAX)
|
|
23
|
+
.pattern(PHONE_NUMBER_PATTERN)
|
|
24
|
+
.messages({
|
|
25
|
+
'string.min': `Personal mobile phone number must be ${PHONE_NUMBER_MIN} characters or more`,
|
|
26
|
+
'string.max': `Personal mobile phone number must be ${PHONE_NUMBER_MAX} characters or less`,
|
|
27
|
+
'string.pattern.base': 'Personal mobile phone number must only include numbers 0 to 9 and special characters such as spaces, brackets and +'
|
|
28
|
+
})
|
|
29
|
+
})
|
|
30
|
+
.or('personalTelephone', 'personalMobile')
|
|
31
|
+
.messages({
|
|
32
|
+
'object.missing': 'Enter at least one phone number'
|
|
33
|
+
})
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { personalNameSchema } from './personal-name-schema.js'
|
|
2
|
+
import { personalDobSchema } from './personal-dob-schema.js'
|
|
3
|
+
import { addressSchema } from '../address-schema.js'
|
|
4
|
+
import { personalEmailSchema } from './personal-email-schema.js'
|
|
5
|
+
import { personalPhoneSchema } from './personal-phone-schema.js'
|
|
6
|
+
|
|
7
|
+
export const personalSchemas = {
|
|
8
|
+
name: personalNameSchema,
|
|
9
|
+
dob: personalDobSchema,
|
|
10
|
+
address: addressSchema,
|
|
11
|
+
phone: personalPhoneSchema,
|
|
12
|
+
email: personalEmailSchema
|
|
13
|
+
}
|
package/src/schemas/schemas.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { businessSchemas } from './business/business-schemas.js'
|
|
2
2
|
import { customerSchemas } from './customer/customer-schemas.js'
|
|
3
|
+
import { personalSchemas } from './personal/personal-schemas.js'
|
|
3
4
|
|
|
4
5
|
export const schemas = {
|
|
5
6
|
business: businessSchemas,
|
|
6
|
-
customer: customerSchemas
|
|
7
|
+
customer: customerSchemas,
|
|
8
|
+
personal: personalSchemas
|
|
7
9
|
}
|