@controleonline/ui-common 1.2.70 → 1.2.71

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 (61) hide show
  1. package/package.json +31 -28
  2. package/src/api/index.js +237 -37
  3. package/src/react/components/AddImportModal.js +1 -1
  4. package/src/react/components/AnimatedModal.js +171 -0
  5. package/src/react/components/AnimatedModal.styles.js +21 -0
  6. package/src/react/components/AppTypeSwitcher.js +164 -0
  7. package/src/react/components/AppTypeSwitcher.styles.js +109 -0
  8. package/src/react/components/BackgroundRuntimeBridge.js +5 -4
  9. package/src/react/components/BottomNavigationBar.js +86 -31
  10. package/src/react/components/BottomNavigationBar.styles.js +118 -110
  11. package/src/react/components/DefaultProvider.native.js +68 -66
  12. package/src/react/components/DefaultProvider.web.js +44 -42
  13. package/src/react/components/DeliveryPushBridge.native.js +2 -2
  14. package/src/react/components/DeviceAlertSoundService.js +3 -3
  15. package/src/react/components/KioskModeBridge.js +2 -2
  16. package/src/react/components/LauncherModeBridge.js +2 -2
  17. package/src/react/components/ManagerPushBridge.native.js +2 -2
  18. package/src/react/components/RemoteCheckoutService.js +28 -20
  19. package/src/react/components/RuntimeBottomNavigationBar.js +146 -119
  20. package/src/react/components/RuntimeInfoFooter.js +37 -9
  21. package/src/react/components/StateStore.js +258 -0
  22. package/src/react/components/WebsocketListener.native.js +11 -11
  23. package/src/react/config/deviceConfigBootstrap.js +66 -27
  24. package/src/react/css/orders.js +72 -0
  25. package/src/react/pages/SettingsPage/PaymentTypesByWalletTab.js +484 -454
  26. package/src/react/pages/SettingsPage/index.js +1266 -1167
  27. package/src/react/print/providers/local.js +1 -1
  28. package/src/react/router/publicRoutes.js +25 -0
  29. package/src/react/services/Cielo/Checkout.js +39 -0
  30. package/src/react/services/Cielo/Cielo.js +193 -0
  31. package/src/react/services/Cielo/Print.js +7 -0
  32. package/src/react/services/InfinitePay/Checkout.js +33 -0
  33. package/src/react/services/InfinitePay/InfinitePay.js +24 -0
  34. package/src/react/{utils → services}/paymentGatewayExecution.js +91 -91
  35. package/src/react/styles/global.js +115 -0
  36. package/src/react/utils/fileUrl.js +182 -16
  37. package/src/react/utils/menuNavigation.js +31 -0
  38. package/src/react/utils/orderIdentity.js +277 -0
  39. package/src/react/utils/remotePayment.js +115 -61
  40. package/src/react/utils/runtimeMenu.js +35 -3
  41. package/src/react/utils/shopConfig.js +50 -24
  42. package/src/react/utils/shopFranchises.js +56 -22
  43. package/src/react/utils/socketRuntimePipeline.js +14 -14
  44. package/src/store/configs/index.js +2 -2
  45. package/src/tests/react/api/loadSmokeIndex.test.js +75 -0
  46. package/src/tests/react/config/deviceConfigBootstrap.test.js +47 -0
  47. package/src/tests/react/print/localPrintProvider.test.js +1 -1
  48. package/src/tests/react/services/Cielo.test.js +190 -0
  49. package/src/tests/react/utils/fileUrl.test.js +62 -0
  50. package/src/tests/react/utils/importStatus.test.js +2 -2
  51. package/src/tests/react/utils/orderIdentity.test.js +139 -0
  52. package/src/tests/react/utils/remotePayment.test.js +62 -0
  53. package/src/tests/react/utils/runtimeFooter.test.js +4 -6
  54. package/src/tests/react/utils/runtimeLanguage.test.js +2 -2
  55. package/src/tests/react/utils/runtimeMenu.test.js +23 -3
  56. package/src/tests/react/utils/shopConfig.test.js +74 -0
  57. package/src/tests/react/utils/shopFranchises.test.js +79 -12
  58. package/src/tests/react/utils/translate.test.mjs +166 -102
  59. package/src/utils/formatter.js +18 -0
  60. package/src/utils/integrationConfigs.js +80 -79
  61. package/src/utils/translate.js +125 -30
