@defra/fcp-sfd-frontend-engine 0.17.0 → 0.19.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.js CHANGED
@@ -184,706 +184,805 @@ var mapBusinessDetails = (value) => {
184
184
  };
185
185
  };
186
186
 
187
- // src/mappers/mappers.js
188
- var mappers = {
189
- personalBusinessDetails: mapPersonalBusinessDetails,
190
- address: mapAddress,
191
- customerName: mapCustomerName,
192
- businessDetails: mapBusinessDetails
193
- };
194
-
195
- // src/mutations/business/update-business-email.js
196
- var updateBusinessEmailMutation = `
197
- mutation Mutation($input: UpdateBusinessEmailInput!) {
198
- updateBusinessEmail(input: $input) {
199
- business {
200
- info {
201
- email {
202
- address
203
- }
204
- }
205
- }
206
- success
187
+ // src/utils/joi.js
188
+ import BaseJoi from "joi";
189
+ var Joi = BaseJoi.extend((joi) => ({
190
+ type: "string",
191
+ base: joi.string(),
192
+ messages: {
193
+ "string.noControlChars": "Field must not contain invalid characters"
194
+ },
195
+ validate(value, helpers) {
196
+ if (typeof value === "string" && !NO_CONTROL_CHARS_PATTERN.test(value)) {
197
+ return { value, errors: helpers.error("string.noControlChars") };
207
198
  }
199
+ return { value };
208
200
  }
209
- `;
201
+ }));
210
202
 
211
- // src/mutations/business/update-business-name.js
212
- var updateBusinessNameMutation = `
213
- mutation UpdateBusinessName($input: UpdateBusinessNameInput!) {
214
- updateBusinessName(input: $input) {
215
- business {
216
- info {
217
- name
218
- }
219
- }
220
- success
221
- }
222
- }
223
- `;
203
+ // src/schemas/business/business-sbi-schema.js
204
+ var businessSbiSchema = Joi.object({
205
+ sbi: Joi.string().pattern(/^\d{9}$/).allow("").optional().messages({
206
+ "string.pattern.base": "Enter the full SBI",
207
+ "string.noControlChars": "SBI must not contain invalid characters"
208
+ })
209
+ });
224
210
 
225
- // src/mutations/personal/update-customer-name.js
226
- var updateCustomerNameMutation = `
227
- mutation UpdateCustomerName($input: UpdateCustomerNameInput!) {
228
- updateCustomerName(input: $input) {
229
- customer {
230
- info {
231
- name {
232
- first
233
- last
234
- middle
235
- }
236
- }
237
- }
238
- }
239
- }
240
- `;
211
+ // src/schemas/shared/address-schema.js
212
+ var addressSchema = Joi.object({
213
+ address1: Joi.string().trim().required().max(ADDRESS_LINE_MAX).messages({
214
+ "string.empty": "Enter address line 1, typically the building and street",
215
+ "string.max": `Address line 1 must be ${ADDRESS_LINE_MAX} characters or less`,
216
+ "any.required": "Enter address line 1, typically the building and street",
217
+ "string.noControlChars": "Address line 1 must not contain invalid characters"
218
+ }),
219
+ address2: Joi.string().trim().allow("").max(ADDRESS_LINE_MAX).messages({
220
+ "string.max": `Address line 2 must be ${ADDRESS_LINE_MAX} characters or less`,
221
+ "string.noControlChars": "Address line 2 must not contain invalid characters"
222
+ }),
223
+ address3: Joi.string().trim().allow("").max(ADDRESS_LINE_MAX).messages({
224
+ "string.max": `Address line 3 must be ${ADDRESS_LINE_MAX} characters or less`,
225
+ "string.noControlChars": "Address line 3 must not contain invalid characters"
226
+ }),
227
+ city: Joi.string().trim().required().max(TOWN_CITY_MAX).messages({
228
+ "string.empty": "Enter town or city",
229
+ "string.max": `Town or city must be ${TOWN_CITY_MAX} characters or less`,
230
+ "any.required": "Enter town or city",
231
+ "string.noControlChars": "Town or city must not contain invalid characters"
232
+ }),
233
+ county: Joi.string().trim().allow("").max(COUNTY_MAX).messages({
234
+ "string.max": `County must be ${COUNTY_MAX} characters or less`,
235
+ "string.noControlChars": "County must not contain invalid characters"
236
+ }),
237
+ postcode: Joi.string().trim().required().max(POSTCODE_MAX).messages({
238
+ "any.required": "Enter a postal code or zip code",
239
+ "string.empty": "Enter a postal code or zip code",
240
+ "string.max": `Postal code or zip code must be ${POSTCODE_MAX} characters or less`,
241
+ "string.noControlChars": "Postal code or zip code must not contain invalid characters"
242
+ }),
243
+ country: Joi.string().trim().required().max(COUNTRY_MAX).messages({
244
+ "string.empty": "Enter a country",
245
+ "string.max": `Country must be ${COUNTRY_MAX} characters or less`,
246
+ "any.required": "Enter a country",
247
+ "string.noControlChars": "Country must not contain invalid characters"
248
+ })
249
+ });
241
250
 
242
- // src/mutations/personal/update-customer-email.js
243
- var updateCustomerEmailMutation = `
244
- mutation UpdateCustomerEmail($input: UpdateCustomerEmailInput!) {
245
- updateCustomerEmail(input: $input) {
246
- customer {
247
- info {
248
- email {
249
- address
250
- }
251
- }
252
- }
253
- }
254
- }
255
- `;
251
+ // src/schemas/business/business-name-schema.js
252
+ var businessNameSchema = Joi.object({
253
+ businessName: Joi.string().trim().required().max(BUSINESS_NAME_MAX).messages({
254
+ "string.empty": "Enter business name",
255
+ "string.max": `Business name must be ${BUSINESS_NAME_MAX} characters or less`,
256
+ "any.required": "Enter business name",
257
+ "string.noControlChars": "Business name must not contain invalid characters"
258
+ })
259
+ });
256
260
 
257
- // src/mutations/personal/update-customer-phone.js
258
- var updateCustomerPhoneMutation = `
259
- mutation UpdateCustomerPhone($input: UpdateCustomerPhoneInput!) {
260
- updateCustomerPhone(input: $input) {
261
- customer {
262
- info {
263
- phone {
264
- landline
265
- mobile
266
- }
267
- }
268
- }
261
+ // src/schemas/business/business-email-schema.js
262
+ var businessEmailSchema = Joi.object({
263
+ businessEmail: Joi.string().required().max(EMAIL_MAX).email({
264
+ minDomainSegments: 2,
265
+ tlds: {
266
+ allow: true
269
267
  }
268
+ }).messages({
269
+ "string.max": `Business email address must be ${EMAIL_MAX} characters or less`,
270
+ "string.empty": "Enter business email address",
271
+ "string.email": "Enter an email address, like name@example.com",
272
+ "string.noControlChars": "Business email address must not contain invalid characters"
273
+ })
274
+ });
275
+
276
+ // src/schemas/business/business-phone-schema.js
277
+ var businessPhoneSchema = Joi.object({
278
+ businessTelephone: Joi.string().trim().empty("").min(PHONE_NUMBER_MIN).max(PHONE_NUMBER_MAX).pattern(PHONE_NUMBER_PATTERN).messages({
279
+ "string.min": `Business telephone number must be ${PHONE_NUMBER_MIN} characters or more`,
280
+ "string.max": `Business telephone number must be ${PHONE_NUMBER_MAX} characters or less`,
281
+ "string.pattern.base": "Business telephone number must only include numbers 0 to 9 and special characters such as spaces, brackets and +",
282
+ "string.noControlChars": "Business telephone number must not contain invalid characters"
283
+ }),
284
+ businessMobile: Joi.string().trim().empty("").min(PHONE_NUMBER_MIN).max(PHONE_NUMBER_MAX).pattern(PHONE_NUMBER_PATTERN).messages({
285
+ "string.min": `Business mobile phone number must be ${PHONE_NUMBER_MIN} characters or more`,
286
+ "string.max": `Business mobile phone number must be ${PHONE_NUMBER_MAX} characters or less`,
287
+ "string.pattern.base": "Business mobile phone number must only include numbers 0 to 9 and special characters such as spaces, brackets and +",
288
+ "string.noControlChars": "Business mobile phone number must not contain invalid characters"
289
+ })
290
+ }).or("businessTelephone", "businessMobile").messages({
291
+ "object.missing": "Enter at least one phone number"
292
+ });
293
+
294
+ // src/schemas/business/business-vat-schema.js
295
+ var businessVatSchema = Joi.object({
296
+ vatNumber: Joi.string().pattern(/^\d{9}$/).allow("").optional().messages({
297
+ "string.pattern.base": "Enter a VAT registration number, like 123456789",
298
+ "string.noControlChars": "VAT registration number must not contain invalid characters"
299
+ })
300
+ });
301
+
302
+ // src/schemas/business/business-vat-change-schema.js
303
+ var businessVatChangeSchema = Joi.object({
304
+ vatNumber: Joi.string().pattern(/^\d{9}$/).required().messages({
305
+ "string.pattern.base": "Enter a VAT registration number, like 123456789",
306
+ "string.empty": "Enter a VAT registration number",
307
+ "any.required": "Enter a VAT registration number",
308
+ "string.noControlChars": "VAT registration number must not contain invalid characters"
309
+ })
310
+ });
311
+
312
+ // src/schemas/business/business-vat-remove-schema.js
313
+ var businessVatRemoveSchema = Joi.object({
314
+ confirmRemove: Joi.string().valid("yes", "no").required().messages({
315
+ "any.required": "Select yes if you want to remove your VAT registration number",
316
+ "any.only": "Select yes if you want to remove your VAT registration number"
317
+ })
318
+ });
319
+
320
+ // src/schemas/business/business-schemas.js
321
+ var businessSchemas = {
322
+ sbi: businessSbiSchema,
323
+ details: {
324
+ name: businessNameSchema,
325
+ address: addressSchema,
326
+ phone: businessPhoneSchema,
327
+ email: businessEmailSchema,
328
+ vat: businessVatSchema
329
+ },
330
+ vat: {
331
+ change: businessVatChangeSchema,
332
+ remove: businessVatRemoveSchema
270
333
  }
271
- `;
334
+ };
272
335
 
