@controleonline/ui-common 1.2.90 → 1.2.92

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,323 @@
1
+ import {useEffect} from 'react';
2
+
3
+ // Extracted bootstrap effects retain their dependency arrays and invocation order.
4
+
5
+ export function useNativeTranslationRefresh({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 useNativeTranslationReset({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 useNativeAppLifecycle({AppState, setAppState}) {
38
+ useEffect(() => {
39
+ const subscription = AppState.addEventListener('change', nextState => {
40
+ setAppState(nextState || 'active');
41
+ });
42
+
43
+ return () => {
44
+ subscription?.remove?.();
45
+ };
46
+ }, []);
47
+ }
48
+
49
+ export function useNativeNativeVersion({DeviceInfo, device, fetchDeviceId}) {
50
+ useEffect(() => {
51
+ const checkVersion = async () => {
52
+ const appVersion = await DeviceInfo.getVersion();
53
+ if (
54
+ device &&
55
+ ((device.appVersion && device.appVersion != appVersion) || !device.appName)
56
+ ) {
57
+ fetchDeviceId();
58
+ }
59
+ };
60
+ checkVersion();
61
+ }, [device]);
62
+ }
63
+
64
+ export function useNativeNativeDeviceIdentity({device, fetchDeviceId, deviceActions}) {
65
+ useEffect(() => {
66
+ if (!device || !device.id) {
67
+ fetchDeviceId();
68
+ } else {
69
+ deviceActions.setItem(device);
70
+ }
71
+ }, [device]);
72
+ }
73
+
74
+ export function useNativeMainCompany({device, peopleActions}) {
75
+ useEffect(() => {
76
+ if (device && device.id) {
77
+ peopleActions.mainCompany();
78
+ }
79
+ }, [device?.id]);
80
+ }
81
+
82
+ export function useNativeDeviceConfigReset({deviceConfigPeopleIri, lastDeviceConfigPeopleIriRef, setDeviceConfigFetched, setDeviceRuntimeConfigSynced, deviceConfigsActions}) {
83
+ useEffect(() => {
84
+ if (
85
+ !deviceConfigPeopleIri ||
86
+ lastDeviceConfigPeopleIriRef.current === deviceConfigPeopleIri
87
+ ) {
88
+ return;
89
+ }
90
+
91
+ lastDeviceConfigPeopleIriRef.current = deviceConfigPeopleIri;
92
+ setDeviceConfigFetched(false);
93
+ setDeviceRuntimeConfigSynced(false);
94
+ deviceConfigsActions.setItem({});
95
+ }, [deviceConfigPeopleIri, deviceConfigsActions]);
96
+ }
97
+
98
+ export function useNativeDeviceRegistration({sessionChecked, isLogged, deviceConfigPeopleIri, device, deviceConfigFetched, currentCompany, canAdministerCompany, mainCompany, user, deviceActions, runtimeDeviceType, buildDeviceRegistrationPayload, app_type, hasDeviceRecordChanges, setDevice}) {
99
+ useEffect(() => {
100
+ if (!sessionChecked || !isLogged || !deviceConfigPeopleIri || !device?.id || deviceConfigFetched) {
101
+ return;
102
+ }
103
+
104
+ // Device registration changes tenant context and is not valid without an
105
+ // active company or administrative authority for that company.
106
+ if (
107
+ !currentCompany?.id ||
108
+ !canAdministerCompany({company: currentCompany, mainCompany, user})
109
+ ) {
110
+ return;
111
+ }
112
+
113
+ let cancelled = false;
114
+
115
+ const syncDeviceRegistration = async () => {
116
+ const items = await deviceActions.getItems({
117
+ device: device.id,
118
+ people: deviceConfigPeopleIri,
119
+ type: runtimeDeviceType,
120
+ });
121
+
122
+ if (cancelled) {
123
+ return;
124
+ }
125
+
126
+ const existingDevice =
127
+ Array.isArray(items) && items.length > 0 ? items[0] : null;
128
+ const nextDevice = buildDeviceRegistrationPayload({
129
+ deviceInfo: device,
130
+ appType: app_type,
131
+ existingDevice,
132
+ });
133
+
134
+ if (!hasDeviceRecordChanges({existingDevice, nextDevice})) {
135
+ const nextLocalDevice = {
136
+ ...device,
137
+ entityId: existingDevice?.id || device?.entityId || null,
138
+ entityIri:
139
+ existingDevice?.['@id'] ||
140
+ device?.entityIri ||
141
+ (existingDevice?.id ? `/devices/${existingDevice.id}` : null),
142
+ alias: existingDevice?.alias || nextDevice.alias,
143
+ type: device?.type || runtimeDeviceType,
144
+ metadata: {
145
+ ...(existingDevice?.metadata || {}),
146
+ ...(nextDevice.metadata || {}),
147
+ },
148
+ };
149
+
150
+ if (JSON.stringify(nextLocalDevice) !== JSON.stringify(device)) {
151
+ setDevice(nextLocalDevice);
152
+ localStorage.setItem('device', JSON.stringify(nextLocalDevice));
153
+ deviceActions.setItem(nextLocalDevice);
154
+ }
155
+ return;
156
+ }
157
+
158
+ const savedDevice = await deviceActions.save(nextDevice);
159
+ if (cancelled || !savedDevice) {
160
+ return;
161
+ }
162
+
163
+ const nextLocalDevice = {
164
+ ...device,
165
+ entityId: savedDevice?.id || device?.entityId || null,
166
+ entityIri:
167
+ savedDevice?.['@id'] ||
168
+ device?.entityIri ||
169
+ (savedDevice?.id ? `/devices/${savedDevice.id}` : null),
170
+ alias: savedDevice.alias || nextDevice.alias,
171
+ type: device?.type || runtimeDeviceType,
172
+ metadata: {
173
+ ...(savedDevice?.metadata || {}),
174
+ ...(nextDevice.metadata || {}),
175
+ },
176
+ };
177
+
178
+ if (JSON.stringify(nextLocalDevice) !== JSON.stringify(device)) {
179
+ setDevice(nextLocalDevice);
180
+ localStorage.setItem('device', JSON.stringify(nextLocalDevice));
181
+ deviceActions.setItem(nextLocalDevice);
182
+ }
183
+ };
184
+
185
+ syncDeviceRegistration().catch(() => {});
186
+
187
+ return () => {
188
+ cancelled = true;
189
+ };
190
+ }, [
191
+ device?.appVersion,
192
+ device?.batteryLevel,
193
+ device?.buildNumber,
194
+ device?.deviceType,
195
+ device?.id,
196
+ device?.metadata,
197
+ device?.manufacturer,
198
+ device?.model,
199
+ device?.systemVersion,
200
+ deviceConfigFetched,
201
+ deviceConfigPeopleIri,
202
+ isLogged,
203
+ currentCompany,
204
+ mainCompany,
205
+ sessionChecked,
206
+ runtimeDeviceType,
207
+ user,
208
+ ]);
209
+ }
210
+
211
+ export function useNativePrinters({isShopClientApp, sessionChecked, isLogged, currentCompany, printerActions}) {
212
+ useEffect(() => {
213
+ if (isShopClientApp || !sessionChecked || !isLogged || !currentCompany?.id) {
214
+ return;
215
+ }
216
+
217
+ printerActions
218
+ .ensureCompanyPrintersLoaded({people: currentCompany.id})
219
+ .catch(() => {});
220
+ }, [currentCompany?.id, isLogged, isShopClientApp, printerActions, sessionChecked]);
221
+ }
222
+
223
+ export function useNativePaymentTypes({isShopClientApp, companyConfigs, currentCompany, device_config, paymentTypeActions, mainConfigsDiscovered, api, selectPosWalletPaymentTypes, getPaymentGateway}) {
224
+ useEffect(() => {
225
+ const paymentConfigSource = isShopClientApp
226
+ ? Object.keys(companyConfigs || {}).length > 0
227
+ ? companyConfigs
228
+ : currentCompany?.configs
229
+ : device_config?.configs;
230
+
231
+ if (!currentCompany?.id || !paymentConfigSource) {
232
+ paymentTypeActions.setItems([]);
233
+ return;
234
+ }
235
+
236
+ if (!isShopClientApp && !mainConfigsDiscovered) {
237
+ return;
238
+ }
239
+
240
+ let isMounted = true;
241
+
242
+ api
243
+ .fetch('wallet_payment_types', {
244
+ params: {
245
+ people: `/people/${currentCompany.id}`,
246
+ },
247
+ })
248
+ .then(response => {
249
+ if (!isMounted) {
250
+ return;
251
+ }
252
+
253
+ const walletPaymentTypes = Array.isArray(response?.member)
254
+ ? response.member
255
+ : Array.isArray(response?.['hydra:member'])
256
+ ? response['hydra:member']
257
+ : Array.isArray(response)
258
+ ? response
259
+ : [];
260
+ paymentTypeActions.setItems(
261
+ selectPosWalletPaymentTypes({
262
+ walletPaymentTypes,
263
+ deviceConfigs: paymentConfigSource,
264
+ companyConfigs,
265
+ gateway: getPaymentGateway(device_config || paymentConfigSource),
266
+ }),
267
+ );
268
+ })
269
+ .catch(() => {
270
+ if (isMounted) {
271
+ paymentTypeActions.setItems([]);
272
+ }
273
+ });
274
+
275
+ return () => {
276
+ isMounted = false;
277
+ };
278
+ }, [
279
+ companyConfigs,
280
+ currentCompany?.configs,
281
+ currentCompany?.id,
282
+ device_config?.configs,
283
+ isShopClientApp,
284
+ paymentTypeActions,
285
+ ]);
286
+ }
287
+
288
+ export function useNativeDeviceConfigFetch({device, isLogged, currentCompany, setDeviceConfigFetched, setDeviceRuntimeConfigSynced, deviceConfigsActions, runtimeDeviceType, parseConfigsObject}) {
289
+ useEffect(() => {
290
+ if (
291
+ device &&
292
+ device.id &&
293
+ isLogged &&
294
+ currentCompany &&
295
+ Object.entries(currentCompany).length > 0
296
+ ) {
297
+ setDeviceConfigFetched(false);
298
+ setDeviceRuntimeConfigSynced(false);
299
+ deviceConfigsActions
300
+ .getItems({
301
+ 'device.device': device.id,
302
+ people: '/people/' + currentCompany.id,
303
+ type: runtimeDeviceType,
304
+ })
305
+ .then(data => {
306
+ if (Array.isArray(data) && data.length > 0) {
307
+ const nextItem = {
308
+ ...data[0],
309
+ configs: parseConfigsObject(data[0]?.configs),
310
+ };
311
+ deviceConfigsActions.setItem(nextItem);
312
+ return;
313
+ }
314
+
315
+ deviceConfigsActions.setItem({});
316
+ })
317
+ .catch(() => {})
318
+ .finally(() => {
319
+ setDeviceConfigFetched(true);
320
+ });
321
+ }
322
+ }, [currentCompany?.id, isLogged, device?.id, runtimeDeviceType]);
323
+ }
@@ -0,0 +1,327 @@
1
+ import {useEffect} from 'react';
2
+
3
+ // Extracted bootstrap effects retain their dependency arrays and invocation order.
4
+
5
+ export function useWebDeviceConfigFetch({device, isLogged, deviceConfigPeopleIri, setDeviceConfigFetched, setDeviceRuntimeConfigSynced, deviceConfigsActions, runtimeDeviceType, selectRuntimeDeviceConfig, parseConfigsObject}) {
6
+ useEffect(() => {
7
+ if (!device?.id || !isLogged || !deviceConfigPeopleIri) {
8
+ return;
9
+ }
10
+
11
+ setDeviceConfigFetched(false);
12
+ setDeviceRuntimeConfigSynced(false);
13
+ deviceConfigsActions.setItem({});
14
+
15
+ deviceConfigsActions
16
+ .getItems({
17
+ 'device.device': device.id,
18
+ people: deviceConfigPeopleIri,
19
+ type: runtimeDeviceType,
20
+ })
21
+ .then(data => {
22
+ const selectedConfig = selectRuntimeDeviceConfig({
23
+ items: data,
24
+ deviceId: device.id,
25
+ companyId: deviceConfigPeopleIri,
26
+ runtimeDeviceType,
27
+ });
28
+
29
+ if (selectedConfig) {
30
+ const nextItem = {
31
+ ...selectedConfig,
32
+ configs: parseConfigsObject(selectedConfig?.configs),
33
+ };
34
+ deviceConfigsActions.setItem(nextItem);
35
+ return;
36
+ }
37
+
38
+ deviceConfigsActions.setItem({});
39
+ })
40
+ .catch(() => {})
41
+ .finally(() => {
42
+ setDeviceConfigFetched(true);
43
+ });
44
+ }, [
45
+ device?.id,
46
+ deviceConfigPeopleIri,
47
+ deviceConfigsActions,
48
+ isLogged,
49
+ runtimeDeviceType,
50
+ ]);
51
+ }
52
+
53
+ export function useWebDeviceConfigSync({deviceConfigFetched, isLogged, deviceConfigPeopleIri, device, deviceRuntimeConfigSynced, runtimeDeviceType, device_config, buildDefaultDeviceConfigs, buildProviderManagedDeviceConfigs, appVersion, setDeviceRuntimeConfigSynced, deviceConfigsActions, user}) {
54
+ useEffect(() => {
55
+ if (
56
+ !deviceConfigFetched ||
57
+ !isLogged ||
58
+ !deviceConfigPeopleIri ||
59
+ !device?.id ||
60
+ deviceRuntimeConfigSynced
61
+ ) {
62
+ return;
63
+ }
64
+
65
+ const isNewPdvConfig =
66
+ runtimeDeviceType === 'PDV' &&
67
+ !device_config?.id &&
68
+ !device_config?.['@id'];
69
+ const buildDeviceConfigs = isNewPdvConfig
70
+ ? buildDefaultDeviceConfigs
71
+ : buildProviderManagedDeviceConfigs;
72
+ const {nextConfigs, needsUpdate} = buildDeviceConfigs({
73
+ configs: device_config?.configs,
74
+ appVersion,
75
+ deviceInfo: device,
76
+ });
77
+
78
+ if (!needsUpdate) {
79
+ setDeviceRuntimeConfigSynced(true);
80
+ return;
81
+ }
82
+
83
+ // app-community#821: never auto-persist device_config from global bootstrap.
84
+ // Provider-managed fields (config-version, pos-gateway, screen metrics) stay
85
+ // in memory only. POST/PUT to /device_configs* is reserved for explicit
86
+ // device settings UI — avoids 403 from DeviceFinancialConfigAuthorizationSubscriber
87
+ // on pages like order-history (PDV/MANAGER types are fully protected).
88
+ const currentItem = device_config || {};
89
+ deviceConfigsActions.setItem({
90
+ ...currentItem,
91
+ configs: nextConfigs,
92
+ device: currentItem.device || device.id,
93
+ people: currentItem.people || deviceConfigPeopleIri,
94
+ type: currentItem.type || runtimeDeviceType,
95
+ });
96
+ setDeviceRuntimeConfigSynced(true);
97
+ }, [
98
+ appVersion,
99
+ device?.id,
100
+ device?.manufacturer,
101
+ device?.isEmulator,
102
+ deviceConfigFetched,
103
+ deviceConfigPeopleIri,
104
+ deviceRuntimeConfigSynced,
105
+ deviceConfigsActions,
106
+ device_config,
107
+ isLogged,
108
+ runtimeDeviceType,
109
+ user,
110
+ ]);
111
+ }
112
+
113
+ export function useWebMainConfigReset({isLogged, currentCompany, device, setMainConfigsDiscovered}) {
114
+ useEffect(() => {
115
+ if (!isLogged || !currentCompany?.id || !device?.id) {
116
+ return;
117
+ }
118
+
119
+ setMainConfigsDiscovered(false);
120
+ }, [isLogged, currentCompany?.id, device?.id]);
121
+ }
122
+
123
+ export function useWebMainConfigDiscovery({isLogged, currentCompany, device, mainConfigsDiscovered, configActions, setMainConfigsDiscovered}) {
124
+ useEffect(() => {
125
+ if (
126
+ !isLogged ||
127
+ !currentCompany?.id ||
128
+ !device?.id ||
129
+ mainConfigsDiscovered
130
+ ) {
131
+ return;
132
+ }
133
+
134
+ configActions
135
+ .discoveryMainConfigs({
136
+ people: '/people/' + currentCompany.id,
137
+ })
138
+ .catch(() => { })
139
+ .finally(() => {
140
+ setMainConfigsDiscovered(true);
141
+ });
142
+ }, [configActions, currentCompany?.id, device?.id, isLogged, mainConfigsDiscovered]);
143
+ }
144
+
145
+ export function useWebMainConfigSeed({isLogged, currentCompany, mainConfigsDiscovered, configActions}) {
146
+ useEffect(() => {
147
+ if (
148
+ isLogged &&
149
+ currentCompany &&
150
+ Object.entries(currentCompany).length > 0 &&
151
+ !mainConfigsDiscovered
152
+ ) {
153
+ configActions.setItems(currentCompany.configs);
154
+ }
155
+ }, [currentCompany, isLogged, mainConfigsDiscovered]);
156
+ }
157
+
158
+ export function useWebTranslationBootstrap({currentRouteName, isPublicRouteActive, isLogged, hasCurrentCompany, deviceConfigFetched, expectedTranslateBootstrapKey, translateBootstrapKeyRef, companies, currentCompany, mainCompany, setActiveTranslateBootstrapKey, setTranslateReady, configuredTranslationLanguage, Translate, stores, translateStore}) {
159
+ useEffect(() => {
160
+ if (!currentRouteName || isPublicRouteActive) {
161
+ return;
162
+ }
163
+
164
+ if (
165
+ !isLogged ||
166
+ !hasCurrentCompany ||
167
+ !deviceConfigFetched ||
168
+ !expectedTranslateBootstrapKey
169
+ ) {
170
+ return;
171
+ }
172
+
173
+ const currentConfig = JSON.parse(localStorage.getItem('config') || '{}');
174
+
175
+ if (
176
+ translateBootstrapKeyRef.current === expectedTranslateBootstrapKey &&
177
+ global.t
178
+ ) {
179
+ global.t.companies = companies;
180
+ global.t.currentCompany = currentCompany;
181
+ global.t.mainCompany = mainCompany;
182
+ setActiveTranslateBootstrapKey(expectedTranslateBootstrapKey);
183
+ setTranslateReady(true);
184
+
185
+ return;
186
+ }
187
+
188
+ setTranslateReady(false);
189
+
190
+ if (currentConfig.language !== configuredTranslationLanguage) {
191
+ const nextConfig = {
192
+ ...currentConfig,
193
+ language: configuredTranslationLanguage,
194
+ };
195
+ localStorage.setItem(
196
+ 'config',
197
+ JSON.stringify(nextConfig),
198
+ );
199
+ }
200
+
201
+ translateBootstrapKeyRef.current = expectedTranslateBootstrapKey;
202
+ global.t = new Translate(
203
+ companies,
204
+ mainCompany,
205
+ currentCompany,
206
+ Object.keys(stores),
207
+ translateStore,
208
+ );
209
+ setActiveTranslateBootstrapKey(expectedTranslateBootstrapKey);
210
+ setTranslateReady(true);
211
+ global.refreshTranslationsUI?.();
212
+ }, [
213
+ companies,
214
+ configuredTranslationLanguage,
215
+ currentCompany,
216
+ currentRouteName,
217
+ mainCompany,
218
+ deviceConfigFetched,
219
+ expectedTranslateBootstrapKey,
220
+ hasCurrentCompany,
221
+ isLogged,
222
+ isPublicRouteActive,
223
+ translateStore,
224
+ ]);
225
+ }
226
+
227
+ export function useWebBootstrapReady({isLogged, hasCurrentCompany, currentRouteName, isTranslateBootstrapReady, onBootstrapReady}) {
228
+ useEffect(() => {
229
+ if (
230
+ !isLogged ||
231
+ !hasCurrentCompany ||
232
+ (currentRouteName && isTranslateBootstrapReady)
233
+ ) {
234
+ onBootstrapReady?.();
235
+ }
236
+ }, [
237
+ currentRouteName,
238
+ hasCurrentCompany,
239
+ isLogged,
240
+ isTranslateBootstrapReady,
241
+ onBootstrapReady,
242
+ ]);
243
+ }
244
+
245
+ export function useWebCompanies({device, isLogged, peopleActions}) {
246
+ useEffect(() => {
247
+ if (device && device.id && isLogged) {
248
+ peopleActions.myCompanies();
249
+ }
250
+ }, [isLogged, device?.id]);
251
+ }
252
+
253
+ export function useWebMenus({isLogged, currentCompany, actions, api, appType, normalizeRuntimeMenuResponse}) {
254
+ useEffect(() => {
255
+ if (!isLogged || !currentCompany?.id) {
256
+ actions.setMenus([]);
257
+ return undefined;
258
+ }
259
+
260
+ let cancelled = false;
261
+
262
+ api
263
+ .fetch('menus-people', {
264
+ params: {
265
+ myCompany: currentCompany.id,
266
+ appType,
267
+ menuType: 'home',
268
+ },
269
+ })
270
+ .then(result => {
271
+ if (!cancelled) {
272
+ actions.setMenus(normalizeRuntimeMenuResponse(result, {appType}));
273
+ }
274
+ })
275
+ .catch(() => {
276
+ if (!cancelled) {
277
+ actions.setMenus(normalizeRuntimeMenuResponse(null, {appType}));
278
+ }
279
+ });
280
+
281
+ return () => {
282
+ cancelled = true;
283
+ };
284
+ }, [actions, appType, currentCompany?.id, isLogged]);
285
+ }
286
+
287
+ export function useWebThemeFetch({api, parseThemeCss, setBaseThemeColors, actions, currentCompany, mainCompany, device}) {
288
+ useEffect(() => {
289
+ const fetchColors = async () => {
290
+ try {
291
+ const cssText = await api.fetch('themes-colors.css', {
292
+ responseType: 'text',
293
+ });
294
+ const parsedColors = parseThemeCss(cssText);
295
+ setBaseThemeColors(parsedColors);
296
+ actions.setColors(parsedColors);
297
+ } catch {
298
+ setBaseThemeColors({});
299
+ }
300
+ };
301
+
302
+ fetchColors();
303
+ }, [actions, currentCompany?.id, mainCompany?.id, device?.id]);
304
+ }
305
+
306
+ export function useWebThemePalette({resolveDomainThemeColors, baseThemeColors, mainCompany, resolveThemePalette, runtimeColors, applyPaletteToRuntimeColors, applyThemeCssVariables, actions, currentCompany, isShopClientApp}) {
307
+ useEffect(() => {
308
+ const mergedThemeColors = resolveDomainThemeColors(baseThemeColors, mainCompany);
309
+
310
+ const palette = resolveThemePalette(mergedThemeColors, runtimeColors);
311
+ applyPaletteToRuntimeColors(palette, runtimeColors);
312
+ applyThemeCssVariables({
313
+ themeColors: mergedThemeColors,
314
+ palette,
315
+ });
316
+
317
+ actions.setColors(mergedThemeColors);
318
+ }, [
319
+ actions,
320
+ baseThemeColors,
321
+ currentCompany?.id,
322
+ currentCompany?.theme?.colors,
323
+ mainCompany?.id,
324
+ mainCompany?.theme?.colors,
325
+ isShopClientApp,
326
+ ]);
327
+ }