@defra/fcp-sfd-frontend-engine 0.14.0 → 0.16.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 CHANGED
@@ -98,6 +98,7 @@ var MONTH_MAP = {
98
98
 
99
99
  // src/constants/success-messages.js
100
100
  var BUSINESS_EMAIL_ADDRESS = "You have updated your business email address";
101
+ var BUSINESS_NAME = "You have updated your business name";
101
102
 
102
103
  // src/constants/index.js
103
104
  var constants = {
@@ -133,7 +134,8 @@ var constants = {
133
134
  },
134
135
  monthMap: MONTH_MAP,
135
136
  successMessages: {
136
- BUSINESS_EMAIL_ADDRESS
137
+ BUSINESS_EMAIL_ADDRESS,
138
+ BUSINESS_NAME
137
139
  }
138
140
  };
139
141
 
@@ -246,6 +248,20 @@ var updateBusinessEmailMutation = `
246
248
  }
247
249
  `;
248
250
 
251
+ // src/mutations/business/update-business-name.js
252
+ var updateBusinessNameMutation = `
253
+ mutation UpdateBusinessName($input: UpdateBusinessNameInput!) {
254
+ updateBusinessName(input: $input) {
255
+ business {
256
+ info {
257
+ name
258
+ }
259
+ }
260
+ success
261
+ }
262
+ }
263
+ `;
264
+
249
265
  // src/mutations/personal/update-customer-name.js
250
266
  var updateCustomerNameMutation = `
251
267
  mutation UpdateCustomerName($input: UpdateCustomerNameInput!) {
@@ -297,6 +313,7 @@ var updateCustomerPhoneMutation = `
297
313
  // src/mutations/mutations.js
298
314
  var mutations = {
299
315
  updateBusinessEmail: updateBusinessEmailMutation,
316
+ updateBusinessName: updateBusinessNameMutation,
300
317
  updateCustomerName: updateCustomerNameMutation,
301
318
  updateCustomerEmail: updateCustomerEmailMutation,
302
319
  updateCustomerPhone: updateCustomerPhoneMutation
@@ -319,6 +336,61 @@ var formatNumber = (payloadNumber, changedNumber, originalNumber) => {
319
336
  }
320
337
  return originalNumber;
321
338
  };
339
+ var sortErrorsBySectionOrder = (errors, orderedSectionsToFix, SECTION_FIELD_ORDER) => {
340
+ const sortedErrors = [];
341
+ for (const section of orderedSectionsToFix) {
342
+ const fieldsInSection = SECTION_FIELD_ORDER[section] || [];
343
+ for (const field of fieldsInSection) {
344
+ if (errors[field]) {
345
+ sortedErrors.push({
346
+ field,
347
+ ...errors[field]
348
+ });
349
+ }
350
+ }
351
+ }
352
+ return sortedErrors;
353
+ };
354
+
355
+ // src/presenters/address-presenter.js
356
+ var addressChangeLink = (postcodeLookup) => {
357
+ if (postcodeLookup) {
358
+ return "/account-address-change";
359
+ } else {
360
+ return "/account-address-enter";
361
+ }
362
+ };
363
+ var addressBackLink = (postcodeLookup) => {
364
+ if (postcodeLookup) {
365
+ return { href: "/account-address-select" };
366
+ } else {
367
+ return { href: "/account-address-enter" };
368
+ }
369
+ };
370
+ var buildAddressLine = (parts) => {
371
+ return parts.filter(Boolean).join(", ") || null;
372
+ };
373
+ var buildStreetLine = (buildingRange, street) => {
374
+ return [buildingRange, street].filter(Boolean).join(" ") || null;
375
+ };
376
+ var formatLookupAddress = (lookup, city, country, postcode) => ({
377
+ address1: buildAddressLine([lookup.pafOrganisationName, lookup.flatName, lookup.buildingName]),
378
+ address2: buildStreetLine(lookup.buildingNumberRange, lookup.street),
379
+ address3: buildAddressLine([lookup.doubleDependentLocality, lookup.dependentLocality]),
380
+ county: lookup.county ?? null,
381
+ city: city ?? null,
382
+ country: country ?? null,
383
+ postcode: postcode ?? null
384
+ });
385
+ var formatManualAddress = (manual, city, country, postcode) => ({
386
+ address1: manual.line1 ?? null,
387
+ address2: manual.line2 ?? null,
388
+ address3: manual.line3 ?? null,
389
+ city: city ?? null,
390
+ county: manual.line4 ?? null,
391
+ country: country ?? null,
392
+ postcode: postcode ?? null
393
+ });
322
394
  var formatDisplayAddress = (address) => {
323
395
  const { lookup, manual, postcode, country, city } = address;
324
396
  let addressLines = [];
@@ -354,30 +426,6 @@ var formatDisplayAddress = (address) => {
354
426
  country
355
427
  ];
356
428
  };
357
- var buildAddressLine = (parts) => {
358
- return parts.filter(Boolean).join(", ") || null;
359
- };
360
- var buildStreetLine = (buildingRange, street) => {
361
- return [buildingRange, street].filter(Boolean).join(" ") || null;
362
- };
363
- var formatLookupAddress = (lookup, city, country, postcode) => ({
364
- address1: buildAddressLine([lookup.pafOrganisationName, lookup.flatName, lookup.buildingName]),
365
- address2: buildStreetLine(lookup.buildingNumberRange, lookup.street),
366
- address3: buildAddressLine([lookup.doubleDependentLocality, lookup.dependentLocality]),
367
- county: lookup.county ?? null,
368
- city: city ?? null,
369
- country: country ?? null,
370
- postcode: postcode ?? null
371
- });
372
- var formatManualAddress = (manual, city, country, postcode) => ({
373
- address1: manual.line1 ?? null,
374
- address2: manual.line2 ?? null,
375
- address3: manual.line3 ?? null,
376
- city: city ?? null,
377
- county: manual.line4 ?? null,
378
- country: country ?? null,
379
- postcode: postcode ?? null
380
- });
381
429
  var formatOriginalAddress = (originalAddress) => {
382
430
  const { lookup, manual, city, country, postcode } = originalAddress;
383
431
  return lookup.uprn ? formatLookupAddress(lookup, city, country, postcode) : formatManualAddress(manual, city, country, postcode);
@@ -424,21 +472,6 @@ var formatDisplayAddresses = (addresses, previouslyPickedAddress) => {
424
472
  });
425
473
  return displayAddresses;
426
474
  };