273
- // src/mutations/mutations.js
274
- var mutations = {
275
- updateBusinessEmail: updateBusinessEmailMutation,
276
- updateBusinessName: updateBusinessNameMutation,
277
- updateCustomerName: updateCustomerNameMutation,
278
- updateCustomerEmail: updateCustomerEmailMutation,
279
- updateCustomerPhone: updateCustomerPhoneMutation
336
+ // src/schemas/customer/customer-crn-schema.js
337
+ var customerCrnSchema = Joi.object({
338
+ crn: Joi.string().pattern(/^\d{10}$/).allow("").optional().messages({
339
+ "string.pattern.base": "Enter the full CRN",
340
+ "string.noControlChars": "CRN must not contain invalid characters"
341
+ })
342
+ });
343
+
344
+ // src/schemas/customer/customer-schemas.js
345
+ var customerSchemas = {
346
+ crn: customerCrnSchema
280
347
  };
281
348
 
282
- // src/presenters/base-presenter.js
283
- var BACK_LINK_DISPLAY_MAX = 50;
284
- var formatBackLink = (businessName) => {
285
- if (businessName.length > BACK_LINK_DISPLAY_MAX) {
286
- return `Back to ${businessName.slice(0, BACK_LINK_DISPLAY_MAX)}\u2026`;
349
+ // src/schemas/personal/personal-name-schema.js
350
+ var personalNameSchema = Joi.object({
351
+ first: Joi.string().trim().required().max(FIRST_NAME_MAX).messages({
352
+ "string.empty": "Enter first name",
353
+ "string.max": `First name must be ${FIRST_NAME_MAX} characters or less`,
354
+ "any.required": "Enter first name",
355
+ "string.noControlChars": "First name must not contain invalid characters"
356
+ }),
357
+ last: Joi.string().trim().required().max(LAST_NAME_MAX).messages({
358
+ "string.empty": "Enter last name",
359
+ "string.max": `Last name must be ${LAST_NAME_MAX} characters or less`,
360
+ "any.required": "Enter last name",
361
+ "string.noControlChars": "Last name must not contain invalid characters"
362
+ }),
363
+ middle: Joi.string().trim().allow("").max(MIDDLE_NAMES_MAX).messages({
364
+ "string.max": `Middle names must be ${MIDDLE_NAMES_MAX} characters or less`,
365
+ "string.noControlChars": "Middle names must not contain invalid characters"
366
+ })
367
+ });
368
+
369
+ // src/schemas/personal/personal-dob-schema.js
370
+ var personalDobSchema = Joi.object({
371
+ day: Joi.string().allow(""),
372
+ month: Joi.string().allow(""),
373
+ year: Joi.string().allow("")
374
+ }).custom((value, helpers) => {
375
+ const { day, month, year } = value;
376
+ const missingFieldsError = checkMissingFields(day, month, year, helpers);
377
+ if (missingFieldsError) {
378
+ return missingFieldsError;
379
+ }
380
+ if (year && year.length !== 4) {
381
+ return makeError(helpers, "dob.yearLength", ["year"]);
382
+ }
383
+ const monthValue = getMonthNumber(month, helpers);
384
+ if (monthValue.isJoiError) {
385
+ return monthValue;
386
+ }
387
+ const fullDate = getFullDate(day, monthValue, year, helpers);
388
+ if (fullDate.isJoiError) {
389
+ return fullDate;
390
+ }
391
+ const today = /* @__PURE__ */ new Date();
392
+ today.setUTCHours(0, 0, 0, 0);
393
+ if (fullDate >= today) {
394
+ return makeError(helpers, "dob.future", ["day", "month", "year"]);
395
+ }
396
+ const tooOldError = checkNotTooOld(fullDate, helpers);
397
+ if (tooOldError) {
398
+ return tooOldError;
399
+ }
400
+ return value;
401
+ }).messages({
402
+ "dob.missingAll": "Enter your date of birth",
403
+ "dob.missingDay": "Date of birth must include a day",
404
+ "dob.missingMonth": "Date of birth must include a month",
405
+ "dob.missingYear": "Date of birth must include a year",
406
+ "dob.missingDayMonth": "Date of birth must include a day and month",
407
+ "dob.missingDayYear": "Date of birth must include a day and year",
408
+ "dob.missingMonthYear": "Date of birth must include a month and year",
409
+ "dob.yearLength": "Enter a year with 4 numbers, like 1975",
410
+ "dob.invalid": "Date of birth must be a real date",
411
+ "dob.future": "Date of birth must be in the past",
412
+ "dob.tooOld": "Date of birth must be on or after {{#oldest}}",
413
+ "string.noControlChars": "Date of birth must not contain invalid characters"
414
+ });
415
+ var checkNotTooOld = (fullDate, helpers) => {
416
+ const oldestDateAllowed = getOldestAllowedDate();
417
+ if (fullDate < oldestDateAllowed) {
418
+ const oldestDateString = oldestDateAllowed.toLocaleDateString("en-GB", {
419
+ day: "numeric",
420
+ month: "long",
421
+ year: "numeric"
422
+ });
423
+ const error = helpers.error("dob.tooOld", { oldest: oldestDateString });
424
+ error.path = ["day", "month", "year"];
425
+ return error;
287
426
  }
288
- return `Back to ${businessName}`;
427
+ return null;
289
428
  };
290
- var formatNumber = (payloadNumber, changedNumber, originalNumber) => {
291
- if (payloadNumber !== void 0) {
292
- return payloadNumber;
293
- }
294
- if (changedNumber !== void 0) {
295
- return changedNumber;
296
- }
297
- return originalNumber;
429
+ var getOldestAllowedDate = () => {
430
+ const date = /* @__PURE__ */ new Date();
431
+ date.setUTCHours(0, 0, 0, 0);
432
+ date.setUTCFullYear(date.getUTCFullYear() - MAX_AGE_YEARS);
433
+ return date;
298
434
  };
299
- var sortErrorsBySectionOrder = (errors, orderedSectionsToFix, SECTION_FIELD_ORDER) => {
300
- const sortedErrors = [];
301
- for (const section of orderedSectionsToFix) {
302
- const fieldsInSection = SECTION_FIELD_ORDER[section] || [];
303
- for (const field of fieldsInSection) {
304
- if (errors[field]) {
305
- sortedErrors.push({
306
- field,
307
- ...errors[field]
308
- });
309
- }
310
- }
435
+ var getFullDate = (day, monthValue, year, helpers) => {
436
+ const dayValue = Number.parseInt(day, 10);
437
+ const yearValue = Number.parseInt(year, 10);
438
+ const date = new Date(Date.UTC(yearValue, monthValue - 1, dayValue));
439
+ if (date.getUTCFullYear() !== yearValue || date.getUTCMonth() + 1 !== monthValue || date.getUTCDate() !== dayValue) {
440
+ return makeError(helpers, "dob.invalid", ["day", "month", "year"]);
311
441
  }
312
- return sortedErrors;
442
+ return date;
313
443
  };
314
-
315
- // src/presenters/address-presenter.js
316
- var addressChangeLink = (postcodeLookup, context) => {
317
- if (context === "business") {
318
- if (postcodeLookup) {
319
- return "/business-address-change";
444
+ var getMonthNumber = (month, helpers) => {
445
+ if (Number.isNaN(Number(month))) {
446
+ const lower = month.toLowerCase();
447
+ const mapped = MONTH_MAP[lower];
448
+ if (!mapped) {
449
+ return makeError(helpers, "dob.invalid", ["month"]);
320
450
  }
321
- return "/business-address-enter";
451
+ return mapped;
322
452
  }
323
- if (context === "personal") {
324
- if (postcodeLookup) {
325
- return "/account-address-change";
326
- }
327
- return "/account-address-enter";
453
+ const parsedMonth = Number.parseInt(month, 10);
454
+ if (parsedMonth < 1 || parsedMonth > 12) {
455
+ return makeError(helpers, "dob.invalid", ["month"]);
328
456
  }
329
- return null;
457
+ return parsedMonth;
330
458
  };
331
- var addressBackLink = (postcodeLookup, context) => {
332
- if (context === "business") {
333
- if (postcodeLookup) {
334
- return { href: "/business-address-select" };
335
- }
336
- return { href: "/business-address-enter" };
459
+ var checkMissingFields = (day, month, year, helpers) => {
460
+ if (!day && !month && !year) {
461
+ return makeError(helpers, "dob.missingAll", ["day", "month", "year"]);
337
462
  }
338
- if (context === "personal") {
339
- if (postcodeLookup) {
340
- return { href: "/account-address-select" };
341
- }
342
- return { href: "/account-address-enter" };
463
+ if (!day && month && year) {
464
+ return makeError(helpers, "dob.missingDay", ["day"]);
343
465
  }
344
- return null;
345
- };
346
- var buildAddressLine = (parts) => {
347
- return parts.filter(Boolean).join(", ") || null;
348
- };
349
- var buildStreetLine = (buildingRange, street) => {
350
- return [buildingRange, street].filter(Boolean).join(" ") || null;
351
- };
352
- var formatLookupAddress = (lookup, city, country, postcode) => ({
353
- address1: buildAddressLine([lookup.pafOrganisationName, lookup.flatName, lookup.buildingName]),
354
- address2: buildStreetLine(lookup.buildingNumberRange, lookup.street),
355
- address3: buildAddressLine([lookup.doubleDependentLocality, lookup.dependentLocality]),
356
- county: lookup.county ?? null,
357
- city: city ?? null,
358
- country: country ?? null,
359
- postcode: postcode ?? null
360
- });
361
- var formatManualAddress = (manual, city, country, postcode) => ({
362
- address1: manual.line1 ?? null,
363
- address2: manual.line2 ?? null,
364
- address3: manual.line3 ?? null,
365
- city: city ?? null,
366
- county: manual.line4 ?? null,
367
- country: country ?? null,
368
- postcode: postcode ?? null
369
- });
370
- var formatDisplayAddress = (address) => {
371
- const { lookup, manual, postcode, country, city } = address;
372
- let addressLines = [];
373
- if (lookup.uprn) {
374
- const buildingAndStreet = [
375
- lookup.buildingNumberRange,
376
- lookup.street
377
- ].filter(Boolean).join(" ");
378
- addressLines = [
379
- lookup.pafOrganisationName,
380
- lookup.flatName,
381
- lookup.buildingName,
382
- buildingAndStreet,
383
- lookup.doubleDependentLocality,
384
- lookup.dependentLocality,
385
- city,
386
- lookup.county
387
- ];
388
- } else {
389
- addressLines = [
390
- manual.line1,
391
- manual.line2,
392
- manual.line3,
393
- city,
394
- manual.line4,
395
- // County
396
- manual.line5
397
- ];
466
+ if (day && !month && year) {
467
+ return makeError(helpers, "dob.missingMonth", ["month"]);
398
468
  }
399
- return [
400
- ...addressLines.filter(Boolean),
401
- postcode,
402
- country
403
- ];
404
- };
405
- var formatOriginalAddress = (originalAddress) => {
406
- const { lookup, manual, city, country, postcode } = originalAddress;
407
- return lookup.uprn ? formatLookupAddress(lookup, city, country, postcode) : formatManualAddress(manual, city, country, postcode);
408
- };
409
- var formatChangedAddress = (changeBusinessAddress) => {
410
- if (!changeBusinessAddress.uprn) {
411
- return changeBusinessAddress;
469
+ if (day && month && !year) {
470
+ return makeError(helpers, "dob.missingYear", ["year"]);
412
471
  }
413
- const {
414
- pafOrganisationName,
415
- flatName,
416
- buildingName,
417
- buildingNumberRange,
418
- street,
419
- doubleDependentLocality,
420
- dependentLocality,
421
- city,
422
- county,
423
- country,
424
- postcode
425
- } = changeBusinessAddress;
426
- return {
427
- address1: buildAddressLine([pafOrganisationName, flatName, buildingName]),
428
- address2: buildStreetLine(buildingNumberRange, street),
429
- address3: buildAddressLine([doubleDependentLocality, dependentLocality]),
430
- city: city ?? null,
431
- county: county ?? null,
432
- country: country ?? null,
433
- postcode: postcode ?? null
434
- };
435
- };
436
- var formatDisplayAddresses = (addresses, previouslyPickedAddress) => {
437
- const displayAddresses = addresses.map((address) => ({
438
- value: `${address.uprn}${address.displayAddress}`,
439
- text: address.displayAddress,
440
- selected: (previouslyPickedAddress == null ? void 0 : previouslyPickedAddress.uprn) === address.uprn && (previouslyPickedAddress == null ? void 0 : previouslyPickedAddress.displayAddress) === address.displayAddress
441
- }));
442
- const hasSelectedAddress = displayAddresses.some((addr) => addr.selected);
443
- const text = addresses.length === 1 ? "1 address found" : `${addresses.length} addresses found`;
444
- displayAddresses.unshift({
445
- value: "display",
446
- text,
447
- selected: !hasSelectedAddress
448
- });
449
- return displayAddresses;
450
- };
451
-
452
- // src/presenters/business-details-presenter.js
453
- var getActionText = (value) => {
454
- return value ? "Change" : "Add";
455
- };
456
- var formatCph = (countyParishHoldings) => {
457
- return (countyParishHoldings || []).filter((cph) => cph == null ? void 0 : cph.cphNumber).map((cph) => cph.cphNumber);
458
- };
459
- var formatCphText = (count) => {
460
- return `County Parish Holding (CPH) number${count !== 1 ? "s" : ""}`;
461
- };
462
- var formatBusinessAddress = (businessAddress) => {
463
- var _a, _b;
464
- if (((_a = businessAddress == null ? void 0 : businessAddress.lookup) == null ? void 0 : _a.uprn) || ((_b = businessAddress == null ? void 0 : businessAddress.manual) == null ? void 0 : _b.line1)) {
465
- return formatDisplayAddress(businessAddress);
472
+ if (!day && !month && year) {
473
+ return makeError(helpers, "dob.missingDayMonth", ["day", "month"]);
466
474
  }
467
- return [];
468
- };
469
-
470
- // src/presenters/presenters.js
471
- var presenters = {
472
- formatBackLink,
473
- formatNumber,
474
- formatDisplayAddress,
475
- formatOriginalAddress,
476
- formatChangedAddress,
477
- formatDisplayAddresses,
478
- sortErrorsBySectionOrder,
479
- getActionText,
480
- formatCph,
481
- formatCphText,
482
- formatBusinessAddress,
483
- addressBackLink,
484
- addressChangeLink
485
- };
486
-
487
- // src/utils/joi.js
488
- import BaseJoi from "joi";
489
- var Joi = BaseJoi.extend((joi) => ({
490
- type: "string",
491
- base: joi.string(),
492
- messages: {
493
- "string.noControlChars": "Field must not contain invalid characters"
494
- },
495
- validate(value, helpers) {
496
- if (typeof value === "string" && !NO_CONTROL_CHARS_PATTERN.test(value)) {
497
- return { value, errors: helpers.error("string.noControlChars") };
498
- }
499
- return { value };
475
+ if (!day && month && !year) {
476
+ return makeError(helpers, "dob.missingDayYear", ["day", "year"]);
477
+ }
478
+ if (day && !month && !year) {
479
+ return makeError(helpers, "dob.missingMonthYear", ["month", "year"]);
500
480
  }
501
- }));
502
-
503
- // src/schemas/business/business-sbi-schema.js
504
- var businessSbiSchema = Joi.object({
505
- sbi: Joi.string().pattern(/^\d{9}$/).allow("").optional().messages({
506
- "string.pattern.base": "Enter the full SBI",
507
- "string.noControlChars": "SBI must not contain invalid characters"
508
- })
509
- });
510
-
511
- // src/schemas/shared/address-schema.js
512
- var addressSchema = Joi.object({
513
- address1: Joi.string().trim().required().max(ADDRESS_LINE_MAX).messages({
514
- "string.empty": "Enter address line 1, typically the building and street",
515
- "string.max": `Address line 1 must be ${ADDRESS_LINE_MAX} characters or less`,
516
- "any.required": "Enter address line 1, typically the building and street",
517
- "string.noControlChars": "Address line 1 must not contain invalid characters"
518
- }),
519
- address2: Joi.string().trim().allow("").max(ADDRESS_LINE_MAX).messages({
520
- "string.max": `Address line 2 must be ${ADDRESS_LINE_MAX} characters or less`,
521
- "string.noControlChars": "Address line 2 must not contain invalid characters"
522
- }),
523
- address3: Joi.string().trim().allow("").max(ADDRESS_LINE_MAX).messages({
524
- "string.max": `Address line 3 must be ${ADDRESS_LINE_MAX} characters or less`,
525
- "string.noControlChars": "Address line 3 must not contain invalid characters"
526
- }),
527
- city: Joi.string().trim().required().max(TOWN_CITY_MAX).messages({
528
- "string.empty": "Enter town or city",
529
- "string.max": `Town or city must be ${TOWN_CITY_MAX} characters or less`,
530
- "any.required": "Enter town or city",
531
- "string.noControlChars": "Town or city must not contain invalid characters"
532
- }),
533
- county: Joi.string().trim().allow("").max(COUNTY_MAX).messages({
534
- "string.max": `County must be ${COUNTY_MAX} characters or less`,
535
- "string.noControlChars": "County must not contain invalid characters"
536
- }),
537
- postcode: Joi.string().trim().required().max(POSTCODE_MAX).messages({
538
- "any.required": "Enter a postal code or zip code",
539
- "string.empty": "Enter a postal code or zip code",
540
- "string.max": `Postal code or zip code must be ${POSTCODE_MAX} characters or less`,
541
- "string.noControlChars": "Postal code or zip code must not contain invalid characters"
542
- }),
543
- country: Joi.string().trim().required().max(COUNTRY_MAX).messages({
544
- "string.empty": "Enter a country",
545
- "string.max": `Country must be ${COUNTRY_MAX} characters or less`,
546
- "any.required": "Enter a country",
547
- "string.noControlChars": "Country must not contain invalid characters"
548
- })
549
- });
550
-
551
- // src/schemas/business/business-name-schema.js
552
- var businessNameSchema = Joi.object({
553
- businessName: Joi.string().trim().required().max(BUSINESS_NAME_MAX).messages({
554
- "string.empty": "Enter business name",
555
- "string.max": `Business name must be ${BUSINESS_NAME_MAX} characters or less`,
556
- "any.required": "Enter business name",
557
- "string.noControlChars": "Business name must not contain invalid characters"
558
- })
559
- });
481
+ return null;
482
+ };
483
+ var makeError = (helpers, code, fields) => {
484
+ const error = helpers.error(code);
485
+ error.path = fields;
486
+ error.isJoiError = true;
487
+ return error;
488
+ };
560
489
 
561
- // src/schemas/business/business-email-schema.js
562
- var businessEmailSchema = Joi.object({
563
- businessEmail: Joi.string().required().max(EMAIL_MAX).email({
490
+ // src/schemas/personal/personal-email-schema.js
491
+ var personalEmailSchema = Joi.object({
492
+ personalEmail: Joi.string().required().max(EMAIL_MAX).email({
564
493
  minDomainSegments: 2,
565
494
  tlds: {
566
- allow: true
495
+ allow: true,
496
+ min: 2
567
497
  }
568
498
  }).messages({
569
- "string.max": `Business email address must be ${EMAIL_MAX} characters or less`,
570
- "string.empty": "Enter business email address",
499
+ "string.max": `Email address must be ${EMAIL_MAX} characters or less`,
500
+ "string.empty": "Enter a personal email address",
571
501
  "string.email": "Enter an email address, like name@example.com",
572
- "string.noControlChars": "Business email address must not contain invalid characters"
502
+ "string.noControlChars": "Personal email address must not contain invalid characters"
573
503
  })
574
504
  });
575
505
 
576
- // src/schemas/business/business-phone-schema.js
577
- var businessPhoneSchema = Joi.object({
578
- businessTelephone: Joi.string().trim().empty("").min(PHONE_NUMBER_MIN).max(PHONE_NUMBER_MAX).pattern(PHONE_NUMBER_PATTERN).messages({
579
- "string.min": `Business telephone number must be ${PHONE_NUMBER_MIN} characters or more`,
580
- "string.max": `Business telephone number must be ${PHONE_NUMBER_MAX} characters or less`,
581
- "string.pattern.base": "Business telephone number must only include numbers 0 to 9 and special characters such as spaces, brackets and +",
582
- "string.noControlChars": "Business telephone number must not contain invalid characters"
506
+ // src/schemas/personal/personal-phone-schema.js
507
+ var personalPhoneSchema = Joi.object({
508
+ personalTelephone: Joi.string().trim().empty("").min(PHONE_NUMBER_MIN).max(PHONE_NUMBER_MAX).pattern(PHONE_NUMBER_PATTERN).messages({
509
+ "string.min": `Personal telephone number must be ${PHONE_NUMBER_MIN} characters or more`,
510
+ "string.max": `Personal telephone number must be ${PHONE_NUMBER_MAX} characters or less`,
511
+ "string.pattern.base": "Personal telephone number must only include numbers 0 to 9 and special characters such as spaces, brackets and +",
512
+ "string.noControlChars": "Personal telephone number must not contain invalid characters"
583
513
  }),
584
- businessMobile: Joi.string().trim().empty("").min(PHONE_NUMBER_MIN).max(PHONE_NUMBER_MAX).pattern(PHONE_NUMBER_PATTERN).messages({
585
- "string.min": `Business mobile phone number must be ${PHONE_NUMBER_MIN} characters or more`,
586
- "string.max": `Business mobile phone number must be ${PHONE_NUMBER_MAX} characters or less`,
587
- "string.pattern.base": "Business mobile phone number must only include numbers 0 to 9 and special characters such as spaces, brackets and +",
588
- "string.noControlChars": "Business mobile phone number must not contain invalid characters"
514
+ personalMobile: Joi.string().trim().empty("").min(PHONE_NUMBER_MIN).max(PHONE_NUMBER_MAX).pattern(PHONE_NUMBER_PATTERN).messages({
515
+ "string.min": `Personal mobile phone number must be ${PHONE_NUMBER_MIN} characters or more`,
516
+ "string.max": `Personal mobile phone number must be ${PHONE_NUMBER_MAX} characters or less`,
517
+ "string.pattern.base": "Personal mobile phone number must only include numbers 0 to 9 and special characters such as spaces, brackets and +",
518
+ "string.noControlChars": "Personal mobile phone number must not contain invalid characters"
589
519
  })
590
- }).or("businessTelephone", "businessMobile").messages({
520
+ }).or("personalTelephone", "personalMobile").messages({
591
521
  "object.missing": "Enter at least one phone number"
592
522
  });
593
523
 
594
- // src/schemas/business/business-vat-schema.js
595
- var businessVatSchema = Joi.object({
596
- vatNumber: Joi.string().pattern(/^\d{9}$/).allow("").optional().messages({
597
- "string.pattern.base": "Enter a VAT registration number, like 123456789",
598
- "string.noControlChars": "VAT registration number must not contain invalid characters"
599
- })
600
- });
524
+ // src/schemas/personal/personal-schemas.js
525
+ var personalSchemas = {
526
+ name: personalNameSchema,
527
+ dob: personalDobSchema,
528
+ address: addressSchema,
529
+ phone: personalPhoneSchema,
530
+ email: personalEmailSchema
531
+ };
601
532
 
602
- // src/schemas/business/business-vat-change-schema.js
603
- var businessVatChangeSchema = Joi.object({
604
- vatNumber: Joi.string().pattern(/^\d{9}$/).required().messages({
605
- "string.pattern.base": "Enter a VAT registration number, like 123456789",
606
- "string.empty": "Enter a VAT registration number",
607
- "any.required": "Enter a VAT registration number",
608
- "string.noControlChars": "VAT registration number must not contain invalid characters"
533
+ // src/schemas/os-places/address-lookup-schema.js
534
+ var addressLookupSchema = Joi.object({
535
+ properties: Joi.object({
536
+ UPRN: Joi.string().required(),
537
+ ADDRESS: Joi.string().required(),
538
+ ORGANISATION_NAME: Joi.string().allow(null),
539
+ DEPARTMENT_NAME: Joi.string().allow(null),
540
+ SUB_BUILDING_NAME: Joi.string().allow(null),
541
+ BUILDING_NAME: Joi.string().allow(null),
542
+ BUILDING_NUMBER: Joi.string().allow(null),
543
+ DEPENDENT_THOROUGHFARE_NAME: Joi.string().allow(null),
544
+ THOROUGHFARE_NAME: Joi.string().allow(null),
545
+ DOUBLE_DEPENDENT_LOCALITY: Joi.string().allow(null),
546
+ DEPENDENT_LOCALITY: Joi.string().allow(null),
547
+ POST_TOWN: Joi.string().required(),
548
+ POSTCODE: Joi.string().required(),
549
+ LOCAL_CUSTODIAN_CODE_DESCRIPTION: Joi.string().allow(null),
550
+ COUNTRY_CODE: Joi.string().required()
551
+ }).unknown(true).required()
552
+ }).unknown(true);
553
+
554
+ // src/schemas/os-places/addresses-schema.js
555
+ var CHOOSE_ADDRESS_ERROR = "Choose an address";
556
+ var addressesSchema = Joi.object({
557
+ addresses: Joi.string().invalid("display").required().messages({
558
+ "any.required": CHOOSE_ADDRESS_ERROR,
559
+ "string.empty": CHOOSE_ADDRESS_ERROR,
560
+ "any.invalid": CHOOSE_ADDRESS_ERROR
609
561
  })
610
562
  });
611
563
 
612
- // src/schemas/business/business-vat-remove-schema.js
613
- var businessVatRemoveSchema = Joi.object({
614
- confirmRemove: Joi.string().valid("yes", "no").required().messages({
615
- "any.required": "Select yes if you want to remove your VAT registration number",
616
- "any.only": "Select yes if you want to remove your VAT registration number"
564
+ // src/schemas/os-places/uk-postcode-schema.js
565
+ var ukPostcodeSchema = Joi.object({
566
+ postcode: Joi.string().trim().required().uppercase().max(POSTCODE_MAX).pattern(/^([A-Z]{1,2}\d[A-Z\d]? ?\d[A-Z]{2}|GIR ?0A{2})$/).messages({
567
+ "any.required": "Enter a postcode",
568
+ "string.empty": "Enter a postcode",
569
+ "string.max": `Postal code must be ${POSTCODE_MAX} characters or less`,
570
+ "string.pattern.base": "Enter a full UK postcode, like AA3 1AB"
617
571
  })
618
572
  });
619
573
 
620
- // src/schemas/business/business-schemas.js
621
- var businessSchemas = {
622
- sbi: businessSbiSchema,
623
- details: {
624
- name: businessNameSchema,
625
- address: addressSchema,
626
- phone: businessPhoneSchema,
627
- email: businessEmailSchema,
628
- vat: businessVatSchema
629
- },
630
- vat: {
631
- change: businessVatChangeSchema,
632
- remove: businessVatRemoveSchema
574
+ // src/schemas/os-places/os-places-schemas.js
575
+ var osPlacesSchemas = {
576
+ addressLookup: addressLookupSchema,
577
+ addresses: addressesSchema,
578
+ ukPostcode: ukPostcodeSchema
579
+ };
580
+
581
+ // src/schemas/schemas.js
582
+ var schemas = {
583
+ business: businessSchemas,
584
+ customer: customerSchemas,
585
+ personal: personalSchemas,
586
+ osPlaces: osPlacesSchemas
587
+ };
588
+
589
+ // src/constants/country-names.js
590
+ var COUNTRY_NAMES = {
591
+ E: "ENGLAND",
592
+ W: "WALES",
593
+ S: "SCOTLAND",
594
+ N: "NORTHERN IRELAND",
595
+ L: "CHANNEL ISLANDS",
596
+ M: "ISLE OF MAN"
597
+ };
598
+
599
+ // src/mappers/address-lookup-mapper.js
600
+ var addressLookupMapper = (addresses) => {
601
+ if (!Array.isArray(addresses)) {
602
+ return [];
603
+ }
604
+ return addresses.map((address) => {
605
+ const { error } = schemas.osPlaces.addressLookup.validate(address);
606
+ if (error) {
607
+ return null;
608
+ }
609
+ const {
610
+ UPRN,
611
+ ADDRESS,
612
+ PO_BOX_NUMBER,
613
+ ORGANISATION_NAME,
614
+ DEPARTMENT_NAME,
615
+ SUB_BUILDING_NAME,
616
+ BUILDING_NAME,
617
+ BUILDING_NUMBER,
618
+ DEPENDENT_THOROUGHFARE_NAME,
619
+ THOROUGHFARE_NAME,
620
+ DOUBLE_DEPENDENT_LOCALITY,
621
+ DEPENDENT_LOCALITY,
622
+ POST_TOWN,
623
+ POSTCODE,
624
+ LOCAL_CUSTODIAN_CODE_DESCRIPTION,
625
+ COUNTRY_CODE
626
+ } = address.properties;
627
+ const buildingName = PO_BOX_NUMBER ? `PO BOX ${PO_BOX_NUMBER}` : BUILDING_NAME || null;
628
+ return {
629
+ displayAddress: ADDRESS,
630
+ pafOrganisationName: filterAndJoin([ORGANISATION_NAME, DEPARTMENT_NAME]),
631
+ flatName: SUB_BUILDING_NAME ?? null,
632
+ buildingName,
633
+ buildingNumberRange: BUILDING_NUMBER ?? null,
634
+ street: filterAndJoin([DEPENDENT_THOROUGHFARE_NAME, THOROUGHFARE_NAME]),
635
+ dependentLocality: DEPENDENT_LOCALITY ?? null,
636
+ doubleDependentLocality: DOUBLE_DEPENDENT_LOCALITY ?? null,
637
+ city: POST_TOWN,
638
+ county: formatCounty(LOCAL_CUSTODIAN_CODE_DESCRIPTION, POST_TOWN),
639
+ postcode: POSTCODE,
640
+ country: COUNTRY_NAMES[COUNTRY_CODE] ?? null,
641
+ uprn: UPRN
642
+ };
643
+ }).filter(Boolean);
644
+ };
645
+ var formatCounty = (localCustodianCodeDescription, postTown) => {
646
+ if (localCustodianCodeDescription === "ORDNANCE SURVEY" || localCustodianCodeDescription === postTown) {
647
+ return null;
648
+ }
649
+ return localCustodianCodeDescription;
650
+ };
651
+ var filterAndJoin = (addressesProperties) => {
652
+ return addressesProperties.filter(Boolean).join(", ") || null;
653
+ };
654
+
655
+ // src/mappers/mappers.js
656
+ var mappers = {
657
+ personalBusinessDetails: mapPersonalBusinessDetails,
658
+ address: mapAddress,
659
+ customerName: mapCustomerName,
660
+ businessDetails: mapBusinessDetails,
661
+ addressLookup: addressLookupMapper
662
+ };
663
+
664
+ // src/mutations/business/update-business-email.js
665
+ var updateBusinessEmailMutation = `
666
+ mutation Mutation($input: UpdateBusinessEmailInput!) {
667
+ updateBusinessEmail(input: $input) {
668
+ business {
669
+ info {
670
+ email {
671
+ address
672
+ }
673
+ }
674
+ }
675
+ success
676
+ }
677
+ }
678
+ `;
679
+
680
+ // src/mutations/business/update-business-name.js
681
+ var updateBusinessNameMutation = `
682
+ mutation UpdateBusinessName($input: UpdateBusinessNameInput!) {
683
+ updateBusinessName(input: $input) {
684
+ business {
685
+ info {
686
+ name
687
+ }
688
+ }
689
+ success
690
+ }
691
+ }
692
+ `;
693
+
694
+ // src/mutations/personal/update-customer-name.js
695
+ var updateCustomerNameMutation = `
696
+ mutation UpdateCustomerName($input: UpdateCustomerNameInput!) {
697
+ updateCustomerName(input: $input) {
698
+ customer {
699
+ info {
700
+ name {
701
+ first
702
+ last
703
+ middle
704
+ }
705
+ }
706
+ }
707
+ }
708
+ }
709
+ `;
710
+
711
+ // src/mutations/personal/update-customer-dob.js
712
+ var updateCustomerDobMutation = `
713
+ mutation UpdateCustomerDateOfBirth($input: UpdateCustomerDateOfBirthInput!) {
714
+ updateCustomerDateOfBirth(input: $input) {
715
+ customer {
716
+ info {
717
+ dateOfBirth
718
+ }
719
+ }
720
+ }
721
+ }
722
+ `;
723
+
724
+ // src/mutations/personal/update-customer-phone.js
725
+ var updateCustomerPhoneMutation = `
726
+ mutation UpdateCustomerPhone($input: UpdateCustomerPhoneInput!) {
727
+ updateCustomerPhone(input: $input) {
728
+ customer {
729
+ info {
730
+ phone {
731
+ landline
732
+ mobile
733
+ }
734
+ }
735
+ }
736
+ }
633
737
  }
634
- };
738
+ `;
635
739
 
636
- // src/schemas/customer/customer-crn-schema.js
637
- var customerCrnSchema = Joi.object({
638
- crn: Joi.string().pattern(/^\d{10}$/).allow("").optional().messages({
639
- "string.pattern.base": "Enter the full CRN",
640
- "string.noControlChars": "CRN must not contain invalid characters"
641
- })
642
- });
740
+ // src/mutations/personal/update-customer-email.js
741
+ var updateCustomerEmailMutation = `
742
+ mutation UpdateCustomerEmail($input: UpdateCustomerEmailInput!) {
743
+ updateCustomerEmail(input: $input) {
744
+ customer {
745
+ info {
746
+ email {
747
+ address
748
+ }
749
+ }
750
+ }
751
+ }
752
+ }
753
+ `;
643
754
 
644
- // src/schemas/customer/customer-schemas.js
645
- var customerSchemas = {
646
- crn: customerCrnSchema
755
+ // src/mutations/mutations.js
756
+ var mutations = {
757
+ updateBusinessEmail: updateBusinessEmailMutation,
758
+ updateBusinessName: updateBusinessNameMutation,
759
+ updateCustomerName: updateCustomerNameMutation,
760
+ updateCustomerDob: updateCustomerDobMutation,
761
+ updateCustomerPhone: updateCustomerPhoneMutation,
762
+ updateCustomerEmail: updateCustomerEmailMutation
647
763
  };
648
764
 
649
- // src/schemas/personal/personal-name-schema.js
650
- var personalNameSchema = Joi.object({
651
- first: Joi.string().trim().required().max(FIRST_NAME_MAX).messages({
652
- "string.empty": "Enter first name",
653
- "string.max": `First name must be ${FIRST_NAME_MAX} characters or less`,
654
- "any.required": "Enter first name",
655
- "string.noControlChars": "First name must not contain invalid characters"
656
- }),
657
- last: Joi.string().trim().required().max(LAST_NAME_MAX).messages({
658
- "string.empty": "Enter last name",
659
- "string.max": `Last name must be ${LAST_NAME_MAX} characters or less`,
660
- "any.required": "Enter last name",
661
- "string.noControlChars": "Last name must not contain invalid characters"
662
- }),
663
- middle: Joi.string().trim().allow("").max(MIDDLE_NAMES_MAX).messages({
664
- "string.max": `Middle names must be ${MIDDLE_NAMES_MAX} characters or less`,
665
- "string.noControlChars": "Middle names must not contain invalid characters"
666
- })
667
- });
668
-
669
- // src/schemas/personal/personal-dob-schema.js
670
- var personalDobSchema = Joi.object({
671
- day: Joi.string().allow(""),
672
- month: Joi.string().allow(""),
673
- year: Joi.string().allow("")
674
- }).custom((value, helpers) => {
675
- const { day, month, year } = value;
676
- const missingFieldsError = checkMissingFields(day, month, year, helpers);
677
- if (missingFieldsError) {
678
- return missingFieldsError;
765
+ // src/presenters/base-presenter.js
766
+ var BACK_LINK_DISPLAY_MAX = 50;
767
+ var formatBackLink = (businessName) => {
768
+ if (businessName.length > BACK_LINK_DISPLAY_MAX) {
769
+ return `Back to ${businessName.slice(0, BACK_LINK_DISPLAY_MAX)}\u2026`;
679
770
  }
680
- if (year && year.length !== 4) {
681
- return makeError(helpers, "dob.yearLength", ["year"]);
771
+ return `Back to ${businessName}`;
772
+ };
773
+ var formatNumber = (payloadNumber, changedNumber, originalNumber) => {
774
+ if (payloadNumber !== void 0) {
775
+ return payloadNumber;
682
776
  }
683
- const monthValue = getMonthNumber(month, helpers);
684
- if (monthValue.isJoiError) {
685
- return monthValue;
777
+ if (changedNumber !== void 0) {
778
+ return changedNumber;
686
779
  }
687
- const fullDate = getFullDate(day, monthValue, year, helpers);
688
- if (fullDate.isJoiError) {
689
- return fullDate;
780
+ return originalNumber;
781
+ };
782
+ var formatDatePart = (changed, original) => {
783
+ return changed ?? (original == null ? void 0 : original.toString()) ?? "";
784
+ };
785
+ var formatDateInputValues = (payloadDob, changedDob, originalDob) => {
786
+ if (payloadDob) {
787
+ return {
788
+ day: payloadDob.day ?? "",
789
+ month: payloadDob.month ?? "",
790
+ year: payloadDob.year ?? ""
791
+ };
690
792
  }
691
- const today = /* @__PURE__ */ new Date();
692
- today.setUTCHours(0, 0, 0, 0);
693
- if (fullDate >= today) {
694
- return makeError(helpers, "dob.future", ["day", "month", "year"]);
793
+ return {
794
+ day: formatDatePart(changedDob == null ? void 0 : changedDob.day, originalDob == null ? void 0 : originalDob.day),
795
+ month: formatDatePart(changedDob == null ? void 0 : changedDob.month, originalDob == null ? void 0 : originalDob.month),
796
+ year: formatDatePart(changedDob == null ? void 0 : changedDob.year, originalDob == null ? void 0 : originalDob.year)
797
+ };
798
+ };
799
+ var sortErrorsBySectionOrder = (errors, orderedSectionsToFix, SECTION_FIELD_ORDER) => {
800
+ const sortedErrors = [];
801
+ for (const section of orderedSectionsToFix) {
802
+ const fieldsInSection = SECTION_FIELD_ORDER[section] || [];
803
+ for (const field of fieldsInSection) {
804
+ if (errors[field]) {
805
+ sortedErrors.push({
806
+ field,
807
+ ...errors[field]
808
+ });
809
+ }
810
+ }
695
811
  }
696
- const tooOldError = checkNotTooOld(fullDate, helpers);
697
- if (tooOldError) {
698
- return tooOldError;
812
+ return sortedErrors;
813
+ };
814
+
815
+ // src/presenters/address-presenter.js
816
+ var addressChangeLink = (postcodeLookup, context) => {
817
+ if (context === "business") {
818
+ if (postcodeLookup) {
819
+ return "/business-address-change";
820
+ }
821
+ return "/business-address-enter";
699
822
  }
700
- return value;
701
- }).messages({
702
- "dob.missingAll": "Enter your date of birth",
703
- "dob.missingDay": "Date of birth must include a day",
704
- "dob.missingMonth": "Date of birth must include a month",
705
- "dob.missingYear": "Date of birth must include a year",
706
- "dob.missingDayMonth": "Date of birth must include a day and month",
707
- "dob.missingDayYear": "Date of birth must include a day and year",
708
- "dob.missingMonthYear": "Date of birth must include a month and year",
709
- "dob.yearLength": "Enter a year with 4 numbers, like 1975",
710
- "dob.invalid": "Date of birth must be a real date",
711
- "dob.future": "Date of birth must be in the past",
712
- "dob.tooOld": "Date of birth must be on or after {{#oldest}}",
713
- "string.noControlChars": "Date of birth must not contain invalid characters"
714
- });
715
- var checkNotTooOld = (fullDate, helpers) => {
716
- const oldestDateAllowed = getOldestAllowedDate();
717
- if (fullDate < oldestDateAllowed) {
718
- const oldestDateString = oldestDateAllowed.toLocaleDateString("en-GB", {
719
- day: "numeric",
720
- month: "long",
721
- year: "numeric"
722
- });
723
- const error = helpers.error("dob.tooOld", { oldest: oldestDateString });
724
- error.path = ["day", "month", "year"];
725
- return error;
823
+ if (context === "personal") {
824
+ if (postcodeLookup) {
825
+ return "/account-address-change";
826
+ }
827
+ return "/account-address-enter";
726
828
  }
727
829
  return null;
728
830
  };
729
- var getOldestAllowedDate = () => {
730
- const date = /* @__PURE__ */ new Date();
731
- date.setUTCHours(0, 0, 0, 0);
732
- date.setUTCFullYear(date.getUTCFullYear() - MAX_AGE_YEARS);
733
- return date;
734
- };
735
- var getFullDate = (day, monthValue, year, helpers) => {
736
- const dayValue = Number.parseInt(day, 10);
737
- const yearValue = Number.parseInt(year, 10);
738
- const date = new Date(Date.UTC(yearValue, monthValue - 1, dayValue));
739
- if (date.getUTCFullYear() !== yearValue || date.getUTCMonth() + 1 !== monthValue || date.getUTCDate() !== dayValue) {
740
- return makeError(helpers, "dob.invalid", ["day", "month", "year"]);
831
+ var addressBackLink = (postcodeLookup, context) => {
832
+ if (context === "business") {
833
+ if (postcodeLookup) {
834
+ return { href: "/business-address-select" };
835
+ }
836
+ return { href: "/business-address-enter" };
741
837
  }
742
- return date;
743
- };
744
- var getMonthNumber = (month, helpers) => {
745
- if (Number.isNaN(Number(month))) {
746
- const lower = month.toLowerCase();
747
- const mapped = MONTH_MAP[lower];
748
- if (!mapped) {
749
- return makeError(helpers, "dob.invalid", ["month"]);
838
+ if (context === "personal") {
839
+ if (postcodeLookup) {
840
+ return { href: "/account-address-select" };
750
841
  }
751
- return mapped;
842
+ return { href: "/account-address-enter" };
752
843
  }
753
- const parsedMonth = Number.parseInt(month, 10);
754
- if (parsedMonth < 1 || parsedMonth > 12) {
755
- return makeError(helpers, "dob.invalid", ["month"]);
844
+ return null;
845
+ };
846
+ var buildAddressLine = (parts) => {
847
+ return parts.filter(Boolean).join(", ") || null;
848
+ };
849
+ var buildStreetLine = (buildingRange, street) => {
850
+ return [buildingRange, street].filter(Boolean).join(" ") || null;
851
+ };
852
+ var formatLookupAddress = (lookup, city, country, postcode) => ({
853
+ address1: buildAddressLine([lookup.pafOrganisationName, lookup.flatName, lookup.buildingName]),
854
+ address2: buildStreetLine(lookup.buildingNumberRange, lookup.street),
855
+ address3: buildAddressLine([lookup.doubleDependentLocality, lookup.dependentLocality]),
856
+ county: lookup.county ?? null,
857
+ city: city ?? null,
858
+ country: country ?? null,
859
+ postcode: postcode ?? null
860
+ });
861
+ var formatManualAddress = (manual, city, country, postcode) => ({
862
+ address1: manual.line1 ?? null,
863
+ address2: manual.line2 ?? null,
864
+ address3: manual.line3 ?? null,
865
+ city: city ?? null,
866
+ county: manual.line4 ?? null,
867
+ country: country ?? null,
868
+ postcode: postcode ?? null
869
+ });
870
+ var formatDisplayAddress = (address) => {
871
+ const { lookup, manual, postcode, country, city } = address;
872
+ let addressLines = [];
873
+ if (lookup.uprn) {
874
+ const buildingAndStreet = [
875
+ lookup.buildingNumberRange,
876
+ lookup.street
877
+ ].filter(Boolean).join(" ");
878
+ addressLines = [
879
+ lookup.pafOrganisationName,
880
+ lookup.flatName,
881
+ lookup.buildingName,
882
+ buildingAndStreet,
883
+ lookup.doubleDependentLocality,
884
+ lookup.dependentLocality,
885
+ city,
886
+ lookup.county
887
+ ];
888
+ } else {
889
+ addressLines = [
890
+ manual.line1,
891
+ manual.line2,
892
+ manual.line3,
893
+ city,
894
+ manual.line4,
895
+ // County
896
+ manual.line5
897
+ ];
756
898
  }
757
- return parsedMonth;
899
+ return [
900
+ ...addressLines.filter(Boolean),
901
+ postcode,
902
+ country
903
+ ];
758
904
  };
759
- var checkMissingFields = (day, month, year, helpers) => {
760
- if (!day && !month && !year) {
761
- return makeError(helpers, "dob.missingAll", ["day", "month", "year"]);
762
- }
763
- if (!day && month && year) {
764
- return makeError(helpers, "dob.missingDay", ["day"]);
765
- }
766
- if (day && !month && year) {
767
- return makeError(helpers, "dob.missingMonth", ["month"]);
768
- }
769
- if (day && month && !year) {
770
- return makeError(helpers, "dob.missingYear", ["year"]);
771
- }
772
- if (!day && !month && year) {
773
- return makeError(helpers, "dob.missingDayMonth", ["day", "month"]);
774
- }
775
- if (!day && month && !year) {
776
- return makeError(helpers, "dob.missingDayYear", ["day", "year"]);
777
- }
778
- if (day && !month && !year) {
779
- return makeError(helpers, "dob.missingMonthYear", ["month", "year"]);
905
+ var formatOriginalAddress = (originalAddress) => {
906
+ const { lookup, manual, city, country, postcode } = originalAddress;
907
+ return lookup.uprn ? formatLookupAddress(lookup, city, country, postcode) : formatManualAddress(manual, city, country, postcode);
908
+ };
909
+ var formatChangedAddress = (changeBusinessAddress) => {
910
+ if (!changeBusinessAddress.uprn) {
911
+ return changeBusinessAddress;
780
912
  }
781
- return null;
913
+ const {
914
+ pafOrganisationName,
915
+ flatName,
916
+ buildingName,
917
+ buildingNumberRange,
918
+ street,
919
+ doubleDependentLocality,
920
+ dependentLocality,
921
+ city,
922
+ county,
923
+ country,
924
+ postcode
925
+ } = changeBusinessAddress;
926
+ return {
927
+ address1: buildAddressLine([pafOrganisationName, flatName, buildingName]),
928
+ address2: buildStreetLine(buildingNumberRange, street),
929
+ address3: buildAddressLine([doubleDependentLocality, dependentLocality]),
930
+ city: city ?? null,
931
+ county: county ?? null,
932
+ country: country ?? null,
933
+ postcode: postcode ?? null
934
+ };
782
935
  };
783
- var makeError = (helpers, code, fields) => {
784
- const error = helpers.error(code);
785
- error.path = fields;
786
- error.isJoiError = true;
787
- return error;
936
+ var formatDisplayAddresses = (addresses, previouslyPickedAddress) => {
937
+ const displayAddresses = addresses.map((address) => ({
938
+ value: `${address.uprn}${address.displayAddress}`,
939
+ text: address.displayAddress,
940
+ selected: (previouslyPickedAddress == null ? void 0 : previouslyPickedAddress.uprn) === address.uprn && (previouslyPickedAddress == null ? void 0 : previouslyPickedAddress.displayAddress) === address.displayAddress
941
+ }));
942
+ const hasSelectedAddress = displayAddresses.some((addr) => addr.selected);
943
+ const text = addresses.length === 1 ? "1 address found" : `${addresses.length} addresses found`;
944
+ displayAddresses.unshift({
945
+ value: "display",
946
+ text,
947
+ selected: !hasSelectedAddress
948
+ });
949
+ return displayAddresses;
788
950
  };
789
951
 
790
- // src/schemas/personal/personal-email-schema.js
791
- var personalEmailSchema = Joi.object({
792
- personalEmail: Joi.string().required().max(EMAIL_MAX).email({
793
- minDomainSegments: 2,
794
- tlds: {
795
- allow: true,
796
- min: 2
797
- }
798
- }).messages({
799
- "string.max": `Email address must be ${EMAIL_MAX} characters or less`,
800
- "string.empty": "Enter a personal email address",
801
- "string.email": "Enter an email address, like name@example.com",
802
- "string.noControlChars": "Personal email address must not contain invalid characters"
803
- })
804
- });
805
-
806
- // src/schemas/personal/personal-phone-schema.js
807
- var personalPhoneSchema = Joi.object({
808
- personalTelephone: Joi.string().trim().empty("").min(PHONE_NUMBER_MIN).max(PHONE_NUMBER_MAX).pattern(PHONE_NUMBER_PATTERN).messages({
809
- "string.min": `Personal telephone number must be ${PHONE_NUMBER_MIN} characters or more`,
810
- "string.max": `Personal telephone number must be ${PHONE_NUMBER_MAX} characters or less`,
811
- "string.pattern.base": "Personal telephone number must only include numbers 0 to 9 and special characters such as spaces, brackets and +",
812
- "string.noControlChars": "Personal telephone number must not contain invalid characters"
813
- }),
814
- personalMobile: Joi.string().trim().empty("").min(PHONE_NUMBER_MIN).max(PHONE_NUMBER_MAX).pattern(PHONE_NUMBER_PATTERN).messages({
815
- "string.min": `Personal mobile phone number must be ${PHONE_NUMBER_MIN} characters or more`,
816
- "string.max": `Personal mobile phone number must be ${PHONE_NUMBER_MAX} characters or less`,
817
- "string.pattern.base": "Personal mobile phone number must only include numbers 0 to 9 and special characters such as spaces, brackets and +",
818
- "string.noControlChars": "Personal mobile phone number must not contain invalid characters"
819
- })
820
- }).or("personalTelephone", "personalMobile").messages({
821
- "object.missing": "Enter at least one phone number"
822
- });
823
-
824
- // src/schemas/personal/personal-schemas.js
825
- var personalSchemas = {
826
- name: personalNameSchema,
827
- dob: personalDobSchema,
828
- address: addressSchema,
829
- phone: personalPhoneSchema,
830
- email: personalEmailSchema
952
+ // src/presenters/business-details-presenter.js
953
+ var getActionText = (value) => {
954
+ return value ? "Change" : "Add";
831
955
  };
832
-
833
- // src/schemas/os-places/address-lookup-schema.js
834
- var addressLookupSchema = Joi.object({
835
- properties: Joi.object({
836
- UPRN: Joi.string().required(),
837
- ADDRESS: Joi.string().required(),
838
- ORGANISATION_NAME: Joi.string().allow(null),
839
- DEPARTMENT_NAME: Joi.string().allow(null),
840
- SUB_BUILDING_NAME: Joi.string().allow(null),
841
- BUILDING_NAME: Joi.string().allow(null),
842
- BUILDING_NUMBER: Joi.string().allow(null),
843
- DEPENDENT_THOROUGHFARE_NAME: Joi.string().allow(null),
844
- THOROUGHFARE_NAME: Joi.string().allow(null),
845
- DOUBLE_DEPENDENT_LOCALITY: Joi.string().allow(null),
846
- DEPENDENT_LOCALITY: Joi.string().allow(null),
847
- POST_TOWN: Joi.string().required(),
848
- POSTCODE: Joi.string().required(),
849
- LOCAL_CUSTODIAN_CODE_DESCRIPTION: Joi.string().allow(null),
850
- COUNTRY_CODE: Joi.string().required()
851
- }).unknown(true).required()
852
- }).unknown(true);
853
-
854
- // src/schemas/os-places/addresses-schema.js
855
- var CHOOSE_ADDRESS_ERROR = "Choose an address";
856
- var addressesSchema = Joi.object({
857
- addresses: Joi.string().invalid("display").required().messages({
858
- "any.required": CHOOSE_ADDRESS_ERROR,
859
- "string.empty": CHOOSE_ADDRESS_ERROR,
860
- "any.invalid": CHOOSE_ADDRESS_ERROR
861
- })
862
- });
863
-
864
- // src/schemas/os-places/uk-postcode-schema.js
865
- var ukPostcodeSchema = Joi.object({
866
- postcode: Joi.string().trim().required().uppercase().max(POSTCODE_MAX).pattern(/^([A-Z]{1,2}\d[A-Z\d]? ?\d[A-Z]{2}|GIR ?0A{2})$/).messages({
867
- "any.required": "Enter a postcode",
868
- "string.empty": "Enter a postcode",
869
- "string.max": `Postal code must be ${POSTCODE_MAX} characters or less`,
870
- "string.pattern.base": "Enter a full UK postcode, like AA3 1AB"
871
- })
872
- });
873
-
874
- // src/schemas/os-places/os-places-schemas.js
875
- var osPlacesSchemas = {
876
- addressLookup: addressLookupSchema,
877
- addresses: addressesSchema,
878
- ukPostcode: ukPostcodeSchema
956
+ var formatCph = (countyParishHoldings) => {
957
+ return (countyParishHoldings || []).filter((cph) => cph == null ? void 0 : cph.cphNumber).map((cph) => cph.cphNumber);
958
+ };
959
+ var formatCphText = (count) => {
960
+ return `County Parish Holding (CPH) number${count !== 1 ? "s" : ""}`;
961
+ };
962
+ var formatBusinessAddress = (businessAddress) => {
963
+ var _a, _b;
964
+ if (((_a = businessAddress == null ? void 0 : businessAddress.lookup) == null ? void 0 : _a.uprn) || ((_b = businessAddress == null ? void 0 : businessAddress.manual) == null ? void 0 : _b.line1)) {
965
+ return formatDisplayAddress(businessAddress);
966
+ }
967
+ return [];
879
968
  };
880
969
 
881
- // src/schemas/schemas.js
882
- var schemas = {
883
- business: businessSchemas,
884
- customer: customerSchemas,
885
- personal: personalSchemas,
886
- osPlaces: osPlacesSchemas
970
+ // src/presenters/presenters.js
971
+ var presenters = {
972
+ formatBackLink,
973
+ formatNumber,
974
+ formatDateInputValues,
975
+ formatDisplayAddress,
976
+ formatOriginalAddress,
977
+ formatChangedAddress,
978
+ formatDisplayAddresses,
979
+ sortErrorsBySectionOrder,
980
+ getActionText,
981
+ formatCph,
982
+ formatCphText,
983
+ formatBusinessAddress,
984
+ addressBackLink,
985
+ addressChangeLink
887
986
  };
888
987
 
889
988
  // src/utils/format-full-name.js
@@ -938,11 +1037,259 @@ var utils = {
938
1037
  buildUpdateBusinessEmailVariables,
939
1038
  buildUpdateBusinessNameVariables
940
1039
  };
1040
+
1041
+ // src/services/os-places/address-lookup-service.js
1042
+ import { placesAPI } from "osdatahub";
1043
+
1044
+ // src/mock-data/mock-os-places-addresses.js
1045
+ var mockAddresses = [
1046
+ {
1047
+ properties: {
1048
+ UPRN: "10000001",
1049
+ ADDRESS: "APARTMENT 1, REDBRIDGE HOUSE, 1 ROSE COURT, LONDON, SW1A 1AA",
1050
+ SUB_BUILDING_NAME: "APARTMENT 1",
1051
+ BUILDING_NAME: "REDBRIDGE HOUSE",
1052
+ BUILDING_NUMBER: "1",
1053
+ THOROUGHFARE_NAME: "ROSE COURT",
1054
+ POST_TOWN: "LONDON",
1055
+ POSTCODE: "SW1A 1AA",
1056
+ LOCAL_CUSTODIAN_CODE_DESCRIPTION: "GREATER LONDON",
1057
+ COUNTRY_CODE: "E"
1058
+ }
1059
+ },
1060
+ {
1061
+ properties: {
1062
+ UPRN: "10000002",
1063
+ ADDRESS: "FLAT 2B, KINGSLEY APARTMENTS, 12 VICTORIA SQUARE, LONDON, SW1A 1AA",
1064
+ SUB_BUILDING_NAME: "FLAT 2B",
1065
+ BUILDING_NAME: "KINGSLEY APARTMENTS",
1066
+ BUILDING_NUMBER: "12",
1067
+ THOROUGHFARE_NAME: "VICTORIA SQUARE",
1068
+ DEPENDENT_LOCALITY: "",
1069
+ POST_TOWN: "LONDON",
1070
+ POSTCODE: "SW1A 1AA",
1071
+ LOCAL_CUSTODIAN_CODE_DESCRIPTION: "GREATER LONDON",
1072
+ COUNTRY_CODE: "E"
1073
+ }
1074
+ },
1075
+ {
1076
+ properties: {
1077
+ UPRN: "10000003",
1078
+ ADDRESS: "THE GROUND FLOOR, THE OLD GRANARY, 5 CHURCH LANE, LONDON, SW1A 1AA",
1079
+ ORGANISATION_NAME: "OLD GRANARY STUDIOS",
1080
+ SUB_BUILDING_NAME: "GROUND FLOOR",
1081
+ BUILDING_NAME: "THE OLD GRANARY",
1082
+ BUILDING_NUMBER: "5",
1083
+ THOROUGHFARE_NAME: "CHURCH LANE",
1084
+ POST_TOWN: "LONDON",
1085
+ POSTCODE: "SW1A 1AA",
1086
+ LOCAL_CUSTODIAN_CODE_DESCRIPTION: "GREATER LONDON",
1087
+ COUNTRY_CODE: "E"
1088
+ }
1089
+ },
1090
+ {
1091
+ properties: {
1092
+ UPRN: "10000004",
1093
+ ADDRESS: "SUITE 3, WELLINGTON HOUSE, 20 QUEEN STREET, LONDON, SW1A 1AA",
1094
+ ORGANISATION_NAME: "WELLINGTON CONSULTING LTD",
1095
+ DEPARTMENT_NAME: "CLIENT SERVICES",
1096
+ SUB_BUILDING_NAME: "SUITE 3",
1097
+ BUILDING_NAME: "WELLINGTON HOUSE",
1098
+ BUILDING_NUMBER: "20",
1099
+ THOROUGHFARE_NAME: "QUEEN STREET",
1100
+ POST_TOWN: "LONDON",
1101
+ POSTCODE: "SW1A 1AA",
1102
+ LOCAL_CUSTODIAN_CODE_DESCRIPTION: "GREATER LONDON",
1103
+ COUNTRY_CODE: "E"
1104
+ }
1105
+ },
1106
+ {
1107
+ properties: {
1108
+ UPRN: "10000005",
1109
+ ADDRESS: "UNIT 6, MARKET ROW, 3 MARKET YARD, LONDON, SW1A 1AA",
1110
+ ORGANISATION_NAME: "MARKET ROW TRADERS",
1111
+ SUB_BUILDING_NAME: "UNIT 6",
1112
+ BUILDING_NAME: "MARKET ROW",
1113
+ BUILDING_NUMBER: "3",
1114
+ THOROUGHFARE_NAME: "MARKET YARD",
1115
+ POST_TOWN: "LONDON",
1116
+ POSTCODE: "SW1A 1AA",
1117
+ LOCAL_CUSTODIAN_CODE_DESCRIPTION: "GREATER LONDON",
1118
+ COUNTRY_CODE: "E"
1119
+ }
1120
+ },
1121
+ {
1122
+ properties: {
1123
+ UPRN: "20000001",
1124
+ ADDRESS: "1 ORCHARD COTTAGES, WESTFIELD LANE, SHEPTON MALLET, BS14 8XX",
1125
+ BUILDING_NAME: "ORCHARD COTTAGES",
1126
+ BUILDING_NUMBER: "1",
1127
+ THOROUGHFARE_NAME: "WESTFIELD LANE",
1128
+ POST_TOWN: "SHEPTON MALLET",
1129
+ POSTCODE: "BS14 8XX",
1130
+ LOCAL_CUSTODIAN_CODE_DESCRIPTION: "SOMERSET",
1131
+ COUNTRY_CODE: "E"
1132
+ }
1133
+ },
1134
+ {
1135
+ properties: {
1136
+ UPRN: "20000002",
1137
+ ADDRESS: "THE STABLES, MANOR FARM, HOLLOW ROAD, SHEPTON MALLET, BS14 8XX",
1138
+ ORGANISATION_NAME: "MANOR FARM",
1139
+ SUB_BUILDING_NAME: "THE STABLES",
1140
+ BUILDING_NAME: "MANOR FARM",
1141
+ THOROUGHFARE_NAME: "HOLLOW ROAD",
1142
+ POST_TOWN: "SHEPTON MALLET",
1143
+ POSTCODE: "BS14 8XX",
1144
+ LOCAL_CUSTODIAN_CODE_DESCRIPTION: "SOMERSET",
1145
+ COUNTRY_CODE: "E"
1146
+ }
1147
+ },
1148
+ {
1149
+ properties: {
1150
+ UPRN: "30000001",
1151
+ ADDRESS: "FLAT 4, BISHOP COURT, 9 FLEET STREET, LONDON, EC1A 1BB",
1152
+ SUB_BUILDING_NAME: "FLAT 4",
1153
+ BUILDING_NAME: "BISHOP COURT",
1154
+ BUILDING_NUMBER: "9",
1155
+ THOROUGHFARE_NAME: "FLEET STREET",
1156
+ POST_TOWN: "LONDON",
1157
+ POSTCODE: "EC1A 1BB",
1158
+ LOCAL_CUSTODIAN_CODE_DESCRIPTION: "CITY OF LONDON",
1159
+ COUNTRY_CODE: "E"
1160
+ }
1161
+ },
1162
+ {
1163
+ properties: {
1164
+ UPRN: "40000001",
1165
+ ADDRESS: "2A PICCADILLY ARCADE, MANCHESTER, M1 1AE",
1166
+ ORGANISATION_NAME: "PICCADILLY ARCADE LTD",
1167
+ SUB_BUILDING_NAME: "2A",
1168
+ BUILDING_NAME: "PICCADILLY ARCADE",
1169
+ BUILDING_NUMBER: "2A",
1170
+ THOROUGHFARE_NAME: "PICCADILLY",
1171
+ POST_TOWN: "MANCHESTER",
1172
+ POSTCODE: "M1 1AE",
1173
+ LOCAL_CUSTODIAN_CODE_DESCRIPTION: "GREATER MANCHESTER",
1174
+ COUNTRY_CODE: "E"
1175
+ }
1176
+ },
1177
+ {
1178
+ properties: {
1179
+ UPRN: "50000001",
1180
+ ADDRESS: "3 WESTBOURNE TERRACE, LONDON, W1A 0AX",
1181
+ BUILDING_NAME: "WESTBOURNE TERRACE",
1182
+ BUILDING_NUMBER: "3",
1183
+ THOROUGHFARE_NAME: "WESTBOURNE TERRACE",
1184
+ POST_TOWN: "LONDON",
1185
+ POSTCODE: "W1A 0AX",
1186
+ LOCAL_CUSTODIAN_CODE_DESCRIPTION: "WESTMINSTER",
1187
+ COUNTRY_CODE: "E"
1188
+ }
1189
+ }
1190
+ ];
1191
+
1192
+ // src/services/os-places/os-places-stub.js
1193
+ var mockPostcode = (postcode) => {
1194
+ const formattedPostcode = postcode == null ? void 0 : postcode.toUpperCase().replaceAll(" ", "");
1195
+ const matchingAddresses = mockAddresses.filter((address) => {
1196
+ var _a;
1197
+ const addressPostcode = (_a = address.properties.POSTCODE) == null ? void 0 : _a.toUpperCase().replaceAll(" ", "");
1198
+ return addressPostcode === formattedPostcode;
1199
+ });
1200
+ return {
1201
+ features: matchingAddresses
1202
+ };
1203
+ };
1204
+
1205
+ // src/services/os-places/address-lookup-service.js
1206
+ var addressLookupService = async (postcode, osPlacesConfig) => {
1207
+ const addresses = await fetchAddressesFromPostcodeLookup(postcode, osPlacesConfig);
1208
+ if (addresses.error) {
1209
+ return addresses;
1210
+ }
1211
+ if (!(addresses == null ? void 0 : addresses.length)) {
1212
+ return {
1213
+ error: [
1214
+ {
1215
+ message: "No addresses found for this postcode",
1216
+ path: ["postcode"]
1217
+ }
1218
+ ]
1219
+ };
1220
+ }
1221
+ const mappedAddresses = addressLookupMapper(addresses);
1222
+ return mappedAddresses;
1223
+ };
1224
+ var fetchAddressesFromPostcodeLookup = async (postcode, osPlacesConfig) => {
1225
+ const MAX_RETRIES = 3;
1226
+ const INITIAL_BACKOFF_MS = 100;
1227
+ for (let attemptNumber = 1; attemptNumber <= MAX_RETRIES; attemptNumber++) {
1228
+ try {
1229
+ const { clientId, osPlacesStub } = osPlacesConfig;
1230
+ if (osPlacesStub) {
1231
+ const response2 = mockPostcode(postcode);
1232
+ return response2.features ?? [];
1233
+ }
1234
+ const response = await fetchFromPlacesAPI(clientId, postcode);
1235
+ return response.features ?? [];
1236
+ } catch (error) {
1237
+ const shouldRetry = isRetryable(error) && attemptNumber < MAX_RETRIES;
1238
+ if (!shouldRetry) {
1239
+ return buildErrorResponse(error.message);
1240
+ }
1241
+ const backoffMs = calculateExponentialBackoff(attemptNumber, INITIAL_BACKOFF_MS);
1242
+ await delayBeforeRetry(backoffMs);
1243
+ }
1244
+ }
1245
+ };
1246
+ var calculateExponentialBackoff = (attemptNumber, baseMs) => {
1247
+ const exponent = attemptNumber - 1;
1248
+ const powerOf2 = Math.pow(2, exponent);
1249
+ const backoffDuration = baseMs * powerOf2;
1250
+ return backoffDuration;
1251
+ };
1252
+ function delayBeforeRetry(ms) {
1253
+ const callback = (resolve) => {
1254
+ setTimeout(resolve, ms);
1255
+ };
1256
+ return new Promise(callback);
1257
+ }
1258
+ var buildErrorResponse = (message) => {
1259
+ return {
1260
+ error: [
1261
+ {
1262
+ message: message || "Failed to fetch addresses",
1263
+ path: ["postcode"]
1264
+ }
1265
+ ]
1266
+ };
1267
+ };
1268
+ var fetchFromPlacesAPI = async (clientId, postcode) => {
1269
+ const response = await placesAPI.postcode(clientId, postcode, { limit: 150 });
1270
+ return response;
1271
+ };
1272
+ var isRetryable = (error) => {
1273
+ const networkErrors = ["ECONNRESET", "ECONNREFUSED", "ETIMEDOUT"];
1274
+ if (networkErrors.includes(error.code)) {
1275
+ return true;
1276
+ }
1277
+ if (error.status && error.status >= INTERNAL_SERVER_ERROR) {
1278
+ return true;
1279
+ }
1280
+ return false;
1281
+ };
1282
+
1283
+ // src/services/services.js
1284
+ var services = {
1285
+ addressLookup: addressLookupService
1286
+ };
941
1287
  export {
942
1288
  constants,
943
1289
  mappers,
944
1290
  mutations,
945
1291
  presenters,
946
1292
  schemas,
1293
+ services,
947
1294
  utils
948
1295
  };