@controleonline/ui-common 1.2.80 → 1.2.82

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.
Files changed (90) hide show
  1. package/AGENTS.md +8 -0
  2. package/package.json +9 -3
  3. package/src/api/fetch.js +7 -7
  4. package/src/react/components/AddImportModal.js +6 -2
  5. package/src/react/components/BackgroundRuntimeBridge.js +201 -215
  6. package/src/react/components/ConfirmModal.js +44 -9
  7. package/src/react/components/ConfirmModal.styles.js +55 -41
  8. package/src/react/components/CopyDeviceConfigModal.js +193 -0
  9. package/src/react/components/CopyDeviceConfigModal.styles.js +123 -0
  10. package/src/react/components/DefaultProvider.native.js +22 -0
  11. package/src/react/components/DefaultProvider.web.js +25 -1
  12. package/src/react/components/EntityLogBranch.js +482 -0
  13. package/src/react/components/EntityLogCards.js +369 -0
  14. package/src/react/components/EntityLogContent.js +113 -794
  15. package/src/react/components/LinkedOrderProductsTab.js +3 -3
  16. package/src/react/components/PrintService.js +130 -409
  17. package/src/react/components/RemoteCheckoutService.js +2 -2
  18. package/src/react/components/RuntimeBottomNavigationBar.js +141 -146
  19. package/src/react/components/RuntimeFooterMarqueeText.js +137 -0
  20. package/src/react/components/RuntimeInfoFooter.js +12 -15
  21. package/src/react/components/SystemErrorToast.js +49 -29
  22. package/src/react/components/SystemErrorToast.styles.js +43 -38
  23. package/src/react/components/UserAvatar.js +120 -28
  24. package/src/react/config/deviceConfigBootstrap.js +330 -304
  25. package/src/react/hooks/usePrintSpoolEffects.js +161 -0
  26. package/src/react/pages/DeviceDetailPage.js +6 -2882
  27. package/src/react/pages/Devices/detail/DeviceDetailAlertsCommandsSection.js +233 -0
  28. package/src/react/pages/Devices/detail/DeviceDetailHeader.js +138 -0
  29. package/src/react/pages/Devices/detail/DeviceDetailMovementSections.js +167 -0
  30. package/src/react/pages/Devices/detail/DeviceDetailOrdersPrintSection.js +216 -0
  31. package/src/react/pages/Devices/detail/DeviceDetailPaymentSection.js +44 -0
  32. package/src/react/pages/Devices/detail/DeviceDetailPdvConfigSection.js +390 -0
  33. package/src/react/pages/Devices/detail/DeviceDetailRenderers.js +135 -0
  34. package/src/react/pages/Devices/detail/DeviceDetailScreen.js +495 -0
  35. package/src/react/pages/Devices/detail/OptionButtonChip.js +65 -0
  36. package/src/react/pages/Devices/detail/deviceDetailConstants.js +36 -0
  37. package/src/react/pages/Devices/detail/deviceDetailHelpers.js +70 -0
  38. package/src/react/pages/Devices/detail/useDeviceDetailActions.js +483 -0
  39. package/src/react/pages/Devices/detail/useDeviceDetailCopyConfig.js +87 -0
  40. package/src/react/pages/Devices/detail/useDeviceDetailLoaders.js +327 -0
  41. package/src/react/pages/Devices/detail/useDeviceDetailSaves.js +429 -0
  42. package/src/react/pages/Devices/detail/useDeviceDetailStateA.js +489 -0
  43. package/src/react/pages/Devices/detail/useDeviceDetailStateB.js +98 -0
  44. package/src/react/pages/Devices/deviceTypes/index.js +2 -0
  45. package/src/react/pages/Devices/deviceTypes/pdv.js +11 -0
  46. package/src/react/pages/Imports.js +3 -2
  47. package/src/react/pages/IntegrationConfigFields.js +126 -0
  48. package/src/react/pages/IntegrationConfigPage.js +122 -591
  49. package/src/react/pages/IntegrationConfigPage.utils.js +95 -0
  50. package/src/react/print/usePrintButtonController.js +9 -1
  51. package/src/react/services/Getnet/Checkout.js +81 -0
  52. package/src/react/services/Getnet/Getnet.js +77 -0
  53. package/src/react/services/paymentGatewayExecution.js +20 -0
  54. package/src/react/utils/bottomNavigationToolbar.js +161 -0
  55. package/src/react/utils/commercialDocumentOrders.js +18 -6
  56. package/src/react/utils/copyDeviceConfigs.js +198 -0
  57. package/src/react/utils/fileUrl.js +43 -0
  58. package/src/react/utils/importStatus.js +19 -10
  59. package/src/react/utils/logSettings.js +7 -7
  60. package/src/react/utils/maintenanceSettings.js +16 -0
  61. package/src/react/utils/paymentDevices.js +6 -0
  62. package/src/react/utils/printManagedPrinter.js +45 -0
  63. package/src/react/utils/printRouting.js +63 -0
  64. package/src/react/utils/printSpoolUtils.js +119 -0
  65. package/src/react/utils/printerDevices.js +368 -368
  66. package/src/react/utils/runtimeFooter.js +10 -19
  67. package/src/react/utils/shopFranchises.js +186 -65
  68. package/src/react/utils/systemErrorMessage.js +71 -67
  69. package/src/store/device_config/customActions.js +119 -30
  70. package/src/tests/browser/manager/bottom-navigation-role-toolbar.spec.js +353 -0
  71. package/src/tests/browser/manager/device-detail-alias-save.spec.js +345 -0
  72. package/src/tests/browser/manager/device-detail-copy-config.spec.js +365 -0
  73. package/src/tests/browser/manager/runtime-footer-marquee.spec.js +262 -0
  74. package/src/tests/browser/manager/theme-confirm-toast.spec.js +236 -0
  75. package/src/tests/react/components/RuntimeFooterMarqueeText.test.js +171 -0
  76. package/src/tests/react/components/UserAvatar.test.js +6 -2
  77. package/src/tests/react/components/themeAwareModals.test.mjs +60 -0
  78. package/src/tests/react/config/deviceConfigBootstrap.test.js +104 -66
  79. package/src/tests/react/pages/deviceDetailDelete.test.js +78 -0
  80. package/src/tests/react/pages/receitaFederalIntegrationContract.test.mjs +48 -0
  81. package/src/tests/react/utils/bottomNavigationToolbar.test.js +222 -0
  82. package/src/tests/react/utils/copyDeviceConfigs.test.mjs +127 -0
  83. package/src/tests/react/utils/fileUrl.test.js +38 -0
  84. package/src/tests/react/utils/importStatus.test.js +26 -9
  85. package/src/tests/react/utils/passwordPolicy.test.js +27 -0
  86. package/src/tests/react/utils/printRouting.test.mjs +86 -0
  87. package/src/tests/react/utils/printSpoolUtils.test.mjs +52 -0
  88. package/src/tests/react/utils/runtimeFooter.test.js +7 -0
  89. package/src/tests/react/utils/shopFranchises.test.js +121 -115
  90. package/src/tests/react/utils/systemErrorMessage.test.js +32 -59
