@controleonline/ui-common 1.2.90 → 1.2.91

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.
@@ -0,0 +1,336 @@
1
+ import {useEffect} from 'react';
2
+
3
+ // Extracted bootstrap effects retain their dependency arrays and invocation order.
4
+
5
+ export function useWebTranslationRefresh({setTranslateVersion}) {
6
+ useEffect(() => {
7
+ global.refreshTranslationsUI = () => {
8
+ setTranslateVersion(version => version + 1);
9
+ };
10
+
11
+ return () => {
12
+ if (global.refreshTranslationsUI) {
13
+ delete global.refreshTranslationsUI;
14
+ }
15
+ };
16
+ }, []);
17
+ }
18
+
19
+ export function useWebTranslationReset({isLogged, isPublicRouteActive, setTranslateReady, setActiveTranslateBootstrapKey, translateBootstrapKeyRef, translateActions}) {
20
+ useEffect(() => {
21
+ if (isLogged && !isPublicRouteActive) {
22
+ return;
23
+ }
24
+
25
+ setTranslateReady(true);
26
+ setActiveTranslateBootstrapKey('');
27
+ translateBootstrapKeyRef.current = '';
28
+ translateActions.setMessages({});
29
+ translateActions.setPendingMessages?.({});
30
+
31
+ if (global.t) {
32
+ delete global.t;
33
+ }
34
+ }, [isLogged, isPublicRouteActive, translateActions]);
35
+ }
36
+
37
+ export function useWebDeviceConfigReset({deviceConfigPeopleIri, lastDeviceConfigPeopleIriRef, setDeviceConfigFetched, setDeviceRuntimeConfigSynced, deviceConfigsActions}) {
38
+ useEffect(() => {
39
+ if (
40
+ !deviceConfigPeopleIri ||
41
+ lastDeviceConfigPeopleIriRef.current === deviceConfigPeopleIri
42
+ ) {
43
+ return;
44
+ }
45
+
46
+ lastDeviceConfigPeopleIriRef.current = deviceConfigPeopleIri;
47
+ setDeviceConfigFetched(false);
48
+ setDeviceRuntimeConfigSynced(false);
49
+ deviceConfigsActions.setItem({});
50
+ }, [deviceConfigPeopleIri, deviceConfigsActions]);
51
+ }
52
+
53
+ export function useWebRuntimeIp({isLogged, api, getRuntimeIpFromResponse, setWebRuntimeIp, user}) {
54
+ useEffect(() => {
55
+ if (!isLogged) {
56
+ return;
57
+ }
58
+
59
+ let cancelled = false;
60
+
61
+ api
62
+ .fetch('runtime/ip', {
63
+ method: 'GET',
64
+ })
65
+ .then(response => {
66
+ const nextRuntimeIp = getRuntimeIpFromResponse(response);
67
+ if (!nextRuntimeIp || cancelled) {
68
+ return;
69
+ }
70
+
71
+ setWebRuntimeIp(currentIp =>
72
+ currentIp === nextRuntimeIp ? currentIp : nextRuntimeIp,
73
+ );
74
+ })
75
+ .catch(() => {});
76
+
77
+ return () => {
78
+ cancelled = true;
79
+ };
80
+ }, [isLogged, user?.id]);
81
+ }
82
+
83
+ export function useWebWebDeviceIdentity({isLogged, isShopClientApp, buildWebDevice, lastWebDeviceSyncRef, setDevice, deviceActions, packageVersion, user, webRuntimeIp}) {
84
+ useEffect(() => {
85
+ if (!isLogged && !isShopClientApp) {
86
+ return;
87
+ }
88
+
89
+ const nextDevice = buildWebDevice();
90
+ if (!nextDevice) {
91
+ return;
92
+ }
93
+
94
+ const signature = JSON.stringify({
95
+ id: nextDevice.id,
96
+ appVersion: nextDevice.appVersion,
97
+ appName: nextDevice.appName,
98
+ type: nextDevice.type,
99
+ metadata: nextDevice.metadata || {},
100
+ externalIp: nextDevice.externalIp || null,
101
+ });
102
+
103
+ if (lastWebDeviceSyncRef.current === signature) {
104
+ return;
105
+ }
106
+
107
+ lastWebDeviceSyncRef.current = signature;
108
+ setDevice(nextDevice);
109
+ localStorage.setItem('device', JSON.stringify(nextDevice));
110
+ deviceActions.setItem(nextDevice);
111
+ localStorage.removeItem('master-device');
112
+ }, [deviceActions, isLogged, isShopClientApp, packageVersion, user?.id, webRuntimeIp]);
113
+ }
114
+
115
+ export function useWebMainCompany({isShopClientApp, device, peopleActions}) {
116
+ useEffect(() => {
117
+ if (isShopClientApp || device?.id) {
118
+ peopleActions.mainCompany().catch(() => {});
119
+ }
120
+ }, [device?.id, isShopClientApp, peopleActions]);
121
+ }
122
+
123
+ export function useWebDeviceRegistration({isLogged, device, currentCompany, canAdministerCompany, mainCompany, user, deviceActions, buildDeviceRegistrationPayload, app_type, hasDeviceRecordChanges, runtimeDeviceType, setDevice}) {
124
+ useEffect(() => {
125
+ if (!isLogged || !device?.id) {
126
+ return;
127
+ }
128
+
129
+ // Device registration changes tenant context and is not valid without an
130
+ // active company or administrative authority for that company.
131
+ if (
132
+ !currentCompany?.id ||
133
+ !canAdministerCompany({company: currentCompany, mainCompany, user})
134
+ ) {
135
+ return;
136
+ }
137
+
138
+ let cancelled = false;
139
+
140
+ const syncDeviceRegistration = async () => {
141
+ const items = await deviceActions.getItems({
142
+ device: device.id,
143
+ });
144
+
145
+ if (cancelled) {
146
+ return;
147
+ }
148
+
149
+ const existingDevice =
150
+ Array.isArray(items) && items.length > 0 ? items[0] : null;
151
+ const nextDevice = buildDeviceRegistrationPayload({
152
+ deviceInfo: device,
153
+ appType: app_type,
154
+ existingDevice,
155
+ });
156
+
157
+ if (!hasDeviceRecordChanges({existingDevice, nextDevice})) {
158
+ const nextLocalDevice = {
159
+ ...device,
160
+ entityId: existingDevice?.id || device?.entityId || null,
161
+ entityIri:
162
+ existingDevice?.['@id'] ||
163
+ device?.entityIri ||
164
+ (existingDevice?.id ? `/devices/${existingDevice.id}` : null),
165
+ alias: existingDevice?.alias || nextDevice.alias,
166
+ type: device?.type || runtimeDeviceType,
167
+ metadata: existingDevice?.metadata || nextDevice.metadata,
168
+ };
169
+
170
+ if (JSON.stringify(nextLocalDevice) !== JSON.stringify(device)) {
171
+ setDevice(nextLocalDevice);
172
+ localStorage.setItem('device', JSON.stringify(nextLocalDevice));
173
+ deviceActions.setItem(nextLocalDevice);
174
+ }
175
+ return;
176
+ }
177
+
178
+ const savedDevice = await deviceActions.save(nextDevice);
179
+ if (cancelled || !savedDevice) {
180
+ return;
181
+ }
182
+
183
+ const nextLocalDevice = {
184
+ ...device,
185
+ entityId: savedDevice?.id || device?.entityId || null,
186
+ entityIri:
187
+ savedDevice?.['@id'] ||
188
+ device?.entityIri ||
189
+ (savedDevice?.id ? `/devices/${savedDevice.id}` : null),
190
+ alias: savedDevice.alias || nextDevice.alias,
191
+ type: device?.type || runtimeDeviceType,
192
+ metadata: savedDevice.metadata || nextDevice.metadata,
193
+ };
194
+
195
+ if (JSON.stringify(nextLocalDevice) !== JSON.stringify(device)) {
196
+ setDevice(nextLocalDevice);
197
+ localStorage.setItem('device', JSON.stringify(nextLocalDevice));
198
+ deviceActions.setItem(nextLocalDevice);
199
+ }
200
+ };
201
+
202
+ syncDeviceRegistration().catch(() => {});
203
+
204
+ return () => {
205
+ cancelled = true;
206
+ };
207
+ }, [
208
+ device?.appVersion,
209
+ device?.batteryLevel,
210
+ device?.buildNumber,
211
+ device?.deviceType,
212
+ device?.externalIp,
213
+ device?.id,
214
+ device?.manufacturer,
215
+ device?.model,
216
+ device?.systemVersion,
217
+ isLogged,
218
+ currentCompany,
219
+ mainCompany,
220
+ runtimeDeviceType,
221
+ user,
222
+ ]);
223
+ }
224
+
225
+ export function useWebPrinters({isShopClientApp, isLogged, currentCompany, printerActions}) {
226
+ useEffect(() => {
227
+ if (isShopClientApp || !isLogged || !currentCompany?.id) {
228
+ return;
229
+ }
230
+
231
+ printerActions
232
+ .ensureCompanyPrintersLoaded({people: currentCompany.id})
233
+ .catch(() => {});
234
+ }, [currentCompany?.id, isLogged, isShopClientApp, printerActions]);
235
+ }
236
+
237
+ export function useWebPaymentTypes({isShopClientApp, companyConfigs, currentCompany, device_config, walletPaymentTypeRequestRef, walletPaymentTypeLoadedKeyRef, paymentTypeActions, mainConfigsDiscovered, resolveDevicePaymentTypeIds, api, selectPosWalletPaymentTypes, getPaymentGateway}) {
238
+ useEffect(() => {
239
+ const paymentConfigSource = isShopClientApp
240
+ ? Object.keys(companyConfigs || {}).length > 0
241
+ ? companyConfigs
242
+ : currentCompany?.configs
243
+ : device_config?.configs;
244
+
245
+ if (!currentCompany?.id || !paymentConfigSource) {
246
+ walletPaymentTypeRequestRef.current = {key: '', promise: null};
247
+ walletPaymentTypeLoadedKeyRef.current = '';
248
+ paymentTypeActions.setItems([]);
249
+ return;
250
+ }
251
+
252
+ if (!isShopClientApp && !mainConfigsDiscovered) {
253
+ return;
254
+ }
255
+
256
+ const paymentTypeSignature = resolveDevicePaymentTypeIds(paymentConfigSource)
257
+ .sort()
258
+ .join(',');
259
+ const requestKey = `${currentCompany.id}:${paymentTypeSignature}`;
260
+
261
+ if (walletPaymentTypeLoadedKeyRef.current === requestKey) {
262
+ return;
263
+ }
264
+
265
+ const existingRequestPromise =
266
+ walletPaymentTypeRequestRef.current.key === requestKey &&
267
+ walletPaymentTypeRequestRef.current.promise
268
+ ? walletPaymentTypeRequestRef.current.promise
269
+ : null;
270
+
271
+ let isMounted = true;
272
+
273
+ const requestPromise =
274
+ existingRequestPromise ||
275
+ api.fetch('wallet_payment_types', {
276
+ params: {
277
+ people: `/people/${currentCompany.id}`,
278
+ },
279
+ });
280
+
281
+ if (!existingRequestPromise) {
282
+ walletPaymentTypeRequestRef.current = {
283
+ key: requestKey,
284
+ promise: requestPromise,
285
+ };
286
+ }
287
+
288
+ requestPromise
289
+ .then(response => {
290
+ if (!isMounted) {
291
+ return;
292
+ }
293
+
294
+ const walletPaymentTypes = Array.isArray(response?.member)
295
+ ? response.member
296
+ : Array.isArray(response?.['hydra:member'])
297
+ ? response['hydra:member']
298
+ : Array.isArray(response)
299
+ ? response
300
+ : [];
301
+ paymentTypeActions.setItems(
302
+ selectPosWalletPaymentTypes({
303
+ walletPaymentTypes,
304
+ deviceConfigs: paymentConfigSource,
305
+ companyConfigs,
306
+ gateway: getPaymentGateway(device_config || paymentConfigSource),
307
+ }),
308
+ );
309
+ walletPaymentTypeLoadedKeyRef.current = requestKey;
310
+ })
311
+ .catch(() => {
312
+ if (isMounted) {
313
+ paymentTypeActions.setItems([]);
314
+ }
315
+ })
316
+ .finally(() => {
317
+ if (
318
+ walletPaymentTypeRequestRef.current.key === requestKey &&
319
+ walletPaymentTypeRequestRef.current.promise === requestPromise
320
+ ) {
321
+ walletPaymentTypeRequestRef.current = {key: '', promise: null};
322
+ }
323
+ });
324
+
325
+ return () => {
326
+ isMounted = false;
327
+ };
328
+ }, [
329
+ companyConfigs,
330
+ currentCompany?.configs,
331
+ currentCompany?.id,
332
+ device_config?.configs,
333
+ isShopClientApp,
334
+ paymentTypeActions,
335
+ ]);
336
+ }
@@ -0,0 +1,5 @@
1
+ // The domain palette owns branding. Franchise selection must not change it.
2
+ export const resolveDomainThemeColors = (baseThemeColors = {}, mainCompany = {}) => ({
3
+ ...(mainCompany?.theme?.colors || {}),
4
+ ...(baseThemeColors || {}),
5
+ });
@@ -213,7 +213,7 @@ export default function Connections() {
213
213
  searchPlaceholder={
214
214
  tt('connections_label', 'search') || 'Buscar conexao, telefone ou status'
215
215
  }
216
- searchKey="search
216
+ searchKey="search"
217
217
  totalItemsLabel="connections"
218
218
  visibleColumnsPreferenceKey="connections"
219
219
  accentColor={brandColors.primary}
@@ -17,7 +17,7 @@ const { API_ORIGIN } = require('../../../../../../../src/tests/browser/apiOrigin
17
17
  const APP_VERSION = packageJson?.version || '1.0.0';
18
18
  const CURRENT_DEVICE_ID = 'web-7';
19
19
  const DEVICE_ENTITY_ID = 396;
20
- const INITIAL_ALIAS = 'Caixa atual';
20
+ const INITIAL_ALIAS = 'Caixa Santa Redonda';
21
21
  const NEXT_ALIAS = 'Caixa Renomeado #382';
22
22
 
23
23
  const CORS_HEADERS = {
@@ -250,7 +250,7 @@ test.describe('device-detail PDV currentCompany (#704)', () => {
250
250
 
251
251
  await page.getByTestId(`device-config-${PDV_CONFIG_ID}`).click();
252
252
  await expect(page).toHaveURL(/device-detail/, {timeout: 15000});
253
- await expect(page.getByText('Configuração do PDV')).toBeVisible({
253
+ await expect(page.getByText('Detalhes do Device', {exact: true})).toBeVisible({
254
254
  timeout: 15000,
255
255
  });
256
256
  await writeEvidence(
@@ -11,11 +11,14 @@ describe('DefaultProvider company authority guard', () => {
11
11
  it.each(providerFiles)(
12
12
  'uses the shared authority helper in %s',
13
13
  providerFile => {
14
- const source = fs.readFileSync(
14
+ const providerSource = fs.readFileSync(
15
15
  path.resolve(__dirname, '../../../react/components', providerFile),
16
16
  'utf8',
17
17
  );
18
18
 
19
+ const root = path.resolve(__dirname, '../../../react/components');
20
+ const platform = providerFile.includes('.web.') ? 'web' : 'native';
21
+ const source = providerSource + ['lifecycle', 'configuration'].map(group => fs.readFileSync(path.join(root, `DefaultProvider.${platform}.${group}.js`), 'utf8')).join('\n');
19
22
  expect(source).not.toContain('isTenantAdministrativeAuthority');
20
23
  expect(source).toMatch(
21
24
  /import\s*\{\s*canAdministerCompany\s*\}\s*from\s*['"]@controleonline\/ui-common\/src\/react\/utils\/companyAuthority['"]/,
@@ -0,0 +1,47 @@
1
+ {
2
+ "web": [
3
+ "ea75d6ac3ae9c95e52c766bdb34d53bc105f14c39d99296870fe5ffc22871c9d",
4
+ "91eb2c6aa1cf5924d8741f6baffc1a1a55ee3a4724ccefa8bde421f9c64150bc",
5
+ "d9e3b6b0bda60f7ee7c20f3b764ef1cf6b3c4505b2dd0c14149d738afc4b7250",
6
+ "dc4de55291686857d4e1e9f450bf94dd7711e6d553b7f7c25f12cdcc15699e8d",
7
+ "3aac47f15f506f8a4f4c6c43025ac01df0d0686f53aeb2693cf995481f542300",
8
+ "401f05b86cbf58f51cf0f8daa9daedd9a11405bd88fd92c0c50f4425cb7426c8",
9
+ "6096f06d9b6dffc3402b2b23bdf1a49acb9d8b07c32a406cda0576d23835123d",
10
+ "08c333c63aab840cc566dfd7e1859624001c1e0cf41385b4fbd320a0772f8683",
11
+ "66e252433a95f6c234521d17405723faa27ff255950355d148c8915a6a121e50",
12
+ "9defefb6dd3fe794f7f5165415ad66bbf9364feabe2950f9101ee75b4e920bbd",
13
+ "e7e947e510177219121d1983055a9d3c1bbff5e2be0e1ed88f0ef33b8b04550d",
14
+ "6a48d5207d4e6ab178c856cba730992f4b7033e72328407bbf9687ab43fea31a",
15
+ "b855618a73da5452161007146c7e6dbc7a455377088e5f3b39740a50785717ea",
16
+ "4e436b845f79c9abb9b02ce885712f3d86cbfc58e7ac013bbf12aa4cadf8766b",
17
+ "b4a82937d543b1ec702eb39a4f59c50fb405591a5cf47d57f26ddf3204c00c79",
18
+ "52f1d9c7c0e526b45a517d5ca6d0bad10f6f13f9e28710d07fbe2c6390fd4663",
19
+ "7fd064dfd9939c27fd50ef7e98870027d6a243818b01a7961250c3541c489b37",
20
+ "5d82d7da62a6a2a1ea620b2e20aed981e54a57f7481d0d7b80274836d18c67b1",
21
+ "06a38bfbc8bea082ea8aed3d115eb0be46a66cf4067e15002a93332edcfe3e18",
22
+ "45f67afbb83db2fc03d4db5eb1cb97d0aef89960f7fed857a13104dc0d577996"
23
+ ],
24
+ "native": [
25
+ "ea75d6ac3ae9c95e52c766bdb34d53bc105f14c39d99296870fe5ffc22871c9d",
26
+ "91eb2c6aa1cf5924d8741f6baffc1a1a55ee3a4724ccefa8bde421f9c64150bc",
27
+ "ca6d2dbaff4a3ceaf72de6a7309dd1fff040b8c0c8becd7e9ad9c5e951747e33",
28
+ "3568e8c2ad8a62ef8ecd3381ca564567348ac30775f77a8e162d0627e65c3149",
29
+ "251907db50e73d845caad88cebde534715c714c5656c71ba101840d1aa387995",
30
+ "69a271277a710e366340f53a73a0ad69c349c0d57903604895a577502982f968",
31
+ "d9e3b6b0bda60f7ee7c20f3b764ef1cf6b3c4505b2dd0c14149d738afc4b7250",
32
+ "ca2d71f74eee04d29f12f99eb6ad2a027d997e059ca624b050b626c33af048da",
33
+ "00aec313bce033cee0f7ec578b884c057d99082fc5d70bd4c427b71f443ac3be",
34
+ "89b61590c0cd1f85d8a0a91d73c2ed76879207aee74e4e06a5a299cd78569ced",
35
+ "e7225c56b302453cc1dca664733fd612b5537b4f172f325635366449d5f9204b",
36
+ "00726c053f3dd7242abc46334bda5e2d85b3881b41027a0bd6cf92d8017f089f",
37
+ "6a48d5207d4e6ab178c856cba730992f4b7033e72328407bbf9687ab43fea31a",
38
+ "ccb781af89d778267e58ed7d92a076bb9266074fabadaacf3615309565c221cd",
39
+ "4e436b845f79c9abb9b02ce885712f3d86cbfc58e7ac013bbf12aa4cadf8766b",
40
+ "b4a82937d543b1ec702eb39a4f59c50fb405591a5cf47d57f26ddf3204c00c79",
41
+ "52f1d9c7c0e526b45a517d5ca6d0bad10f6f13f9e28710d07fbe2c6390fd4663",
42
+ "17ed12ca2369541649112e7e894eb8e9e7f5af739406ae752e49c41f9d0f954c",
43
+ "5d82d7da62a6a2a1ea620b2e20aed981e54a57f7481d0d7b80274836d18c67b1",
44
+ "2e76f30b34a18f41ab7cc6e78525ac8e88d5dffe1aa76f92be795d4d4800652a",
45
+ "a8b1a30dc99d18b5c0c1361c71a604efc4a546e3af45bb9e33f4d51469bd570a"
46
+ ]
47
+ }
@@ -0,0 +1,24 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import crypto from 'node:crypto';
4
+ import {parse} from '@babel/parser';
5
+ import traverse from '@babel/traverse';
6
+ import expected from './providerEffects.fingerprint.json';
7
+
8
+ // Protect the unrelated auth/device/bootstrap callbacks while extracting hooks.
9
+ // Fingerprints record the approved runtime callbacks, dependencies and hook order.
10
+ it.each(['web', 'native'])('preserves bootstrap effects in %s', platform => {
11
+ const root = path.resolve(__dirname, '../../../react/components');
12
+ const provider = parse(fs.readFileSync(path.join(root, `DefaultProvider.${platform}.js`), 'utf8'), {sourceType: 'module', plugins: ['jsx']});
13
+ const hooks = new Map();
14
+ for (const group of ['lifecycle', 'configuration']) {
15
+ const code = fs.readFileSync(path.join(root, `DefaultProvider.${platform}.${group}.js`), 'utf8');
16
+ traverse(parse(code, {sourceType: 'module'}), {FunctionDeclaration(p) {
17
+ const effect = p.node.body.body.find(n => n.expression?.callee?.name === 'useEffect');
18
+ if (effect) hooks.set(p.node.id.name, crypto.createHash('sha256').update(code.slice(effect.start, effect.end)).digest('hex'));
19
+ }});
20
+ }
21
+ const actual = [];
22
+ traverse(provider, {CallExpression(p) {if (hooks.has(p.node.callee.name)) actual.push(hooks.get(p.node.callee.name));}});
23
+ expect(actual).toEqual(expected[platform]);
24
+ });