@@ -1,11 +1,12 @@
1
- import { env as APP_ENV } from '@env';
2
- import { buildAssetUrl } from '@controleonline/../../src/styles/branding';
1
+ import {env as APP_ENV} from '@env';
2
+ import {buildAssetUrl} from '@controleonline/../../src/styles/branding';
3
3
  import {
4
4
  resolveAppDomain,
5
5
  resolveCompanyDomain,
6
6
  } from '@controleonline/ui-common/src/utils/appDomain';
7
7
 
8
8
  const IMAGE_PATH_PATTERN = /\.(?:avif|bmp|gif|ico|jpe?g|png|svg|webp)(?:\?|$)/i;
9
+ const FILE_DOWNLOAD_PATTERN = /(?:^|\/)files\/[^/?#]+\/download(?:[?#]|$)/i;
9
10
 
10
11
  const normalizeText = value => String(value || '').trim();
11
12
 
@@ -21,6 +22,84 @@ const unwrapFile = file => {
21
22
  return file;
22
23
  };
23
24
 
25
+ const isDownloadLikeUrl = value =>
26
+ FILE_DOWNLOAD_PATTERN.test(normalizeText(value));
27
+
28
+ const appendQueryParam = (url, key, value) => {
29
+ const normalizedUrl = normalizeText(url);
30
+ const normalizedKey = normalizeText(key);
31
+ const normalizedValue = normalizeText(value);
32
+
33
+ if (!normalizedUrl || !normalizedKey || !normalizedValue) {
34
+ return normalizedUrl;
35
+ }
36
+
37
+ const hashIndex = normalizedUrl.indexOf('#');
38
+ const hash = hashIndex >= 0 ? normalizedUrl.slice(hashIndex) : '';
39
+ const urlWithoutHash =
40
+ hashIndex >= 0 ? normalizedUrl.slice(0, hashIndex) : normalizedUrl;
41
+ const queryIndex = urlWithoutHash.indexOf('?');
42
+ const base =
43
+ queryIndex >= 0 ? urlWithoutHash.slice(0, queryIndex) : urlWithoutHash;
44
+ const query = queryIndex >= 0 ? urlWithoutHash.slice(queryIndex + 1) : '';
45
+ const params = query
46
+ ? query
47
+ .split('&')
48
+ .map(item => item.trim())
49
+ .filter(Boolean)
50
+ .filter(item => {
51
+ const [rawKey] = item.split('=');
52
+ return decodeURIComponent(rawKey || '') !== normalizedKey;
53
+ })
54
+ : [];
55
+
56
+ params.push(
57
+ `${encodeURIComponent(normalizedKey)}=${encodeURIComponent(normalizedValue)}`,
58
+ );
59
+
60
+ return `${base}?${params.join('&')}${hash}`;
61
+ };
62
+
63
+ const ensureAbsoluteUrl = url => {
64
+ const normalizedUrl = normalizeText(url);
65
+ if (!normalizedUrl) {
66
+ return '';
67
+ }
68
+
69
+ if (/^https?:\/\//i.test(normalizedUrl) || /^\/\//.test(normalizedUrl)) {
70
+ return buildAssetUrl(normalizedUrl) || normalizedUrl;
71
+ }
72
+
73
+ const apiEntryPoint = normalizeText(APP_ENV?.API_ENTRYPOINT).replace(
74
+ /\/$/,
75
+ '',
76
+ );
77
+ const normalizedPath = normalizedUrl.startsWith('/')
78
+ ? normalizedUrl
79
+ : `/${normalizedUrl}`;
80
+
81
+ return apiEntryPoint ? `${apiEntryPoint}${normalizedPath}` : normalizedPath;
82
+ };
83
+
84
+ const resolveDownloadHost = ({company = null, appDomain = ''} = {}) => {
85
+ const fallbackDomain =
86
+ normalizeText(appDomain) || resolveAppDomain(APP_ENV?.DOMAIN);
87
+ return resolveCompanyDomain(company, fallbackDomain);
88
+ };
89
+
90
+ const buildBackendDownloadUrl = (fileId, options = {}) => {
91
+ const normalizedId = String(fileId || '').trim();
92
+ if (!normalizedId) {
93
+ return '';
94
+ }
95
+
96
+ const host = resolveDownloadHost(options);
97
+ const relativeUrl = `/files/${normalizedId}/download`;
98
+ const absoluteUrl = ensureAbsoluteUrl(relativeUrl);
99
+
100
+ return host ? appendQueryParam(absoluteUrl, 'app-domain', host) : absoluteUrl;
101
+ };
102
+
24
103
  const resolveDirectFileUrl = file => {
25
104
  if (!file) {
26
105
  return '';
@@ -77,26 +156,113 @@ export const extractFileId = file => {
77
156
  return null;
78
157
  };
79
158
 
80
- // Centraliza a resolucao de imagem/arquivo do backend em uma unica regra.
81
- export const resolveFileImageUrl = (file, {company = null, appDomain = ''} = {}) => {
159
+ export const resolveDefaultFileSource = (
160
+ file,
161
+ {company = null, appDomain = '', headers = {}} = {},
162
+ ) => {
82
163
  const normalizedFile = unwrapFile(file);
83
- const directUrl = resolveDirectFileUrl(normalizedFile);
84
- if (directUrl) {
85
- return directUrl;
164
+ const host = resolveDownloadHost({company, appDomain});
165
+
166
+ if (!normalizedFile) {
167
+ return null;
86
168
  }
87
169
 
88
- const fileId = extractFileId(normalizedFile);
89
- if (!fileId) {
90
- return '';
170
+ if (typeof normalizedFile === 'number') {
171
+ const uri = buildBackendDownloadUrl(normalizedFile, {company, appDomain});
172
+
173
+ return uri
174
+ ? {
175
+ uri,
176
+ headers: host ? {...headers, 'app-domain': host} : {...headers},
177
+ }
178
+ : null;
179
+ }
180
+
181
+ if (typeof normalizedFile === 'string') {
182
+ const normalizedValue = normalizeText(normalizedFile);
183
+ if (!normalizedValue) {
184
+ return null;
185
+ }
186
+
187
+ if (isDownloadLikeUrl(normalizedValue)) {
188
+ const uri = appendQueryParam(
189
+ ensureAbsoluteUrl(normalizedValue),
190
+ 'app-domain',
191
+ host,
192
+ );
193
+
194
+ return uri
195
+ ? {
196
+ uri,
197
+ headers: host ? {...headers, 'app-domain': host} : {...headers},
198
+ }
199
+ : null;
200
+ }
201
+
202
+ const numericId = extractFileId(normalizedValue);
203
+ if (numericId && !IMAGE_PATH_PATTERN.test(normalizedValue)) {
204
+ const uri = buildBackendDownloadUrl(numericId, {company, appDomain});
205
+
206
+ return uri
207
+ ? {
208
+ uri,
209
+ headers: host ? {...headers, 'app-domain': host} : {...headers},
210
+ }
211
+ : null;
212
+ }
213
+
214
+ const directUrl = resolveDirectFileUrl(normalizedValue);
215
+
216
+ return directUrl
217
+ ? {
218
+ uri: directUrl,
219
+ headers: {...headers},
220
+ }
221
+ : null;
91
222
  }
92
223
 
93
- const fallbackDomain = normalizeText(appDomain) || resolveAppDomain(APP_ENV?.DOMAIN);
94
- const host = resolveCompanyDomain(company, fallbackDomain);
95
- const query = host ? `?app-domain=${encodeURIComponent(host)}` : '';
96
- const relativeUrl = `/files/${fileId}/download${query}`;
97
- const apiEntryPoint = normalizeText(APP_ENV?.API_ENTRYPOINT).replace(/\/$/, '');
224
+ if (typeof normalizedFile === 'object' && !Array.isArray(normalizedFile)) {
225
+ const sourceHeaders =
226
+ normalizedFile?.headers && typeof normalizedFile.headers === 'object'
227
+ ? normalizedFile.headers
228
+ : {};
229
+ const fileId = extractFileId(normalizedFile);
230
+ const directUrl = resolveDirectFileUrl(normalizedFile);
231
+ const isBackendDownload =
232
+ Boolean(fileId) ||
233
+ isDownloadLikeUrl(directUrl) ||
234
+ isDownloadLikeUrl(normalizedFile?.uri) ||
235
+ isDownloadLikeUrl(normalizedFile?.url) ||
236
+ isDownloadLikeUrl(normalizedFile?.path);
237
+ const uriBase =
238
+ directUrl ||
239
+ (fileId ? buildBackendDownloadUrl(fileId, {company, appDomain}) : '');
240
+
241
+ if (!uriBase) {
242
+ return null;
243
+ }
244
+
245
+ const uri = isBackendDownload
246
+ ? appendQueryParam(ensureAbsoluteUrl(uriBase), 'app-domain', host)
247
+ : uriBase;
98
248
 
99
- return apiEntryPoint ? `${apiEntryPoint}${relativeUrl}` : relativeUrl;
249
+ return {
250
+ uri,
251
+ headers:
252
+ isBackendDownload && host
253
+ ? {...sourceHeaders, ...headers, 'app-domain': host}
254
+ : {...sourceHeaders, ...headers},
255
+ };
256
+ }
257
+
258
+ return null;
100
259
  };
101
260
 
261
+ export const resolveDefaultFileUrl = (file, options = {}) =>
262
+ resolveDefaultFileSource(file, options)?.uri || '';
263
+
264
+ // Centraliza a resolucao de imagem/arquivo do backend em uma unica regra.
265
+ export const resolveFileImageUrl = (file, options = {}) =>
266
+ resolveDefaultFileUrl(file, options);
267
+
102
268
  export const resolveFileDownloadUrl = resolveFileImageUrl;
@@ -0,0 +1,31 @@
1
+ const LEGACY_ROUTE_ALIASES = {
2
+ displayList: 'DisplayList',
3
+ displayDetails: 'DisplayDetails',
4
+ };
5
+
6
+ export const resolveMenuRouteName = routeName => {
7
+ const normalizedRouteName = String(routeName || '').trim();
8
+
9
+ if (!normalizedRouteName) {
10
+ return '';
11
+ }
12
+
13
+ return LEGACY_ROUTE_ALIASES[normalizedRouteName] || normalizedRouteName;
14
+ };
15
+
16
+ export const resolveMenuRouteParams = routeParams => {
17
+ if (
18
+ routeParams &&
19
+ typeof routeParams === 'object' &&
20
+ !Array.isArray(routeParams)
21
+ ) {
22
+ return routeParams;
23
+ }
24
+
25
+ return {};
26
+ };
27
+
28
+ export default {
29
+ resolveMenuRouteName,
30
+ resolveMenuRouteParams,
31
+ };
@@ -0,0 +1,277 @@
1
+ /*
2
+ * Regra de negocio: a identidade operacional nao pode inventar codigo de
3
+ * marketplace. O resolvedor usa apenas campos canonicos do pedido.
4
+ */
5
+
6
+ const normalizeText = value => String(value ?? '').trim();
7
+
8
+ const normalizeKey = value =>
9
+ normalizeText(value)
10
+ .normalize('NFD')
11
+ .replace(/[\u0300-\u036f]/g, '')
12
+ .toLowerCase();
13
+
14
+ const formatOrderCode = value => {
15
+ const normalized = normalizeText(value);
16
+ return normalized ? `#${normalized}` : '';
17
+ };
18
+
19
+ const isObject = value =>
20
+ value !== null && typeof value === 'object' && !Array.isArray(value);
21
+
22
+ const parseJsonObject = value => {
23
+ if (isObject(value)) {
24
+ return value;
25
+ }
26
+
27
+ if (typeof value !== 'string') {
28
+ return {};
29
+ }
30
+
31
+ try {
32
+ const parsed = JSON.parse(value);
33
+ if (typeof parsed === 'string') {
34
+ return parseJsonObject(parsed);
35
+ }
36
+
37
+ return isObject(parsed) ? parsed : {};
38
+ } catch {
39
+ return {};
40
+ }
41
+ };
42
+
43
+ const getExtraDataList = order => {
44
+ if (Array.isArray(order?.extraData)) {
45
+ return order.extraData;
46
+ }
47
+
48
+ if (Array.isArray(order?.extra_data)) {
49
+ return order.extra_data;
50
+ }
51
+
52
+ return [];
53
+ };
54
+
55
+ const getExtraDataMap = order =>
56
+ getExtraDataList(order).reduce((currentMap, extraData) => {
57
+ const context = normalizeKey(
58
+ extraData?.extra_fields?.context || extraData?.extraFields?.context,
59
+ );
60
+ const name = normalizeText(
61
+ extraData?.extra_fields?.name || extraData?.extraFields?.name,
62
+ );
63
+ const value = normalizeText(extraData?.value);
64
+
65
+ if (!context || !name || !value) {
66
+ return currentMap;
67
+ }
68
+
69
+ if (!currentMap[context]) {
70
+ currentMap[context] = {};
71
+ }
72
+
73
+ currentMap[context][name] = value;
74
+ return currentMap;
75
+ }, {});
76
+
77
+ const decodeOrderOtherInformations = order =>
78
+ parseJsonObject(
79
+ order?.otherInformations ??
80
+ order?.other_information ??
81
+ order?.otherInformation ??
82
+ order?.otherInformationsJson ??
83
+ order?.other_information_json,
84
+ );
85
+
86
+ const getContextFromOtherInformations = (order, context) => {
87
+ const otherInformations = decodeOrderOtherInformations(order);
88
+ const matchedKey = Object.keys(otherInformations).find(
89
+ key => normalizeKey(key) === normalizeKey(context),
90
+ );
91
+
92
+ if (!matchedKey) {
93
+ return {};
94
+ }
95
+
96
+ const matchedValue = otherInformations?.[matchedKey];
97
+ return isObject(matchedValue) ? matchedValue : parseJsonObject(matchedValue);
98
+ };
99
+
100
+ const findNestedFieldValue = (source, fieldName) => {
101
+ if (Array.isArray(source)) {
102
+ for (const entry of source) {
103
+ const nestedValue = findNestedFieldValue(entry, fieldName);
104
+ if (nestedValue) {
105
+ return nestedValue;
106
+ }
107
+ }
108
+
109
+ return '';
110
+ }
111
+
112
+ if (!isObject(source)) {
113
+ return '';
114
+ }
115
+
116
+ const normalizedFieldName = normalizeKey(fieldName);
117
+ const matchedKey = Object.keys(source).find(
118
+ key => normalizeKey(key) === normalizedFieldName,
119
+ );
120
+
121
+ if (matchedKey) {
122
+ const directValue = normalizeText(source?.[matchedKey]);
123
+ if (directValue) {
124
+ return directValue;
125
+ }
126
+ }
127
+
128
+ for (const value of Object.values(source)) {
129
+ const nestedValue = findNestedFieldValue(value, fieldName);
130
+ if (nestedValue) {
131
+ return nestedValue;
132
+ }
133
+ }
134
+
135
+ return '';
136
+ };
137
+
138
+ const getMarketplaceField = (order, contexts, fieldName) => {
139
+ const extraDataMap = getExtraDataMap(order);
140
+
141
+ for (const context of contexts) {
142
+ const value = normalizeText(extraDataMap?.[normalizeKey(context)]?.[fieldName]);
143
+ if (value) {
144
+ return value;
145
+ }
146
+ }
147
+
148
+ for (const context of contexts) {
149
+ const value = findNestedFieldValue(
150
+ getContextFromOtherInformations(order, context),
151
+ fieldName,
152
+ );
153
+ if (value) {
154
+ return value;
155
+ }
156
+ }
157
+
158
+ return '';
159
+ };
160
+
161
+ const resolveMarketplaceLabel = order => {
162
+ const normalizedApp = normalizeText(order?.app).toLowerCase();
163
+
164
+ if (normalizedApp.includes('99')) {
165
+ return '99';
166
+ }
167
+
168
+ if (normalizedApp.includes('ifood')) {
169
+ return 'IFOOD';
170
+ }
171
+
172
+ return normalizeText(order?.app).toUpperCase();
173
+ };
174
+
175
+ const resolveMarketplaceOrderCodeFromOrder = order => {
176
+ const normalizedApp = normalizeText(order?.app).toLowerCase();
177
+
178
+ if (normalizedApp.includes('99')) {
179
+ return getMarketplaceField(order, ['Food99', '99Food', '99food'], 'code');
180
+ }
181
+
182
+ if (normalizedApp.includes('ifood')) {
183
+ return getMarketplaceField(order, ['iFood', 'IFood', 'ifood'], 'code');
184
+ }
185
+
186
+ return '';
187
+ };
188
+
189
+ const resolveMarketplaceOrderCodeFromSummary = remoteOrderSummary => {
190
+ if (!remoteOrderSummary || typeof remoteOrderSummary !== 'object') {
191
+ return '';
192
+ }
193
+
194
+ return normalizeText(
195
+ remoteOrderSummary?.identifiers?.code ||
196
+ remoteOrderSummary?.identifiers?.externalId ||
197
+ remoteOrderSummary?.code ||
198
+ remoteOrderSummary?.externalId ||
199
+ remoteOrderSummary?.external_code,
200
+ );
201
+ };
202
+
203
+ const resolvePosExternalCode = order =>
204
+ normalizeText(
205
+ normalizeText(order?.app).toUpperCase() === 'POS'
206
+ ? order?.externalCode || order?.external_code || order?.externalId
207
+ : '',
208
+ );
209
+
210
+ const resolvePosExternalLabel = () =>
211
+ global.t?.t('orders', 'title', 'table') || 'Table';
212
+
213
+ export const resolveMarketplaceAppLabel = order => resolveMarketplaceLabel(order);
214
+
215
+ export const resolveMarketplaceOrderCode = (
216
+ order,
217
+ remoteOrderSummary = null,
218
+ ) =>
219
+ resolveMarketplaceOrderCodeFromSummary(remoteOrderSummary) ||
220
+ resolveMarketplaceOrderCodeFromOrder(order);
221
+
222
+ export const resolveOrderIdentityRemoteSummary = (
223
+ order,
224
+ remoteOrderSummary = null,
225
+ ) => remoteOrderSummary || null;
226
+
227
+ export const resolveOrderIdentity = (order, remoteOrderSummary = null) => {
228
+ const effectiveRemoteOrderSummary = resolveOrderIdentityRemoteSummary(
229
+ order,
230
+ remoteOrderSummary,
231
+ );
232
+ const internalId = normalizeText(order?.id);
233
+ const marketplaceLabel = resolveMarketplaceAppLabel(order);
234
+ const marketplaceOrderCode = resolveMarketplaceOrderCode(
235
+ order,
236
+ effectiveRemoteOrderSummary,
237
+ );
238
+ const posExternalCode = resolvePosExternalCode(order);
239
+ const hasMarketplaceReference = !!marketplaceLabel && !!marketplaceOrderCode;
240
+
241
+ if (posExternalCode) {
242
+ return {
243
+ internalId,
244
+ externalId: posExternalCode,
245
+ externalLabel: '',
246
+ hasMarketplaceReference: false,
247
+ primaryText: [resolvePosExternalLabel(), formatOrderCode(posExternalCode)]
248
+ .filter(Boolean)
249
+ .join(' '),
250
+ secondaryText:
251
+ normalizeText(posExternalCode) === internalId
252
+ ? ''
253
+ : formatOrderCode(internalId),
254
+ };
255
+ }
256
+
257
+ if (hasMarketplaceReference) {
258
+ return {
259
+ internalId,
260
+ externalId: marketplaceOrderCode,
261
+ externalLabel: marketplaceLabel,
262
+ hasMarketplaceReference: true,
263
+ primaryText: formatOrderCode(marketplaceOrderCode),
264
+ secondaryText: formatOrderCode(internalId),
265
+ };
266
+ }
267
+
268
+ return {
269
+ internalId,
270
+ externalId: '',
271
+ externalLabel: '',
272
+ hasMarketplaceReference: false,
273
+ primaryText:
274
+ formatOrderCode(internalId) || global.t?.t('orders', 'title', 'order'),
275
+ secondaryText: '',
276
+ };
277
+ };