@@ -1,6 +1,10 @@
1
+ import { getDeviceTypeLabel } from '@controleonline/ui-common/src/react/utils/printerDevices';
2
+
1
3
  const DEVICE_RUNTIME_FOOTER_TEXT_CONFIG_KEY = 'device-runtime-footer-text';
2
- const PRINTER_DEVICE_TYPES = new Set(['PRINT', 'PRINTER']);
3
- const IP_CAMERA_DEVICE_TYPE = 'IP_CAMERA';
4
+ const RUNTIME_FOOTER_HIDDEN_ROUTES = new Set(['PaylistPage']);
5
+
6
+ const shouldShowRuntimeFooter = currentRouteName =>
7
+ !RUNTIME_FOOTER_HIDDEN_ROUTES.has(String(currentRouteName || '').trim());
4
8
 
5
9
  const safeTrim = value => String(value || '').trim();
6
10
 
@@ -25,20 +29,6 @@ const normalizeComparableText = value =>
25
29
  .normalize('NFD')
26
30
  .replace(/[\u0300-\u036f]/g, '');
27
31
 
28
- const getRuntimeFooterDeviceTypeLabel = type => {
29
- const normalizedType = safeTrim(type).toUpperCase();
30
-
31
- if (PRINTER_DEVICE_TYPES.has(normalizedType)) {
32
- return 'Impressora';
33
- }
34
-
35
- if (normalizedType === IP_CAMERA_DEVICE_TYPE) {
36
- return 'Camera IP';
37
- }
38
-
39
- return normalizedType || 'DEVICE';
40
- };
41
-
42
32
  const getRuntimeFooterTranslation = (store, type, key) =>
