@controleonline/ui-common 1.2.79 → 1.2.81

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 (91) 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 +40 -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 +493 -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 +484 -0
  43. package/src/react/pages/Devices/detail/useDeviceDetailStateB.js +71 -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/passwordPolicy.js +73 -0
  62. package/src/react/utils/paymentDevices.js +6 -0
  63. package/src/react/utils/printManagedPrinter.js +45 -0
  64. package/src/react/utils/printRouting.js +63 -0
  65. package/src/react/utils/printSpoolUtils.js +119 -0
  66. package/src/react/utils/printerDevices.js +368 -368
  67. package/src/react/utils/runtimeFooter.js +6 -1
  68. package/src/react/utils/shopFranchises.js +186 -65
  69. package/src/react/utils/systemErrorMessage.js +71 -67
  70. package/src/store/device_config/customActions.js +119 -30
  71. package/src/tests/browser/manager/bottom-navigation-role-toolbar.spec.js +353 -0
  72. package/src/tests/browser/manager/device-detail-alias-save.spec.js +345 -0
  73. package/src/tests/browser/manager/device-detail-copy-config.spec.js +365 -0
  74. package/src/tests/browser/manager/runtime-footer-marquee.spec.js +262 -0
  75. package/src/tests/browser/manager/theme-confirm-toast.spec.js +236 -0
  76. package/src/tests/react/components/RuntimeFooterMarqueeText.test.js +171 -0
  77. package/src/tests/react/components/UserAvatar.test.js +6 -2
  78. package/src/tests/react/components/themeAwareModals.test.mjs +60 -0
  79. package/src/tests/react/config/deviceConfigBootstrap.test.js +104 -66
  80. package/src/tests/react/pages/deviceDetailDelete.test.js +78 -0
  81. package/src/tests/react/pages/receitaFederalIntegrationContract.test.mjs +48 -0
  82. package/src/tests/react/utils/bottomNavigationToolbar.test.js +222 -0
  83. package/src/tests/react/utils/copyDeviceConfigs.test.mjs +127 -0
  84. package/src/tests/react/utils/fileUrl.test.js +38 -0
  85. package/src/tests/react/utils/importStatus.test.js +27 -10
  86. package/src/tests/react/utils/passwordPolicy.test.js +27 -0
  87. package/src/tests/react/utils/printRouting.test.mjs +86 -0
  88. package/src/tests/react/utils/printSpoolUtils.test.mjs +52 -0
  89. package/src/tests/react/utils/runtimeFooter.test.js +10 -8
  90. package/src/tests/react/utils/shopFranchises.test.js +121 -115
  91. package/src/tests/react/utils/systemErrorMessage.test.js +32 -59
@@ -5,6 +5,15 @@ import {getGravatarUrl, getUserInitials} from '../utils/userAvatar';
5
5
 
6
6
  const normalizeUrl = value => String(value || '').trim();
7
7
 