427
- var sortErrorsBySectionOrder = (errors, orderedSectionsToFix, SECTION_FIELD_ORDER) => {
428
- const sortedErrors = [];
429
- for (const section of orderedSectionsToFix) {
430
- const fieldsInSection = SECTION_FIELD_ORDER[section] || [];
431
- for (const field of fieldsInSection) {
432
- if (errors[field]) {
433
- sortedErrors.push({
434
- field,
435
- ...errors[field]
436
- });
437
- }
438
- }
439
- }
440
- return sortedErrors;
441
- };
442
475
 
443
476
  // src/presenters/business-details-presenter.js
444
477
  var getActionText = (value) => {
@@ -470,7 +503,9 @@ var presenters = {
470
503
  getActionText,
471
504
  formatCph,
472
505
  formatCphText,
473
- formatBusinessAddress
506
+ formatBusinessAddress,
507
+ addressBackLink,
508
+ addressChangeLink
474
509
  };
475
510
 
476
511
  // src/utils/joi.js
@@ -915,11 +950,17 @@ var buildUpdateBusinessEmailVariables = (email, sbi) => {
915
950
  return { input: { email: { address: email }, sbi } };
916
951
  };
917
952
 
953
+ // src/utils/build-update-business-name-variables.js
954
+ var buildUpdateBusinessNameVariables = (name, sbi) => {
955
+ return { input: { name, sbi } };
956
+ };
957
+
918
958
  // src/utils/utils.js
919
959
  var utils = {
920
960
  formatFullName,
921
961
  formatValidationErrors,
922
- buildUpdateBusinessEmailVariables
962
+ buildUpdateBusinessEmailVariables,
963
+ buildUpdateBusinessNameVariables
923
964
  };
924
965
  // Annotate the CommonJS export names for ESM import in node:
925
966
  0 && (module.exports = {
package/dist/index.js CHANGED
@@ -58,6 +58,7 @@ var MONTH_MAP = {
58
58
 
59
59
  // src/constants/success-messages.js
60
60
  var BUSINESS_EMAIL_ADDRESS = "You have updated your business email address";
61
+ var BUSINESS_NAME = "You have updated your business name";
61
62
 
62
63
  // src/constants/index.js
63
64
  var constants = {
@@ -93,7 +94,8 @@ var constants = {
93
94
  },
94
95
  monthMap: MONTH_MAP,
95
96
  successMessages: {
96
- BUSINESS_EMAIL_ADDRESS
97
+ BUSINESS_EMAIL_ADDRESS,
98
+ BUSINESS_NAME
97
99
  }
98
100
  };
99
101
 
@@ -206,6 +208,20 @@ var updateBusinessEmailMutation = `
206
208
  }
207
209
  `;
208
210
 
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
+ `;
224
+
209
225
  // src/mutations/personal/update-customer-name.js
210
226
  var updateCustomerNameMutation = `
211
227
  mutation UpdateCustomerName($input: UpdateCustomerNameInput!) {
@@ -257,6 +273,7 @@ var updateCustomerPhoneMutation = `
257
273
  // src/mutations/mutations.js
258
274
  var mutations = {
259
275
  updateBusinessEmail: updateBusinessEmailMutation,
276
+ updateBusinessName: updateBusinessNameMutation,
260
277
  updateCustomerName: updateCustomerNameMutation,
261
278
  updateCustomerEmail: updateCustomerEmailMutation,
262
279
  updateCustomerPhone: updateCustomerPhoneMutation
@@ -279,6 +296,61 @@ var formatNumber = (payloadNumber, changedNumber, originalNumber) => {
279
296
  }
280
297
  return originalNumber;
281
298
  };
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
+ }
311
+ }
312
+ return sortedErrors;
313
+ };
314
+
315
+ // src/presenters/address-presenter.js
316
+ var addressChangeLink = (postcodeLookup) => {
317
+ if (postcodeLookup) {
318
+ return "/account-address-change";
319
+ } else {
320
+ return "/account-address-enter";
321
+ }
322
+ };
323
+ var addressBackLink = (postcodeLookup) => {
324
+ if (postcodeLookup) {
325
+ return { href: "/account-address-select" };
326
+ } else {
327
+ return { href: "/account-address-enter" };
328
+ }
329
+ };
330
+ var buildAddressLine = (parts) => {
331
+ return parts.filter(Boolean).join(", ") || null;
332
+ };
333
+ var buildStreetLine = (buildingRange, street) => {
334
+ return [buildingRange, street].filter(Boolean).join(" ") || null;
335
+ };
336
+ var formatLookupAddress = (lookup, city, country, postcode) => ({
337
+ address1: buildAddressLine([lookup.pafOrganisationName, lookup.flatName, lookup.buildingName]),
338
+ address2: buildStreetLine(lookup.buildingNumberRange, lookup.street),
339
+ address3: buildAddressLine([lookup.doubleDependentLocality, lookup.dependentLocality]),
340
+ county: lookup.county ?? null,
341
+ city: city ?? null,
342
+ country: country ?? null,
343
+ postcode: postcode ?? null
344
+ });
345
+ var formatManualAddress = (manual, city, country, postcode) => ({
346
+ address1: manual.line1 ?? null,
347
+ address2: manual.line2 ?? null,
348
+ address3: manual.line3 ?? null,
349
+ city: city ?? null,
350
+ county: manual.line4 ?? null,
351
+ country: country ?? null,
352
+ postcode: postcode ?? null
353
+ });
282
354
  var formatDisplayAddress = (address) => {
283
355
  const { lookup, manual, postcode, country, city } = address;
284
356
  let addressLines = [];
@@ -314,30 +386,6 @@ var formatDisplayAddress = (address) => {
314
386
  country
315
387
  ];
316
388
  };
317
- var buildAddressLine = (parts) => {
318
- return parts.filter(Boolean).join(", ") || null;
319
- };
320
- var buildStreetLine = (buildingRange, street) => {
321
- return [buildingRange, street].filter(Boolean).join(" ") || null;
322
- };
323
- var formatLookupAddress = (lookup, city, country, postcode) => ({
324
- address1: buildAddressLine([lookup.pafOrganisationName, lookup.flatName, lookup.buildingName]),
325
- address2: buildStreetLine(lookup.buildingNumberRange, lookup.street),
326
- address3: buildAddressLine([lookup.doubleDependentLocality, lookup.dependentLocality]),
327
- county: lookup.county ?? null,
328
- city: city ?? null,
329
- country: country ?? null,
330
- postcode: postcode ?? null
331
- });
332
- var formatManualAddress = (manual, city, country, postcode) => ({
333
- address1: manual.line1 ?? null,
334
- address2: manual.line2 ?? null,
335
- address3: manual.line3 ?? null,
336
- city: city ?? null,
337
- county: manual.line4 ?? null,
338
- country: country ?? null,
339
- postcode: postcode ?? null
340
- });
341
389
  var formatOriginalAddress = (originalAddress) => {
342
390
  const { lookup, manual, city, country, postcode } = originalAddress;
343
391
  return lookup.uprn ? formatLookupAddress(lookup, city, country, postcode) : formatManualAddress(manual, city, country, postcode);
@@ -384,21 +432,6 @@ var formatDisplayAddresses = (addresses, previouslyPickedAddress) => {
384
432
  });
385
433
  return displayAddresses;
386
434
  };
387
- var sortErrorsBySectionOrder = (errors, orderedSectionsToFix, SECTION_FIELD_ORDER) => {
388
- const sortedErrors = [];
389
- for (const section of orderedSectionsToFix) {
390
- const fieldsInSection = SECTION_FIELD_ORDER[section] || [];
391
- for (const field of fieldsInSection) {
392
- if (errors[field]) {
393
- sortedErrors.push({
394
- field,
395
- ...errors[field]
396
- });
397
- }
398
- }
399
- }
400
- return sortedErrors;
401
- };
402
435
 
403
436
  // src/presenters/business-details-presenter.js
404
437
  var getActionText = (value) => {
@@ -430,7 +463,9 @@ var presenters = {
430
463
  getActionText,
431
464
  formatCph,
432
465
  formatCphText,
433
- formatBusinessAddress
466
+ formatBusinessAddress,
467
+ addressBackLink,
468
+ addressChangeLink
434
469
  };
435
470
 
436
471
  // src/utils/joi.js
@@ -875,11 +910,17 @@ var buildUpdateBusinessEmailVariables = (email, sbi) => {
875
910
  return { input: { email: { address: email }, sbi } };
876
911
  };
877
912
 
913
+ // src/utils/build-update-business-name-variables.js
914
+ var buildUpdateBusinessNameVariables = (name, sbi) => {
915
+ return { input: { name, sbi } };
916
+ };
917
+
878
918
  // src/utils/utils.js
879
919
  var utils = {
880
920
  formatFullName,
881
921
  formatValidationErrors,
882
- buildUpdateBusinessEmailVariables
922
+ buildUpdateBusinessEmailVariables,
923
+ buildUpdateBusinessNameVariables
883
924
  };
884
925
  export {
885
926
  constants,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defra/fcp-sfd-frontend-engine",
3
- "version": "0.14.0",
3
+ "version": "0.16.0",
4
4
  "description": "Shared frontend engine used by both the internal and external frontend applications.",
5
5
  "main": "./dist/index.cjs",
6
6
  "exports": {
@@ -46,6 +46,7 @@
46
46
  "vitest": "4.1.10"
47
47
  },
48
48
  "dependencies": {
49
- "joi": "18.2.3"
49
+ "joi": "18.2.3",
50
+ "osdatahub": "0.2.2"
50
51
  }
51
52
  }
@@ -33,7 +33,7 @@ import { PHONE_NUMBER_PATTERN, NO_CONTROL_CHARS_PATTERN } from './patterns.js'
33
33
  import { MONTH_MAP } from './month-map.js'
34
34
 
35
35
  // Success messages
36
- import { BUSINESS_EMAIL_ADDRESS } from './success-messages.js'
36
+ import { BUSINESS_EMAIL_ADDRESS, BUSINESS_NAME } from './success-messages.js'
37
37
 
38
38
  export const constants = {
39
39
  statusCodes: {
@@ -68,6 +68,7 @@ export const constants = {
68
68
  },
69
69
  monthMap: MONTH_MAP,
70
70
  successMessages: {
71
- BUSINESS_EMAIL_ADDRESS
71
+ BUSINESS_EMAIL_ADDRESS,
72
+ BUSINESS_NAME
72
73
  }
73
74
  }
@@ -1 +1,2 @@
1
1
  export const BUSINESS_EMAIL_ADDRESS = 'You have updated your business email address'
2
+ export const BUSINESS_NAME = 'You have updated your business name'
@@ -0,0 +1,12 @@
1
+ export const updateBusinessNameMutation = `
2
+ mutation UpdateBusinessName($input: UpdateBusinessNameInput!) {
3
+ updateBusinessName(input: $input) {
4
+ business {
5
+ info {
6
+ name
7
+ }
8
+ }
9
+ success
10
+ }
11
+ }
12
+ `
@@ -1,10 +1,12 @@
1
1
  import { updateBusinessEmailMutation } from './business/update-business-email.js'
2
+ import { updateBusinessNameMutation } from './business/update-business-name.js'
2
3
  import { updateCustomerNameMutation } from './personal/update-customer-name.js'
3
4
  import { updateCustomerEmailMutation } from './personal/update-customer-email.js'
4
5
  import { updateCustomerPhoneMutation } from './personal/update-customer-phone.js'
5
6
 
6
7
  export const mutations = {
7
8
  updateBusinessEmail: updateBusinessEmailMutation,
9
+ updateBusinessName: updateBusinessNameMutation,
8
10
  updateCustomerName: updateCustomerNameMutation,
9
11
  updateCustomerEmail: updateCustomerEmailMutation,
10
12
  updateCustomerPhone: updateCustomerPhoneMutation
@@ -0,0 +1,254 @@
1
+ /**
2
+ * Determines the correct link for changing an address.
3
+ *
4
+ * If the user previously selected an address from a postcode lookup, they will
5
+ * navigate to the address change page to choose a different address.
6
+ * Otherwise, they navigate to the manual address entry page.
7
+ *
8
+ * @param {boolean} postcodeLookup - Whether the user used a postcode lookup to find their address
9
+ * @returns {string} The URL path for the address change page
10
+ */
11
+ export const addressChangeLink = (postcodeLookup) => {
12
+ if (postcodeLookup) {
13
+ return '/account-address-change'
14
+ } else {
15
+ return '/account-address-enter'
16
+ }
17
+ }
18
+
19
+ /**
20
+ * Generates the back link object for address-related pages.
21
+ *
22
+ * If the user used a postcode lookup, the back link returns to the address selection page.
23
+ * If they manually entered the address, the back link returns to the manual entry page.
24
+ *
25
+ * @param {boolean} postcodeLookup - Whether the user used a postcode lookup to find their address
26
+ * @returns {Object} An object with an `href` property containing the back link URL
27
+ */
28
+ export const addressBackLink = (postcodeLookup) => {
29
+ if (postcodeLookup) {
30
+ return { href: '/account-address-select' }
31
+ } else {
32
+ return { href: '/account-address-enter' }
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Combines address parts into a single line, joining non-empty values with commas.
38
+ *
39
+ * @private
40
+ */
41
+ const buildAddressLine = (parts) => {
42
+ return parts.filter(Boolean).join(', ') || null
43
+ }
44
+
45
+ /**
46
+ * Combines building range and street into a single line, joining with a space.
47
+ *
48
+ * @private
49
+ */
50
+ const buildStreetLine = (buildingRange, street) => {
51
+ return [buildingRange, street].filter(Boolean).join(' ') || null
52
+ }
53
+
54
+ /**
55
+ * Formats a lookup address (from address API) into a consistent flat structure.
56
+ *
57
+ * @private
58
+ */
59
+ const formatLookupAddress = (lookup, city, country, postcode) => ({
60
+ address1: buildAddressLine([lookup.pafOrganisationName, lookup.flatName, lookup.buildingName]),
61
+ address2: buildStreetLine(lookup.buildingNumberRange, lookup.street),
62
+ address3: buildAddressLine([lookup.doubleDependentLocality, lookup.dependentLocality]),
63
+ county: lookup.county ?? null,
64
+ city: city ?? null,
65
+ country: country ?? null,
66
+ postcode: postcode ?? null
67
+ })
68
+
69
+ /**
70
+ * Formats a manually entered address into a consistent flat structure.
71
+ *
72
+ * @private
73
+ */
74
+ const formatManualAddress = (manual, city, country, postcode) => ({
75
+ address1: manual.line1 ?? null,
76
+ address2: manual.line2 ?? null,
77
+ address3: manual.line3 ?? null,
78
+ city: city ?? null,
79
+ county: manual.line4 ?? null,
80
+ country: country ?? null,
81
+ postcode: postcode ?? null
82
+ })
83
+
84
+ /**
85
+ * Identify the correct address to use from the DAL response.
86
+ *
87
+ * An address lookup is where a user enters a postcode and selects an address
88
+ * returned from an API. Manual input is when a user chooses to type the address in themselves.
89
+ *
90
+ * If the UPRN is populated, the user has selected an address from the lookup.
91
+ * UPRN is only returned by the lookup API; manual addresses will always have `uprn = null`.
92
+ *
93
+ * If both a building number range and a street are present, they are combined into one line so they display together.
94
+ *
95
+ * City, postcode and country are always appended to the final address array.
96
+ *
97
+ * @param {Object} address - The complete address object
98
+ *
99
+ * @returns {string[]} An array of address fields (either from lookup or manual)
100
+ *
101
+ * @private
102
+ */
103
+ export const formatDisplayAddress = (address) => {
104
+ const { lookup, manual, postcode, country, city } = address
105
+
106
+ let addressLines = []
107
+
108
+ if (lookup.uprn) {
109
+ // If the uprn is populated then the user has selected an address from the lookup
110
+ const buildingAndStreet = [
111
+ lookup.buildingNumberRange,
112
+ lookup.street
113
+ ].filter(Boolean).join(' ')
114
+
115
+ addressLines = [
116
+ lookup.pafOrganisationName,
117
+ lookup.flatName,
118
+ lookup.buildingName,
119
+ buildingAndStreet,
120
+ lookup.doubleDependentLocality,
121
+ lookup.dependentLocality,
122
+ city,
123
+ lookup.county
124
+ ]
125
+ } else {
126
+ // Otherwise the user manually entered the address
127
+ addressLines = [
128
+ manual.line1,
129
+ manual.line2,
130
+ manual.line3,
131
+ city,
132
+ manual.line4, // County
133
+ manual.line5
134
+ ]
135
+ }
136
+
137
+ return [
138
+ ...addressLines.filter(Boolean),
139
+ postcode,
140
+ country
141
+ ]
142
+ }
143
+
144
+ /**
145
+ * Formats an original address fetched from the DAL into a flattened structure
146
+ * suitable for display or form population on the `address-enter` pages.
147
+ *
148
+ * If the address contains a UPRN, it is treated as a lookup address;
149
+ * otherwise, it is treated as a manually entered address.
150
+ *
151
+ * @param {Object} originalAddress - The full address object from the DAL
152
+ *
153
+ * @returns {Object} A flattened address object with consistent keys
154
+ */
155
+ export const formatOriginalAddress = (originalAddress) => {
156
+ const { lookup, manual, city, country, postcode } = originalAddress
157
+ return lookup.uprn
158
+ ? formatLookupAddress(lookup, city, country, postcode)
159
+ : formatManualAddress(manual, city, country, postcode)
160
+ }
161
+
162
+ /**
163
+ * Formats a changed address object into a consistent structure for form display.
164
+ *
165
+ * If the address includes a UPRN, it indicates the user selected it from an address lookup.
166
+ * In that case, the lookup fields are combined and mapped into the manual address format used by the form.
167
+ *
168
+ * If the address does not include a UPRN, it is assumed to be manually entered and returned as-is.
169
+ *
170
+ * @param {Object} changeBusinessAddress - The changed address object to format
171
+ *
172
+ * @returns {Object} A formatted address object with fields `address1`, `address2`, `address3`,
173
+ * `city`, `county`, `country`, and `postcode`.
174
+ */
175
+ export const formatChangedAddress = (changeBusinessAddress) => {
176
+ if (!changeBusinessAddress.uprn) {
177
+ // manual address (no lookup used)
178
+ return changeBusinessAddress
179
+ }
180
+
181
+ const {
182
+ pafOrganisationName,
183
+ flatName,
184
+ buildingName,
185
+ buildingNumberRange,
186
+ street,
187
+ doubleDependentLocality,
188
+ dependentLocality,
189
+ city,
190
+ county,
191
+ country,
192
+ postcode
193
+ } = changeBusinessAddress
194
+
195
+ return {
196
+ address1: buildAddressLine([pafOrganisationName, flatName, buildingName]),
197
+ address2: buildStreetLine(buildingNumberRange, street),
198
+ address3: buildAddressLine([doubleDependentLocality, dependentLocality]),
199
+ city: city ?? null,
200
+ county: county ?? null,
201
+ country: country ?? null,
202
+ postcode: postcode ?? null
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Formats a list of address objects for display in a dropdown menu.
208
+ *
209
+ * Each address object is transformed into an option with:
210
+ * - `value`: a concatenation of the address UPRN and displayAddress
211
+ * - `text`: the formatted display address string
212
+ * - `selected`: true only if it matches the previously picked address
213
+ *
214
+ * A summary option is prepended to the start of the list, showing how many
215
+ * addresses were found. This summary option is selected by default unless
216
+ * a previously picked address exists.
217
+ *
218
+ * This function is shared across presenters that display address selection
219
+ * lists (e.g. personal or business address flows).
220
+ *
221
+ * Note: The `value` combines `uprn` and `displayAddress` to ensure uniqueness.
222
+ * Some addresses (for example, postcode LL55 2NF) have been observed to share
223
+ * the same UPRN, which caused incorrect selections when UPRN alone was used.
224
+ * Concatenating both fields guarantees each dropdown option has a unique value.
225
+ *
226
+ * @param {Array<Object>} addresses - List of address objects with `uprn` and `displayAddress` properties
227
+ * @param {Object} [previouslyPickedAddress] - Optional object representing the address previously selected by the user
228
+ *
229
+ * @returns {Array<Object>} Array of formatted address options ready for display
230
+ */
231
+ export const formatDisplayAddresses = (addresses, previouslyPickedAddress) => {
232
+ const displayAddresses = addresses.map(address => ({
233
+ value: `${address.uprn}${address.displayAddress}`,
234
+ text: address.displayAddress,
235
+ selected:
236
+ previouslyPickedAddress?.uprn === address.uprn &&
237
+ previouslyPickedAddress?.displayAddress === address.displayAddress
238
+ }))
239
+
240
+ // Check if any address is already selected
241
+ const hasSelectedAddress = displayAddresses.some(addr => addr.selected)
242
+
243
+ // Add a display summary option to the beginning of the list
244
+ // e.g. "18 addresses found"
245
+ const text = addresses.length === 1 ? '1 address found' : `${addresses.length} addresses found`
246
+
247
+ displayAddresses.unshift({
248
+ value: 'display',
249
+ text,
250
+ selected: !hasSelectedAddress
251
+ })
252
+
253
+ return displayAddresses
254
+ }
@@ -37,227 +37,6 @@ export const formatNumber = (payloadNumber, changedNumber, originalNumber) => {
37
37
  return originalNumber
38
38
  }
39
39
 
40
- /**
41
- * Identify the correct address to use from the DAL response.
42
- *
43
- * An address lookup is where a user enters a postcode and selects an address
44
- * returned from an API. Manual input is when a user chooses to type the address in themselves.
45
- *
46
- * If the UPRN is populated, the user has selected an address from the lookup.
47
- * UPRN is only returned by the lookup API; manual addresses will always have `uprn = null`.
48
- *
49
- * If both a building number range and a street are present, they are combined into one line so they display together.
50
- *
51
- * City, postcode and country are always appended to the final address array.
52
- *
53
- * @param {Object} address - The complete address object
54
- *
55
- * @returns {string[]} An array of address fields (either from lookup or manual)
56
- *
57
- * @private
58
- */
59
-
60
- export const formatDisplayAddress = (address) => {
61
- const { lookup, manual, postcode, country, city } = address
62
-
63
- let addressLines = []
64
-
65
- if (lookup.uprn) {
66
- // If the uprn is populated then the user has selected an address from the lookup
67
- const buildingAndStreet = [
68
- lookup.buildingNumberRange,
69
- lookup.street
70
- ].filter(Boolean).join(' ')
71
-
72
- addressLines = [
73
- lookup.pafOrganisationName,
74
- lookup.flatName,
75
- lookup.buildingName,
76
- buildingAndStreet,
77
- lookup.doubleDependentLocality,
78
- lookup.dependentLocality,
79
- city,
80
- lookup.county
81
- ]
82
- } else {
83
- // Otherwise the user manually entered the address
84
- addressLines = [
85
- manual.line1,
86
- manual.line2,
87
- manual.line3,
88
- city,
89
- manual.line4, // County
90
- manual.line5
91
- ]
92
- }
93
-
94
- return [
95
- ...addressLines.filter(Boolean),
96
- postcode,
97
- country
98
- ]
99
- }
100
-
101
- /**
102
- * Combines address parts into a single line, joining non-empty values with commas.
103
- *
104
- * @private
105
- */
106
- const buildAddressLine = (parts) => {
107
- return parts.filter(Boolean).join(', ') || null
108
- }
109
-
110
- /**
111
- * Combines building range and street into a single line, joining with a space.
112
- *
113
- * @private
114
- */
115
- const buildStreetLine = (buildingRange, street) => {
116
- return [buildingRange, street].filter(Boolean).join(' ') || null
117
- }
118
-
119
- /**
120
- * Formats a lookup address (from address API) into a consistent flat structure.
121
- *
122
- * @private
123
- */
124
- const formatLookupAddress = (lookup, city, country, postcode) => ({
125
- address1: buildAddressLine([lookup.pafOrganisationName, lookup.flatName, lookup.buildingName]),
126
- address2: buildStreetLine(lookup.buildingNumberRange, lookup.street),
127
- address3: buildAddressLine([lookup.doubleDependentLocality, lookup.dependentLocality]),
128
- county: lookup.county ?? null,
129
- city: city ?? null,
130
- country: country ?? null,
131
- postcode: postcode ?? null
132
- })
133
-
134
- /**
135
- * Formats a manually entered address into a consistent flat structure.
136
- *
137
- * @private
138
- */
139
- const formatManualAddress = (manual, city, country, postcode) => ({
140
- address1: manual.line1 ?? null,
141
- address2: manual.line2 ?? null,
142
- address3: manual.line3 ?? null,
143
- city: city ?? null,
144
- county: manual.line4 ?? null,
145
- country: country ?? null,
146
- postcode: postcode ?? null
147
- })
148
-
149
- /**
150
- * Formats an original address fetched from the DAL into a flattened structure
151
- * suitable for display or form population on the `address-enter` pages.
152
- *
153
- * If the address contains a UPRN, it is treated as a lookup address;
154
- * otherwise, it is treated as a manually entered address.
155
- *
156
- * @param {Object} originalAddress - The full address object from the DAL
157
- *
158
- * @returns {Object} A flattened address object with consistent keys
159
- */
160
- export const formatOriginalAddress = (originalAddress) => {
161
- const { lookup, manual, city, country, postcode } = originalAddress
162
- return lookup.uprn
163
- ? formatLookupAddress(lookup, city, country, postcode)
164
- : formatManualAddress(manual, city, country, postcode)
165
- }
166
-
167
- /**
168
- * Formats a changed address object into a consistent structure for form display.
169
- *
170
- * If the address includes a UPRN, it indicates the user selected it from an address lookup.
171
- * In that case, the lookup fields are combined and mapped into the manual address format used by the form.
172
- *
173
- * If the address does not include a UPRN, it is assumed to be manually entered and returned as-is.
174
- *
175
- * @param {Object} changeBusinessAddress - The changed address object to format
176
- *
177
- * @returns {Object} A formatted address object with fields `address1`, `address2`, `address3`,
178
- * `city`, `county`, `country`, and `postcode`.
179
- */
180
- export const formatChangedAddress = (changeBusinessAddress) => {
181
- if (!changeBusinessAddress.uprn) {
182
- // manual address (no lookup used)
183
- return changeBusinessAddress
184
- }
185
-
186
- const {
187
- pafOrganisationName,
188
- flatName,
189
- buildingName,
190
- buildingNumberRange,
191
- street,
192
- doubleDependentLocality,
193
- dependentLocality,
194
- city,
195
- county,
196
- country,
197
- postcode
198
- } = changeBusinessAddress
199
-
200
- return {
201
- address1: buildAddressLine([pafOrganisationName, flatName, buildingName]),
202
- address2: buildStreetLine(buildingNumberRange, street),
203
- address3: buildAddressLine([doubleDependentLocality, dependentLocality]),
204
- city: city ?? null,
205
- county: county ?? null,
206
- country: country ?? null,
207
- postcode: postcode ?? null
208
- }
209
- }
210
-
211
- /**
212
- * Formats a list of address objects for display in a dropdown menu.
213
- *
214
- * Each address object is transformed into an option with:
215
- * - `value`: a concatenation of the address UPRN and displayAddress
216
- * - `text`: the formatted display address string
217
- * - `selected`: true only if it matches the previously picked address
218
- *
219
- * A summary option is prepended to the start of the list, showing how many
220
- * addresses were found. This summary option is selected by default unless
221
- * a previously picked address exists.
222
- *
223
- * This function is shared across presenters that display address selection
224
- * lists (e.g. personal or business address flows).
225
- *
226
- * Note: The `value` combines `uprn` and `displayAddress` to ensure uniqueness.
227
- * Some addresses (for example, postcode LL55 2NF) have been observed to share
228
- * the same UPRN, which caused incorrect selections when UPRN alone was used.
229
- * Concatenating both fields guarantees each dropdown option has a unique value.
230
- *
231
- * @param {Array<Object>} addresses - List of address objects with `uprn` and `displayAddress` properties
232
- * @param {Object} [previouslyPickedAddress] - Optional object representing the address previously selected by the user
233
- *
234
- * @returns {Array<Object>} Array of formatted address options ready for display
235
- */
236
- export const formatDisplayAddresses = (addresses, previouslyPickedAddress) => {
237
- const displayAddresses = addresses.map(address => ({
238
- value: `${address.uprn}${address.displayAddress}`,
239
- text: address.displayAddress,
240
- selected:
241
- previouslyPickedAddress?.uprn === address.uprn &&
242
- previouslyPickedAddress?.displayAddress === address.displayAddress
243
- }))
244
-
245
- // Check if any address is already selected
246
- const hasSelectedAddress = displayAddresses.some(addr => addr.selected)
247
-
248
- // Add a display summary option to the beginning of the list
249
- // e.g. "18 addresses found"
250
- const text = addresses.length === 1 ? '1 address found' : `${addresses.length} addresses found`
251
-
252
- displayAddresses.unshift({
253
- value: 'display',
254
- text,
255
- selected: !hasSelectedAddress
256
- })
257
-
258
- return displayAddresses
259
- }
260
-
261
40
  /**
262
41
  * Shared helper used by base presenters to sort validation errors so they
263
42
  * appear in the same order as the sections and fields shown on a Fix List page.
@@ -5,7 +5,7 @@
5
5
  * applications and are provided here to avoid duplication.
6
6
  */
7
7
 
8
- import { formatDisplayAddress } from './base-presenter.js'
8
+ import { formatDisplayAddress } from './address-presenter.js'
9
9
 
10
10
  /**
11
11
  * Returns the action text for a summary-list row.
@@ -1,10 +1,6 @@
1
1
  import {
2
2
  formatBackLink,
3
3
  formatNumber,
4
- formatDisplayAddress,
5
- formatOriginalAddress,
6
- formatChangedAddress,
7
- formatDisplayAddresses,
8
4
  sortErrorsBySectionOrder
9
5
  } from './base-presenter.js'
10
6
 
@@ -15,6 +11,15 @@ import {
15
11
  formatBusinessAddress
16
12
  } from './business-details-presenter.js'
17
13
 
14
+ import {
15
+ addressBackLink,
16
+ addressChangeLink,
17
+ formatDisplayAddress,
18
+ formatOriginalAddress,
19
+ formatChangedAddress,
20
+ formatDisplayAddresses
21
+ } from './address-presenter.js'
22
+
18
23
  export const presenters = {
19
24
  formatBackLink,
20
25
  formatNumber,
@@ -26,5 +31,7 @@ export const presenters = {
26
31
  getActionText,
27
32
  formatCph,
28
33
  formatCphText,
29
- formatBusinessAddress
34
+ formatBusinessAddress,
35
+ addressBackLink,
36
+ addressChangeLink
30
37
  }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Builds the GraphQL variables for the `updateBusinessName` mutation.
3
+ *
4
+ * @param {string} name - The new business name
5
+ * @param {string} sbi - The Single Business Identifier of the business being updated
6
+ * @returns {object} The mutation variables in the shape `{ input: { name, sbi } }`
7
+ */
8
+ export const buildUpdateBusinessNameVariables = (name, sbi) => {
9
+ return { input: { name, sbi } }
10
+ }
@@ -1,9 +1,11 @@
1
1
  import { formatFullName } from './format-full-name.js'
2
2
  import { formatValidationErrors } from './format-validation-errors.js'
3
3
  import { buildUpdateBusinessEmailVariables } from './build-update-business-email-variables.js'
4
+ import { buildUpdateBusinessNameVariables } from './build-update-business-name-variables.js'
4
5
 
5
6
  export const utils = {
6
7
  formatFullName,
7
8
  formatValidationErrors,
8
- buildUpdateBusinessEmailVariables
9
+ buildUpdateBusinessEmailVariables,
10
+ buildUpdateBusinessNameVariables
9
11
  }