43
33
  safeTrim(globalThis?.t?.getMessageFromBuckets?.(store, type, key));
44
34
 
@@ -268,7 +258,7 @@ const getRuntimeFooterOperationalTypeLabel = ({device, deviceConfig} = {}) => {
268
258
  });
269
259
 
270
260
  return operationalTypeInfo.value
271
- ? getRuntimeFooterDeviceTypeLabel(operationalTypeInfo.value)
261
+ ? getDeviceTypeLabel(operationalTypeInfo.value)
272
262
  : '';
273
263
  };
274
264
 
@@ -396,7 +386,7 @@ const getRuntimeFooterDebugInfo = ({device, appVersion, deviceConfig}) => {
396
386
  deviceConfig,
397
387
  });
398
388
  const operationalTypeLabel = operationalTypeInfo.value
399
- ? getRuntimeFooterDeviceTypeLabel(operationalTypeInfo.value)
389
+ ? getDeviceTypeLabel(operationalTypeInfo.value)
400
390
  : '';
401
391
  const operationModeLabel =
402
392
  operationalTypeLabel === 'PDV'
@@ -503,5 +493,6 @@ module.exports = {
503
493
  getRuntimeFooterWebIdentifierCandidates,
504
494
  getRuntimeFooterText,
505
495
  getRuntimeFooterWebHost,
506
- normalizeRuntimeFooterText,
496
+ normalizeRuntimeFooterText,
497
+ shouldShowRuntimeFooter,
507
498
  };
@@ -1,10 +1,10 @@
1
- import {api} from '@controleonline/ui-common/src/api';
2
- import {
3
- extractCollectionItems,
4
- toEntityIri,
5
- } from '@controleonline/ui-common/src/react/utils/commercialDocumentOrders';
1
+ import {api} from '@controleonline/ui-common/src/api';
2
+ import {
3
+ extractCollectionItems,
4
+ toEntityIri,
5
+ } from '@controleonline/ui-common/src/react/utils/commercialDocumentOrders';
6
6
  import {normalizeShopEntityId} from '@controleonline/ui-common/src/react/utils/shopConfig';
7
-
7
+
8
8
  export const SHOP_FRANCHISE_LINK_TYPE = 'franchisee';
9
9
  export const SHOP_FRANCHISE_PAGE_SIZE = 50;
10
10
 
@@ -150,39 +150,113 @@ export const filterShopFranchiseDirectory = ({
150
150
  })
151
151
  .filter(Boolean);
152
152
  };