8
+ const isGravatarUrl = uri =>
9
+ /gravatar\.com\/avatar\//i.test(String(uri || ''));
10
+
11
+ const isBackendDownloadUrl = uri =>
12
+ /\/files\/[^/?#]+\/download/i.test(String(uri || ''));
13
+
14
+ /** Session cache: gravatar URL → true (ok) | false (missing). Avoids repeat probes. */
15
+ const gravatarProbeCache = new Map();
16
+
8
17
  const readSessionToken = () => {
9
18
  if (typeof localStorage === 'undefined' || !localStorage?.getItem) {
10
19
  return '';
@@ -18,51 +27,119 @@ const readSessionToken = () => {
18
27
  }
19
28
  };
20
29
 
30
+ /**
31
+ * Probe Gravatar (d=404) via fetch BEFORE exposing URL to <Image>.
32
+ * Browser "Failed to load resource" for <img> 404 is avoided; XHR 404 is
33
+ * expected once per unique hash and cached for the session.
34
+ */
35
+ const probeGravatar = async url => {
36
+ if (gravatarProbeCache.has(url)) {
37
+ return gravatarProbeCache.get(url);
38
+ }
39
+
40
+ try {
41
+ let response = await fetch(url, {
42
+ method: 'HEAD',
43
+ mode: 'cors',
44
+ cache: 'force-cache',
45
+ });
46
+ if (response.status === 405 || response.status === 501) {
47
+ response = await fetch(url, {
48
+ method: 'GET',
49
+ mode: 'cors',
50
+ cache: 'force-cache',
51
+ });
52
+ }
53
+ const ok = response.ok;
54
+ gravatarProbeCache.set(url, ok);
55
+ return ok;
56
+ } catch {
57
+ gravatarProbeCache.set(url, false);
58
+ return false;
59
+ }
60
+ };
61
+
62
+ /**
63
+ * Resolve display URI.
64
+ * - Backend /files/.../download on web: authenticated blob fetch.
65
+ * - Gravatar (d=404) on web: probe first; never set <img src> on expected 404.
66
+ * - Other URLs / native: pass through (native Image onError handles 404).
67
+ */
21
68
  const useDisplayUri = uri => {
22
- const [displayUri, setDisplayUri] = useState(normalizeUrl(uri));
69
+ const [displayUri, setDisplayUri] = useState('');
70
+ const [resolved, setResolved] = useState(false);
23
71
 
24
72
  useEffect(() => {
25
73
  let cancelled = false;
26
74
  let objectUrl = '';
27
75
  const next = normalizeUrl(uri);
28
76
 
77
+ setResolved(false);
78
+ setDisplayUri('');
79
+
29
80
  const run = async () => {
30
81
  if (!next) {
31
- setDisplayUri('');
82
+ if (!cancelled) {
83
+ setDisplayUri('');
84
+ setResolved(true);
85
+ }
32
86
  return;
33
87
  }
34
88
 
35
- const isBackendDownload = /\/files\/[^/?#]+\/download/i.test(next);
36
89
  const token = readSessionToken();
37
90
 
38
- if (!isBackendDownload || Platform.OS !== 'web' || !token) {
39
- setDisplayUri(next);
91
+ // Authenticated backend download on web
92
+ if (isBackendDownloadUrl(next) && Platform.OS === 'web' && token) {
93
+ try {
94
+ const headers = {
95
+ Accept: '*/*',
96
+ 'API-TOKEN': token,
97
+ };
98
+ const host =
99
+ normalizeUrl(APP_ENV?.DOMAIN) ||
100
+ (typeof location !== 'undefined' ? location.host : '');
101
+ if (host) {
102
+ headers['App-Domain'] = host;
103
+ }
104
+
105
+ const response = await fetch(next, {method: 'GET', headers});
106
+ if (!response.ok) {
107
+ if (!cancelled) {
108
+ setDisplayUri('');
109
+ setResolved(true);
110
+ }
111
+ return;
112
+ }
113
+
114
+ const blob = await response.blob();
115
+ objectUrl = URL.createObjectURL(blob);
116
+ if (!cancelled) {
117
+ setDisplayUri(objectUrl);
118
+ setResolved(true);
119
+ }
120
+ } catch {
121
+ if (!cancelled) {
122
+ setDisplayUri('');
123
+ setResolved(true);
124
+ }
125
+ }
40
126
  return;
41
127
  }
42
128
 
43
- try {
44
- const headers = {
45
- Accept: '*/*',
46
- 'API-TOKEN': token,
47
- };
48
- const host =
49
- normalizeUrl(APP_ENV?.DOMAIN) ||
50
- (typeof location !== 'undefined' ? location.host : '');
51
- if (host) {
52
- headers['App-Domain'] = host;
53
- }
54
-
55
- const response = await fetch(next, {method: 'GET', headers});
56
- if (!response.ok) {
57
- if (!cancelled) setDisplayUri(next);
58
- return;
129
+ // Gravatar d=404: probe on web so <img> never receives a known-missing URL
130
+ if (isGravatarUrl(next) && Platform.OS === 'web') {
131
+ const ok = await probeGravatar(next);
132
+ if (!cancelled) {
133
+ setDisplayUri(ok ? next : '');
134
+ setResolved(true);
59
135
  }
136
+ return;
137
+ }
60
138
 
61
- const blob = await response.blob();
62
- objectUrl = URL.createObjectURL(blob);
63
- if (!cancelled) setDisplayUri(objectUrl);
64
- } catch {
65
- if (!cancelled) setDisplayUri(next);
139
+ // Default: use as-is (native, or non-special web URLs)
140
+ if (!cancelled) {
141
+ setDisplayUri(next);
142
+ setResolved(true);
66
143
  }
67
144
  };
68
145
 
@@ -74,7 +151,7 @@ const useDisplayUri = uri => {
74
151
  };
75
152
  }, [uri]);
76
153
 
77
- return displayUri;
154
+ return {displayUri, resolved};
78
155
  };
79
156
 
80
157
  const UserAvatar = ({
@@ -104,7 +181,22 @@ const UserAvatar = ({
104
181
  }, [sources]);
105
182
 
106
183
  const currentSource = sources[sourceIndex];
107
- const displayUri = useDisplayUri(currentSource);
184
+ const {displayUri, resolved} = useDisplayUri(currentSource);
185
+
186
+ // After probe resolves empty for gravatar/backend → try next source or initials
187
+ useEffect(() => {
188
+ if (!currentSource || !resolved || displayUri) {
189
+ return;
190
+ }
191
+ if (
192
+ isGravatarUrl(currentSource) ||
193
+ isBackendDownloadUrl(currentSource)
194
+ ) {
195
+ setSourceIndex(index =>
196
+ index + 1 < sources.length ? index + 1 : index,
197
+ );
198
+ }
199
+ }, [currentSource, displayUri, resolved, sources.length]);
108
200
 
109
201
  const containerStyle = [
110
202
  styles.container,