@defra/fcp-sfd-frontend-engine 0.2.12 → 0.3.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 +151 -0
- package/dist/index.js +150 -0
- package/package.json +2 -2
- package/src/index.js +1 -0
- package/src/presenters/base-presenter.js +299 -0
- package/src/presenters/presenters.js +19 -0
package/dist/index.cjs
CHANGED
|
@@ -30,6 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
var index_exports = {};
|
|
31
31
|
__export(index_exports, {
|
|
32
32
|
mappers: () => mappers,
|
|
33
|
+
presenters: () => presenters,
|
|
33
34
|
schemas: () => schemas,
|
|
34
35
|
utils: () => utils
|
|
35
36
|
});
|
|
@@ -155,9 +156,159 @@ var formatValidationErrors = (errors) => {
|
|
|
155
156
|
var utils = {
|
|
156
157
|
formatValidationErrors
|
|
157
158
|
};
|
|
159
|
+
|
|
160
|
+
// src/presenters/base-presenter.js
|
|
161
|
+
var BACK_LINK_DISPLAY_MAX = 50;
|
|
162
|
+
var formatBackLink = (businessName) => {
|
|
163
|
+
if (businessName.length > BACK_LINK_DISPLAY_MAX) {
|
|
164
|
+
return `Back to ${businessName.slice(0, BACK_LINK_DISPLAY_MAX)}\u2026`;
|
|
165
|
+
}
|
|
166
|
+
return `Back to ${businessName}`;
|
|
167
|
+
};
|
|
168
|
+
var formatNumber = (payloadNumber, changedNumber, originalNumber) => {
|
|
169
|
+
if (payloadNumber !== void 0) {
|
|
170
|
+
return payloadNumber;
|
|
171
|
+
}
|
|
172
|
+
if (changedNumber !== void 0) {
|
|
173
|
+
return changedNumber;
|
|
174
|
+
}
|
|
175
|
+
return originalNumber;
|
|
176
|
+
};
|
|
177
|
+
var formatDisplayAddress = (address) => {
|
|
178
|
+
const { lookup, manual, postcode, country, city } = address;
|
|
179
|
+
let addressLines = [];
|
|
180
|
+
if (lookup.uprn) {
|
|
181
|
+
const buildingAndStreet = [
|
|
182
|
+
lookup.buildingNumberRange,
|
|
183
|
+
lookup.street
|
|
184
|
+
].filter(Boolean).join(" ");
|
|
185
|
+
addressLines = [
|
|
186
|
+
lookup.pafOrganisationName,
|
|
187
|
+
lookup.flatName,
|
|
188
|
+
lookup.buildingName,
|
|
189
|
+
buildingAndStreet,
|
|
190
|
+
lookup.doubleDependentLocality,
|
|
191
|
+
lookup.dependentLocality,
|
|
192
|
+
city,
|
|
193
|
+
lookup.county
|
|
194
|
+
];
|
|
195
|
+
} else {
|
|
196
|
+
addressLines = [
|
|
197
|
+
manual.line1,
|
|
198
|
+
manual.line2,
|
|
199
|
+
manual.line3,
|
|
200
|
+
city,
|
|
201
|
+
manual.line4,
|
|
202
|
+
// County
|
|
203
|
+
manual.line5
|
|
204
|
+
];
|
|
205
|
+
}
|
|
206
|
+
return [
|
|
207
|
+
...addressLines.filter(Boolean),
|
|
208
|
+
postcode,
|
|
209
|
+
country
|
|
210
|
+
];
|
|
211
|
+
};
|
|
212
|
+
var buildAddressLine = (parts) => {
|
|
213
|
+
return parts.filter(Boolean).join(", ") || null;
|
|
214
|
+
};
|
|
215
|
+
var buildStreetLine = (buildingRange, street) => {
|
|
216
|
+
return [buildingRange, street].filter(Boolean).join(" ") || null;
|
|
217
|
+
};
|
|
218
|
+
var formatLookupAddress = (lookup, city, country, postcode) => ({
|
|
219
|
+
address1: buildAddressLine([lookup.pafOrganisationName, lookup.flatName, lookup.buildingName]),
|
|
220
|
+
address2: buildStreetLine(lookup.buildingNumberRange, lookup.street),
|
|
221
|
+
address3: buildAddressLine([lookup.doubleDependentLocality, lookup.dependentLocality]),
|
|
222
|
+
county: lookup.county ?? null,
|
|
223
|
+
city: city ?? null,
|
|
224
|
+
country: country ?? null,
|
|
225
|
+
postcode: postcode ?? null
|
|
226
|
+
});
|
|
227
|
+
var formatManualAddress = (manual, city, country, postcode) => ({
|
|
228
|
+
address1: manual.line1 ?? null,
|
|
229
|
+
address2: manual.line2 ?? null,
|
|
230
|
+
address3: manual.line3 ?? null,
|
|
231
|
+
city: city ?? null,
|
|
232
|
+
county: manual.line4 ?? null,
|
|
233
|
+
country: country ?? null,
|
|
234
|
+
postcode: postcode ?? null
|
|
235
|
+
});
|
|
236
|
+
var formatOriginalAddress = (originalAddress) => {
|
|
237
|
+
const { lookup, manual, city, country, postcode } = originalAddress;
|
|
238
|
+
return lookup.uprn ? formatLookupAddress(lookup, city, country, postcode) : formatManualAddress(manual, city, country, postcode);
|
|
239
|
+
};
|
|
240
|
+
var formatChangedAddress = (changeBusinessAddress) => {
|
|
241
|
+
if (!changeBusinessAddress.uprn) {
|
|
242
|
+
return changeBusinessAddress;
|
|
243
|
+
}
|
|
244
|
+
const {
|
|
245
|
+
pafOrganisationName,
|
|
246
|
+
flatName,
|
|
247
|
+
buildingName,
|
|
248
|
+
buildingNumberRange,
|
|
249
|
+
street,
|
|
250
|
+
doubleDependentLocality,
|
|
251
|
+
dependentLocality,
|
|
252
|
+
city,
|
|
253
|
+
county,
|
|
254
|
+
country,
|
|
255
|
+
postcode
|
|
256
|
+
} = changeBusinessAddress;
|
|
257
|
+
return {
|
|
258
|
+
address1: buildAddressLine([pafOrganisationName, flatName, buildingName]),
|
|
259
|
+
address2: buildStreetLine(buildingNumberRange, street),
|
|
260
|
+
address3: buildAddressLine([doubleDependentLocality, dependentLocality]),
|
|
261
|
+
city: city ?? null,
|
|
262
|
+
county: county ?? null,
|
|
263
|
+
country: country ?? null,
|
|
264
|
+
postcode: postcode ?? null
|
|
265
|
+
};
|
|
266
|
+
};
|
|
267
|
+
var formatDisplayAddresses = (addresses, previouslyPickedAddress) => {
|
|
268
|
+
const displayAddresses = addresses.map((address) => ({
|
|
269
|
+
value: `${address.uprn}${address.displayAddress}`,
|
|
270
|
+
text: address.displayAddress,
|
|
271
|
+
selected: (previouslyPickedAddress == null ? void 0 : previouslyPickedAddress.uprn) === address.uprn && (previouslyPickedAddress == null ? void 0 : previouslyPickedAddress.displayAddress) === address.displayAddress
|
|
272
|
+
}));
|
|
273
|
+
const hasSelectedAddress = displayAddresses.some((addr) => addr.selected);
|
|
274
|
+
const text = addresses.length === 1 ? "1 address found" : `${addresses.length} addresses found`;
|
|
275
|
+
displayAddresses.unshift({
|
|
276
|
+
value: "display",
|
|
277
|
+
text,
|
|
278
|
+
selected: !hasSelectedAddress
|
|
279
|
+
});
|
|
280
|
+
return displayAddresses;
|
|
281
|
+
};
|
|
282
|
+
var sortErrorsBySectionOrder = (errors, orderedSectionsToFix, SECTION_FIELD_ORDER) => {
|
|
283
|
+
const sortedErrors = [];
|
|
284
|
+
for (const section of orderedSectionsToFix) {
|
|
285
|
+
const fieldsInSection = SECTION_FIELD_ORDER[section] || [];
|
|
286
|
+
for (const field of fieldsInSection) {
|
|
287
|
+
if (errors[field]) {
|
|
288
|
+
sortedErrors.push({
|
|
289
|
+
field,
|
|
290
|
+
...errors[field]
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return sortedErrors;
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
// src/presenters/presenters.js
|
|
299
|
+
var presenters = {
|
|
300
|
+
formatBackLink,
|
|
301
|
+
formatNumber,
|
|
302
|
+
formatDisplayAddress,
|
|
303
|
+
formatOriginalAddress,
|
|
304
|
+
formatChangedAddress,
|
|
305
|
+
formatDisplayAddresses,
|
|
306
|
+
sortErrorsBySectionOrder
|
|
307
|
+
};
|
|
158
308
|
// Annotate the CommonJS export names for ESM import in node:
|
|
159
309
|
0 && (module.exports = {
|
|
160
310
|
mappers,
|
|
311
|
+
presenters,
|
|
161
312
|
schemas,
|
|
162
313
|
utils
|
|
163
314
|
});
|
package/dist/index.js
CHANGED
|
@@ -118,8 +118,158 @@ var formatValidationErrors = (errors) => {
|
|
|
118
118
|
var utils = {
|
|
119
119
|
formatValidationErrors
|
|
120
120
|
};
|
|
121
|
+
|
|
122
|
+
// src/presenters/base-presenter.js
|
|
123
|
+
var BACK_LINK_DISPLAY_MAX = 50;
|
|
124
|
+
var formatBackLink = (businessName) => {
|
|
125
|
+
if (businessName.length > BACK_LINK_DISPLAY_MAX) {
|
|
126
|
+
return `Back to ${businessName.slice(0, BACK_LINK_DISPLAY_MAX)}\u2026`;
|
|
127
|
+
}
|
|
128
|
+
return `Back to ${businessName}`;
|
|
129
|
+
};
|
|
130
|
+
var formatNumber = (payloadNumber, changedNumber, originalNumber) => {
|
|
131
|
+
if (payloadNumber !== void 0) {
|
|
132
|
+
return payloadNumber;
|
|
133
|
+
}
|
|
134
|
+
if (changedNumber !== void 0) {
|
|
135
|
+
return changedNumber;
|
|
136
|
+
}
|
|
137
|
+
return originalNumber;
|
|
138
|
+
};
|
|
139
|
+
var formatDisplayAddress = (address) => {
|
|
140
|
+
const { lookup, manual, postcode, country, city } = address;
|
|
141
|
+
let addressLines = [];
|
|
142
|
+
if (lookup.uprn) {
|
|
143
|
+
const buildingAndStreet = [
|
|
144
|
+
lookup.buildingNumberRange,
|
|
145
|
+
lookup.street
|
|
146
|
+
].filter(Boolean).join(" ");
|
|
147
|
+
addressLines = [
|
|
148
|
+
lookup.pafOrganisationName,
|
|
149
|
+
lookup.flatName,
|
|
150
|
+
lookup.buildingName,
|
|
151
|
+
buildingAndStreet,
|
|
152
|
+
lookup.doubleDependentLocality,
|
|
153
|
+
lookup.dependentLocality,
|
|
154
|
+
city,
|
|
155
|
+
lookup.county
|
|
156
|
+
];
|
|
157
|
+
} else {
|
|
158
|
+
addressLines = [
|
|
159
|
+
manual.line1,
|
|
160
|
+
manual.line2,
|
|
161
|
+
manual.line3,
|
|
162
|
+
city,
|
|
163
|
+
manual.line4,
|
|
164
|
+
// County
|
|
165
|
+
manual.line5
|
|
166
|
+
];
|
|
167
|
+
}
|
|
168
|
+
return [
|
|
169
|
+
...addressLines.filter(Boolean),
|
|
170
|
+
postcode,
|
|
171
|
+
country
|
|
172
|
+
];
|
|
173
|
+
};
|
|
174
|
+
var buildAddressLine = (parts) => {
|
|
175
|
+
return parts.filter(Boolean).join(", ") || null;
|
|
176
|
+
};
|
|
177
|
+
var buildStreetLine = (buildingRange, street) => {
|
|
178
|
+
return [buildingRange, street].filter(Boolean).join(" ") || null;
|
|
179
|
+
};
|
|
180
|
+
var formatLookupAddress = (lookup, city, country, postcode) => ({
|
|
181
|
+
address1: buildAddressLine([lookup.pafOrganisationName, lookup.flatName, lookup.buildingName]),
|
|
182
|
+
address2: buildStreetLine(lookup.buildingNumberRange, lookup.street),
|
|
183
|
+
address3: buildAddressLine([lookup.doubleDependentLocality, lookup.dependentLocality]),
|
|
184
|
+
county: lookup.county ?? null,
|
|
185
|
+
city: city ?? null,
|
|
186
|
+
country: country ?? null,
|
|
187
|
+
postcode: postcode ?? null
|
|
188
|
+
});
|
|
189
|
+
var formatManualAddress = (manual, city, country, postcode) => ({
|
|
190
|
+
address1: manual.line1 ?? null,
|
|
191
|
+
address2: manual.line2 ?? null,
|
|
192
|
+
address3: manual.line3 ?? null,
|
|
193
|
+
city: city ?? null,
|
|
194
|
+
county: manual.line4 ?? null,
|
|
195
|
+
country: country ?? null,
|
|
196
|
+
postcode: postcode ?? null
|
|
197
|
+
});
|
|
198
|
+
var formatOriginalAddress = (originalAddress) => {
|
|
199
|
+
const { lookup, manual, city, country, postcode } = originalAddress;
|
|
200
|
+
return lookup.uprn ? formatLookupAddress(lookup, city, country, postcode) : formatManualAddress(manual, city, country, postcode);
|
|
201
|
+
};
|
|
202
|
+
var formatChangedAddress = (changeBusinessAddress) => {
|
|
203
|
+
if (!changeBusinessAddress.uprn) {
|
|
204
|
+
return changeBusinessAddress;
|
|
205
|
+
}
|
|
206
|
+
const {
|
|
207
|
+
pafOrganisationName,
|
|
208
|
+
flatName,
|
|
209
|
+
buildingName,
|
|
210
|
+
buildingNumberRange,
|
|
211
|
+
street,
|
|
212
|
+
doubleDependentLocality,
|
|
213
|
+
dependentLocality,
|
|
214
|
+
city,
|
|
215
|
+
county,
|
|
216
|
+
country,
|
|
217
|
+
postcode
|
|
218
|
+
} = changeBusinessAddress;
|
|
219
|
+
return {
|
|
220
|
+
address1: buildAddressLine([pafOrganisationName, flatName, buildingName]),
|
|
221
|
+
address2: buildStreetLine(buildingNumberRange, street),
|
|
222
|
+
address3: buildAddressLine([doubleDependentLocality, dependentLocality]),
|
|
223
|
+
city: city ?? null,
|
|
224
|
+
county: county ?? null,
|
|
225
|
+
country: country ?? null,
|
|
226
|
+
postcode: postcode ?? null
|
|
227
|
+
};
|
|
228
|
+
};
|
|
229
|
+
var formatDisplayAddresses = (addresses, previouslyPickedAddress) => {
|
|
230
|
+
const displayAddresses = addresses.map((address) => ({
|
|
231
|
+
value: `${address.uprn}${address.displayAddress}`,
|
|
232
|
+
text: address.displayAddress,
|
|
233
|
+
selected: (previouslyPickedAddress == null ? void 0 : previouslyPickedAddress.uprn) === address.uprn && (previouslyPickedAddress == null ? void 0 : previouslyPickedAddress.displayAddress) === address.displayAddress
|
|
234
|
+
}));
|
|
235
|
+
const hasSelectedAddress = displayAddresses.some((addr) => addr.selected);
|
|
236
|
+
const text = addresses.length === 1 ? "1 address found" : `${addresses.length} addresses found`;
|
|
237
|
+
displayAddresses.unshift({
|
|
238
|
+
value: "display",
|
|
239
|
+
text,
|
|
240
|
+
selected: !hasSelectedAddress
|
|
241
|
+
});
|
|
242
|
+
return displayAddresses;
|
|
243
|
+
};
|
|
244
|
+
var sortErrorsBySectionOrder = (errors, orderedSectionsToFix, SECTION_FIELD_ORDER) => {
|
|
245
|
+
const sortedErrors = [];
|
|
246
|
+
for (const section of orderedSectionsToFix) {
|
|
247
|
+
const fieldsInSection = SECTION_FIELD_ORDER[section] || [];
|
|
248
|
+
for (const field of fieldsInSection) {
|
|
249
|
+
if (errors[field]) {
|
|
250
|
+
sortedErrors.push({
|
|
251
|
+
field,
|
|
252
|
+
...errors[field]
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return sortedErrors;
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
// src/presenters/presenters.js
|
|
261
|
+
var presenters = {
|
|
262
|
+
formatBackLink,
|
|
263
|
+
formatNumber,
|
|
264
|
+
formatDisplayAddress,
|
|
265
|
+
formatOriginalAddress,
|
|
266
|
+
formatChangedAddress,
|
|
267
|
+
formatDisplayAddresses,
|
|
268
|
+
sortErrorsBySectionOrder
|
|
269
|
+
};
|
|
121
270
|
export {
|
|
122
271
|
mappers,
|
|
272
|
+
presenters,
|
|
123
273
|
schemas,
|
|
124
274
|
utils
|
|
125
275
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@defra/fcp-sfd-frontend-engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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": {
|
|
@@ -47,4 +47,4 @@
|
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"joi": "18.2.1"
|
|
49
49
|
}
|
|
50
|
-
}
|
|
50
|
+
}
|
package/src/index.js
CHANGED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base presenter for formatting data for display
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Formats the business name into back link text.
|
|
7
|
+
* If the business name is greater than 50 characters, it will be truncated with an ellipsis.
|
|
8
|
+
*/
|
|
9
|
+
const BACK_LINK_DISPLAY_MAX = 50
|
|
10
|
+
|
|
11
|
+
export const formatBackLink = (businessName) => {
|
|
12
|
+
if (businessName.length > BACK_LINK_DISPLAY_MAX) {
|
|
13
|
+
return `Back to ${businessName.slice(0, BACK_LINK_DISPLAY_MAX)}…`
|
|
14
|
+
}
|
|
15
|
+
return `Back to ${businessName}`
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The first time a user loads the phone numbers change page they won't have entered any data, so a payload
|
|
20
|
+
* or a changedNumber won't be present. If a user has a validation issue then we want to replay the payload data to them.
|
|
21
|
+
* We check if payload is not undefined because it could be a user has removed the 'mobile' number for example but
|
|
22
|
+
* incorrectly entered the telephone number so the payload for this would appear as an empty string.
|
|
23
|
+
*
|
|
24
|
+
* Payload is the priority to check and then after that if changedNumber is present then we display that value.
|
|
25
|
+
*
|
|
26
|
+
* @private
|
|
27
|
+
*/
|
|
28
|
+
export const formatNumber = (payloadNumber, changedNumber, originalNumber) => {
|
|
29
|
+
if (payloadNumber !== undefined) {
|
|
30
|
+
return payloadNumber
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (changedNumber !== undefined) {
|
|
34
|
+
return changedNumber
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return originalNumber
|
|
38
|
+
}
|
|
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
|
+
/**
|
|
262
|
+
* Shared helper used by base presenters to sort validation errors so they
|
|
263
|
+
* appear in the same order as the sections and fields shown on a Fix List page.
|
|
264
|
+
*
|
|
265
|
+
* Fix List pages are built dynamically depending on:
|
|
266
|
+
* - which sections need fixing, and
|
|
267
|
+
* - which field the user originally selected.
|
|
268
|
+
*
|
|
269
|
+
* Because of that, we can’t rely on the order of the validation object.
|
|
270
|
+
* We need to deliberately sort the errors so they match:
|
|
271
|
+
* 1. The order the sections appear on the page, and
|
|
272
|
+
* 2. The logical order of fields within each section.
|
|
273
|
+
*
|
|
274
|
+
* This function keeps that logic in one reusable place so it can be used
|
|
275
|
+
* by Business, Personal, or any future Fix List presenter.
|
|
276
|
+
*
|
|
277
|
+
* It returns the errors as an array, already arranged in the correct
|
|
278
|
+
* display order for the UI.
|
|
279
|
+
*/
|
|
280
|
+
export const sortErrorsBySectionOrder = (errors, orderedSectionsToFix, SECTION_FIELD_ORDER) => {
|
|
281
|
+
const sortedErrors = []
|
|
282
|
+
|
|
283
|
+
for (const section of orderedSectionsToFix) {
|
|
284
|
+
// A section (i.e 'address') can have multiple fields (i.e 'line1', 'line2', 'line3')
|
|
285
|
+
const fieldsInSection = SECTION_FIELD_ORDER[section] || []
|
|
286
|
+
|
|
287
|
+
for (const field of fieldsInSection) {
|
|
288
|
+
// If there's an error for this field, add it to the sorted list with the error details
|
|
289
|
+
if (errors[field]) {
|
|
290
|
+
sortedErrors.push({
|
|
291
|
+
field,
|
|
292
|
+
...errors[field]
|
|
293
|
+
})
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return sortedErrors
|
|
299
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {
|
|
2
|
+
formatBackLink,
|
|
3
|
+
formatNumber,
|
|
4
|
+
formatDisplayAddress,
|
|
5
|
+
formatOriginalAddress,
|
|
6
|
+
formatChangedAddress,
|
|
7
|
+
formatDisplayAddresses,
|
|
8
|
+
sortErrorsBySectionOrder
|
|
9
|
+
} from './base-presenter.js'
|
|
10
|
+
|
|
11
|
+
export const presenters = {
|
|
12
|
+
formatBackLink,
|
|
13
|
+
formatNumber,
|
|
14
|
+
formatDisplayAddress,
|
|
15
|
+
formatOriginalAddress,
|
|
16
|
+
formatChangedAddress,
|
|
17
|
+
formatDisplayAddresses,
|
|
18
|
+
sortErrorsBySectionOrder
|
|
19
|
+
}
|