153
-
154
- export const resolveFranchiseCompanyLabel = company =>
155
- String(company?.alias || company?.name || '').trim() ||
156
- `Franquia #${normalizeShopEntityId(company) || ''}`.trim();
157
-
158
- export const fetchShopFranchiseCompanies = async ({
153
+
154
+ export const resolveFranchiseCompanyLabel = company =>
155
+ String(company?.alias || company?.name || '').trim() ||
156
+ `Franquia #${normalizeShopEntityId(company) || ''}`.trim();
157
+
158
+ /**
159
+ * Resolve the franchise company entity from a people_link relative to the
160
+ * viewed franchisor/company id. DB rows may store the viewed company as
161
+ * `company` or as `people` (inverted association).
162
+ */
163
+ export const extractFranchiseCompanyFromLink = (link, viewerCompanyId) => {
164
+ const viewerId = normalizeShopEntityId(viewerCompanyId);
165
+ const companySide = link?.company;
166
+ const peopleSide = link?.people;
167
+ const companyId = normalizeShopEntityId(companySide);
168
+ const peopleId = normalizeShopEntityId(peopleSide);
169
+
170
+ if (viewerId && companyId === viewerId) {
171
+ return peopleSide;
172
+ }
173
+ if (viewerId && peopleId === viewerId) {
174
+ return companySide;
175
+ }
176
+ // Fallback: franchisee is typically the `people` side of linkType=franchisee.
177
+ return peopleSide || companySide || null;
178
+ };
179
+
180
+ const fetchFranchiseLinksPage = async ({
159
181
  companyId,
160
- search = '',
182
+ side,
161
183
  page = 1,
162
184
  itemsPerPage = SHOP_FRANCHISE_PAGE_SIZE,
163
- publicDirectory = false,
164
- } = {}) => {
185
+ search = '',
186
+ }) => {
187
+ const id = normalizeShopEntityId(companyId);
188
+ if (!id) {
189
+ return [];
190
+ }
191
+
165
192
  const params = {
166
193
  page: Math.max(1, Number(page) || 1),
167
194
  itemsPerPage: normalizeItemsPerPage(itemsPerPage),
195
+ linkType: SHOP_FRANCHISE_LINK_TYPE,
196
+ [side]: toEntityIri(id, 'people'),
168
197
  };
169
198
 
170
199
  if (String(search || '').trim()) {
171
200
  params.search = String(search).trim();
172
201
  }
173
202
 
174
- const response = publicDirectory
175
- ? await api.fetch('/shop/franchises', {params})
176
- : await api.fetch('people', {
177
- params: {
178
- ...params,
179
- 'link.company': toEntityIri(companyId, 'people'),
180
- 'link.linkType': SHOP_FRANCHISE_LINK_TYPE,
181
- },
203
+ const response = await api.fetch('people_links', {params});
204
+ return extractCollectionItems(response);
205
+ };
206
+
207
+ /**
208
+ * Authenticated franchise directory via people_links (both sides).
209
+ * GET /people?link.company=… does not reliably return franchisee PJs.
210
+ */
211
+ const fetchFranchiseCompaniesFromLinks = async ({
212
+ companyId,
213
+ search = '',
214
+ itemsPerPage = SHOP_FRANCHISE_PAGE_SIZE,
215
+ } = {}) => {
216
+ const viewerId = normalizeShopEntityId(companyId);
217
+ if (!viewerId) {
218
+ return [];
219
+ }
220
+
221
+ const pageSize = normalizeItemsPerPage(itemsPerPage);
222
+ const byId = new Map();
223
+
224
+ for (const side of ['company', 'people']) {
225
+ let page = 1;
226
+ while (true) {
227
+ const links = await fetchFranchiseLinksPage({
228
+ companyId: viewerId,
229
+ side,
230
+ page,
231
+ itemsPerPage: pageSize,
232
+ search,
233
+ });
234
+ const pageLinks = Array.isArray(links) ? links : [];
235
+
236
+ pageLinks.forEach(link => {
237
+ const franchise = extractFranchiseCompanyFromLink(link, viewerId);
238
+ const franchiseId = normalizeShopEntityId(franchise);
239
+ if (!franchiseId || franchiseId === viewerId) {
240
+ return;
241
+ }
242
+ if (!byId.has(franchiseId)) {
243
+ byId.set(
244
+ franchiseId,
245
+ typeof franchise === 'object' && franchise
246
+ ? franchise
247
+ : {id: franchiseId},
248
+ );
249
+ }
182
250
  });
183
- const items = extractCollectionItems(response);
184
251
 
185
- return items
252
+ if (pageLinks.length < pageSize) {
253
+ break;
254
+ }
255
+ page += 1;
256
+ }
257
+ }
258
+
259
+ return Array.from(byId.values())
186
260
  .map(normalizeFranchiseDirectoryItem)
187
261
  .sort((left, right) =>
188
262
  sortByLabel(
@@ -192,67 +266,113 @@ export const fetchShopFranchiseCompanies = async ({
192
266
  );
193
267
  };
194
268
 
195
- export const fetchAllShopFranchiseDirectory = async ({
269
+ export const fetchShopFranchiseCompanies = async ({
196
270
  companyId,
197
- publicDirectory = false,
198
271
  search = '',
272
+ page = 1,
199
273
  itemsPerPage = SHOP_FRANCHISE_PAGE_SIZE,
274
+ publicDirectory = false,
200
275
  } = {}) => {
201
- const normalizedItemsPerPage = normalizeItemsPerPage(itemsPerPage);
202
- const items = [];
203
- let page = 1;
204
-
205
- while (true) {
206
- const pageItems = await fetchShopFranchiseCompanies({
207
- companyId,
208
- publicDirectory,
209
- search,
210
- page,
211
- itemsPerPage: normalizedItemsPerPage,
212
- });
213
- const normalizedPageItems = Array.isArray(pageItems) ? pageItems : [];
276
+ const params = {
277
+ page: Math.max(1, Number(page) || 1),
278
+ itemsPerPage: normalizeItemsPerPage(itemsPerPage),
279
+ };
214
280
 
215
- items.push(...normalizedPageItems);
281
+ if (String(search || '').trim()) {
282
+ params.search = String(search).trim();
283
+ }
284
+
285
+ if (publicDirectory) {
286
+ const response = await api.fetch('/shop/franchises', {params});
287
+ const items = extractCollectionItems(response);
288
+ return items
289
+ .map(normalizeFranchiseDirectoryItem)
290
+ .sort((left, right) =>
291
+ sortByLabel(
292
+ resolveFranchiseCompanyLabel(left),
293
+ resolveFranchiseCompanyLabel(right),
294
+ ),
295
+ );
296
+ }
297
+
298
+ // Management path: people_links dual-side (company + people).
299
+ // Page param is ignored for dual aggregation; callers that need full list
300
+ // should use fetchAllShopFranchiseDirectory.
301
+ const all = await fetchFranchiseCompaniesFromLinks({
302
+ companyId,
303
+ search,
304
+ itemsPerPage,
305
+ });
306
+ const start = (Math.max(1, Number(page) || 1) - 1) * normalizeItemsPerPage(itemsPerPage);
307
+ return all.slice(start, start + normalizeItemsPerPage(itemsPerPage));
308
+ };
216
309
 
217
- if (normalizedPageItems.length < normalizedItemsPerPage) {
218
- break;
310
+ export const fetchAllShopFranchiseDirectory = async ({
311
+ companyId,
312
+ publicDirectory = false,
313
+ search = '',
314
+ itemsPerPage = SHOP_FRANCHISE_PAGE_SIZE,
315
+ } = {}) => {
316
+ if (publicDirectory) {
317
+ const normalizedItemsPerPage = normalizeItemsPerPage(itemsPerPage);
318
+ const items = [];
319
+ let page = 1;
320
+
321
+ while (true) {
322
+ const pageItems = await fetchShopFranchiseCompanies({
323
+ companyId,
324
+ publicDirectory: true,
325
+ search,
326
+ page,
327
+ itemsPerPage: normalizedItemsPerPage,
328
+ });
329
+ const normalizedPageItems = Array.isArray(pageItems) ? pageItems : [];
330
+ items.push(...normalizedPageItems);
331
+ if (normalizedPageItems.length < normalizedItemsPerPage) {
332
+ break;
333
+ }
334
+ page += 1;
219
335
  }
220
336
 
221
- page += 1;
337
+ return items
338
+ .map(normalizeFranchiseDirectoryItem)
339
+ .sort((left, right) =>
340
+ sortByLabel(
341
+ resolveFranchiseCompanyLabel(left),
342
+ resolveFranchiseCompanyLabel(right),
343
+ ),
344
+ );
222
345
  }
223
346
 
224
- return items
225
- .map(normalizeFranchiseDirectoryItem)
226
- .sort((left, right) =>
227
- sortByLabel(
228
- resolveFranchiseCompanyLabel(left),
229
- resolveFranchiseCompanyLabel(right),
230
- ),
231
- );
347
+ return fetchFranchiseCompaniesFromLinks({
348
+ companyId,
349
+ search,
350
+ itemsPerPage,
351
+ });
232
352
  };
233
353
 
234
354
  export const fetchShopFranchiseAddresses = async ({
235
355
  peopleId,
236
356
  search = '',
237
357
  } = {}) => {
238
- if (!peopleId) {
239
- return [];
240
- }
241
-
358
+ if (!peopleId) {
359
+ return [];
360
+ }
361
+
242
362
  const params = {
243
363
  people: toEntityIri(peopleId, 'people'),
244
364
  itemsPerPage: SHOP_FRANCHISE_PAGE_SIZE,
245
365
  page: 1,
246
366
  };
247
-
248
- if (String(search || '').trim()) {
249
- params.search = String(search).trim();
250
- }
251
-
252
- const response = await api.fetch('addresses', {params});
253
- return extractCollectionItems(response);
254
- };
255
-
367
+
368
+ if (String(search || '').trim()) {
369
+ params.search = String(search).trim();
370
+ }
371
+
372
+ const response = await api.fetch('addresses', {params});
373
+ return extractCollectionItems(response);
374
+ };
375
+
256
376
  export const fetchShopFranchiseDirectory = async ({
257
377
  companyId,
258
378
  publicDirectory = false,
@@ -270,4 +390,5 @@ export const fetchShopFranchiseDirectory = async ({
270
390
 
271
391
  return companies.map(normalizeFranchiseDirectoryItem);
272
392
  };
393
+
273
394
  // TODO(store-first): quando este arquivo for mexido, mover a leitura para stores, remover api.fetch e evitar repassar dados em objetos quando o store ja resolver isso.
@@ -1,67 +1,71 @@
1
- const normalizeText = value => String(value || '').trim()
2
-
3
- const resolveMessageList = items =>
4
- (Array.isArray(items) ? items : [])
5
- .map(item =>
6
- normalizeText(
7
- item?.['hydra:description'] ||
8
- item?.['hydra:title'] ||
9
- item?.message ||
10
- item?.title ||
11
- item?.detail ||
12
- item?.description ||
13
- item,
14
- ),
15
- )
16
- .filter(Boolean)
17
- .join('\n')
18
- .trim()
19
-
20
- export const resolveSystemErrorMessage = error => {
21
- if (error === undefined || error === null) {
22
- return ''
23
- }
24
-
25
- if (typeof error === 'string') {
26
- return error.trim()
27
- }
28
-
29
- if (Array.isArray(error)) {
30
- return resolveMessageList(error)
31
- }
32
-
33
- if (Array.isArray(error?.message)) {
34
- return resolveMessageList(error.message)
35
- }
36
-
37
- if (Array.isArray(error?.violations)) {
38
- return resolveMessageList(error.violations)
39
- }
40
-
41
- if (error?.response?.data) {
42
- const responseMessage = resolveSystemErrorMessage(error.response.data)
43
- if (responseMessage) {
44
- return responseMessage
45
- }
46
- }
47
-
48
- if (error?.body) {
49
- const bodyMessage = resolveSystemErrorMessage(error.body)
50
- if (bodyMessage) {
51
- return bodyMessage
52
- }
53
- }
54
-
55
- return normalizeText(
56
- error?.['hydra:description'] ||
57
- error?.['hydra:title'] ||
58
- error?.detail ||
59
- error?.description ||
60
- error?.errmsg ||
61
- error?.error ||
62
- error?.message ||
63
- error?.title,
64
- )
65
- }
66
-
67
- export default resolveSystemErrorMessage
1
+ import { mapPasswordErrorMessage } from './passwordPolicy'
2
+
3
+ const normalizeText = value => String(value || '').trim()
4
+
5
+ const resolveMessageList = items =>
6
+ (Array.isArray(items) ? items : [])
7
+ .map(item =>
8
+ normalizeText(
9
+ item?.['hydra:description'] ||
10
+ item?.['hydra:title'] ||
11
+ item?.message ||
12
+ item?.title ||
13
+ item?.detail ||
14
+ item?.description ||
15
+ item,
16
+ ),
17
+ )
18
+ .filter(Boolean)
19
+ .join('\n')
20
+ .trim()
21
+
22
+ export const resolveSystemErrorMessage = error => {
23
+ if (error === undefined || error === null) {
24
+ return ''
25
+ }
26
+
27
+ if (typeof error === 'string') {
28
+ return mapPasswordErrorMessage(error.trim())
29
+ }
30
+
31
+ if (Array.isArray(error)) {
32
+ return mapPasswordErrorMessage(resolveMessageList(error))
33
+ }
34
+
35
+ if (Array.isArray(error?.message)) {
36
+ return mapPasswordErrorMessage(resolveMessageList(error.message))
37
+ }
38
+
39
+ if (Array.isArray(error?.violations)) {
40
+ return mapPasswordErrorMessage(resolveMessageList(error.violations))
41
+ }
42
+
43
+ if (error?.response?.data) {
44
+ const responseMessage = resolveSystemErrorMessage(error.response.data)
45
+ if (responseMessage) {
46
+ return responseMessage
47
+ }
48
+ }
49
+
50
+ if (error?.body) {
51
+ const bodyMessage = resolveSystemErrorMessage(error.body)
52
+ if (bodyMessage) {
53
+ return bodyMessage
54
+ }
55
+ }
56
+
57
+ return mapPasswordErrorMessage(
58
+ normalizeText(
59
+ error?.['hydra:description'] ||
60
+ error?.['hydra:title'] ||
61
+ error?.detail ||
62
+ error?.description ||
63
+ error?.errmsg ||
64
+ error?.error ||
65
+ error?.message ||
66
+ error?.title,
67
+ ),
68
+ )
69
+ }
70
+
71
+ export default resolveSystemErrorMessage