@controleonline/ui-common 1.2.88 → 1.2.90

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,238 @@
1
+ import {
2
+ normalizeBooleanConfig,
3
+ SHOP_LOYALTY_COUPONS_ENABLED_CONFIG_KEY,
4
+ } from '@controleonline/ui-common/src/react/utils/shopConfig.js';
5
+
6
+ export const CIELO_DEVICES = ['quantum', 'ingenico', 'positivo'];
7
+ export const SUPPORTED_POS_GATEWAYS = ['cielo', 'infinite-pay'];
8
+ export const DEVICE_ALERT_SOUND_ENABLED_KEY = 'notification-sound-enabled';
9
+ export const DEVICE_ALERT_SOUND_URL_KEY = 'notification-sound-url';
10
+ export const DEVICE_ORDER_VISIBILITY_KEY = 'pos-order-visibility';
11
+ export const DEVICE_ORDER_VISIBILITY_DEVICE = 'device';
12
+ export const DEVICE_ORDER_VISIBILITY_COMPANY = 'company';
13
+ export const POS_DELIVERY_ENABLED_CONFIG_KEY = 'pos-delivery-enabled';
14
+ export const DEVICE_RUNTIME_DEBUG_INFO_ENABLED_KEY =
15
+ 'device-runtime-debug-info-enabled';
16
+ export const DISPLAY_AUTO_PRINT_PRODUCT_CONFIG_KEY =
17
+ 'display-auto-print-product';
18
+ export const DISPLAY_ALLOW_PRINTER_CHANGE_CONFIG_KEY =
19
+ 'display-allow-printer-change';
20
+ export const DISPLAY_SIZE_CONFIG_KEY = 'display-size';
21
+ export const DISPLAY_SIDE_BREAK_CONFIG_KEY = 'display-side-break';
22
+ export const DISPLAY_SIZE_MIN = 1;
23
+ export const DISPLAY_SIZE_MAX = 10;
24
+ export const DISPLAY_SIZE_DEFAULT = 5;
25
+ export const POS_OPERATION_MODE_CONFIG_KEY = 'pos-operation-mode';
26
+ /** Independent of operation mode: whether this device may run local charge/payment. */
27
+ export const POS_LOCAL_CHARGE_ENABLED_CONFIG_KEY = 'pos-local-charge-enabled';
28
+ // Canonical financial permission already enforced by the API.
29
+ export const ORDER_CHARGE_ENABLED_CONFIG_KEY = 'order-charge-enabled';
30
+ export const isOrderChargeEnabled = configs => {
31
+ const value = parseConfigsObject(configs)?.[ORDER_CHARGE_ENABLED_CONFIG_KEY];
32
+ return value === true || value === 1 ||
33
+ ['1', 'true', 'yes', 'on'].includes(String(value ?? '').trim().toLowerCase());
34
+ };
35
+ export const POS_PRODUCT_SHOWCASE_CONFIG_KEY = 'pos-product-showcase-id';
36
+ export const DEVICE_ANDROID_KIOSK_ENABLED_CONFIG_KEY =
37
+ 'android-kiosk-enabled';
38
+ export const DEVICE_ANDROID_LAUNCHER_ENABLED_CONFIG_KEY =
39
+ 'android-launcher-enabled';
40
+ export const POS_AUTO_PRINT_ENABLED_CONFIG_KEY = 'pos-auto-print-enabled';
41
+ export const POS_CASH_MANAGEMENT_MODE_CONFIG_KEY =
42
+ 'pos-cash-management-mode';
43
+ export const POS_CHECK_ORDER_TYPE_CONFIG_KEY = 'check-order-type';
44
+ export const POS_CHECK_ORDER_MANAGEMENT_MODE_CONFIG_KEY =
45
+ 'check-order-management-mode';
46
+ export const POS_OPERATION_MODE_COUNTER = 'counter';
47
+ export const POS_OPERATION_MODE_WAITER = 'waiter';
48
+ export const POS_OPERATION_MODE_TOTEM = 'totem';
49
+ export const POS_OPERATION_MODE_SINGLE_ITEM = 'single-item';
50
+ export const POS_OPERATION_MODE_CASHIER = 'cashier';
51
+ export const POS_CHECK_ORDER_TYPE_NONE = 'none';
52
+ export const POS_CHECK_ORDER_TYPE_TAB = 'tab';
53
+ export const POS_CHECK_ORDER_TYPE_TABLE = 'table';
54
+ export const POS_CHECK_ORDER_TYPE_TABLE_TAB = 'table-tab';
55
+ export const POS_CHECK_ORDER_TYPE_STAMP = 'stamp';
56
+ export const POS_CHECK_ORDER_MANAGEMENT_MODE_MANAGE = 'manage';
57
+ export const POS_CHECK_ORDER_MANAGEMENT_MODE_EXISTING_ONLY = 'existing-only';
58
+ export const POS_OPERATION_MODE_DEFAULT = POS_OPERATION_MODE_COUNTER;
59
+ export const POS_PRINT_MODE_ORDER = 'order';
60
+ export const POS_PRINT_MODE_FORM = 'form';
61
+ export const POS_PRINT_MODE_DEFAULT = POS_PRINT_MODE_ORDER;
62
+ export const POS_CASH_MANAGEMENT_MODE_CASH_REGISTER = 'cash-register';
63
+ export const POS_CASH_MANAGEMENT_MODE_DAILY = 'daily';
64
+ export const POS_CASH_MANAGEMENT_MODE_DEFAULT =
65
+ POS_CASH_MANAGEMENT_MODE_CASH_REGISTER;
66
+ export const POS_OPERATION_MODE_OPTIONS = [
67
+ {
68
+ value: POS_OPERATION_MODE_COUNTER,
69
+ translationKey: 'counterService',
70
+ descriptionKey: 'counterServiceDescription',
71
+ },
72
+ {
73
+ value: POS_OPERATION_MODE_WAITER,
74
+ translationKey: 'waiterService',
75
+ descriptionKey: 'waiterServiceDescription',
76
+ },
77
+ {
78
+ value: POS_OPERATION_MODE_TOTEM,
79
+ translationKey: 'selfServiceKiosk',
80
+ descriptionKey: 'selfServiceKioskDescription',
81
+ },
82
+ {
83
+ value: POS_OPERATION_MODE_SINGLE_ITEM,
84
+ translationKey: 'singleItemSale',
85
+ descriptionKey: 'singleItemSaleDescription',
86
+ },
87
+ {
88
+ value: POS_OPERATION_MODE_CASHIER,
89
+ translationKey: 'cashierPOS',
90
+ descriptionKey: 'cashierPOSDescription',
91
+ },
92
+ ];
93
+
94
+ export const DEFAULT_DEVICE_CONFIGS = {
95
+ 'pos-type': 'full',
96
+ 'print-mode': 'order',
97
+ 'check-type': 'manual',
98
+ 'product-input-type': 'manual',
99
+ 'selection-type': 'single',
100
+ sound: '0',
101
+ vibration: '0',
102
+ [DEVICE_ORDER_VISIBILITY_KEY]: DEVICE_ORDER_VISIBILITY_DEVICE,
103
+ [POS_DELIVERY_ENABLED_CONFIG_KEY]: '1',
104
+ [DEVICE_ALERT_SOUND_ENABLED_KEY]: '0',
105
+ [DEVICE_ALERT_SOUND_URL_KEY]: '',
106
+ [DEVICE_RUNTIME_DEBUG_INFO_ENABLED_KEY]: '0',
107
+ [DEVICE_ANDROID_LAUNCHER_ENABLED_CONFIG_KEY]: '0',
108
+ [DISPLAY_ALLOW_PRINTER_CHANGE_CONFIG_KEY]: '0',
109
+ [DISPLAY_SIZE_CONFIG_KEY]: String(DISPLAY_SIZE_DEFAULT),
110
+ [DISPLAY_SIDE_BREAK_CONFIG_KEY]: '0',
111
+ [POS_CHECK_ORDER_TYPE_CONFIG_KEY]: POS_CHECK_ORDER_TYPE_NONE,
112
+ [POS_CHECK_ORDER_MANAGEMENT_MODE_CONFIG_KEY]:
113
+ POS_CHECK_ORDER_MANAGEMENT_MODE_MANAGE,
114
+ [POS_OPERATION_MODE_CONFIG_KEY]: POS_OPERATION_MODE_DEFAULT,
115
+ [POS_PRODUCT_SHOWCASE_CONFIG_KEY]: '',
116
+ [POS_AUTO_PRINT_ENABLED_CONFIG_KEY]: '0',
117
+ [POS_CASH_MANAGEMENT_MODE_CONFIG_KEY]: POS_CASH_MANAGEMENT_MODE_DEFAULT,
118
+ };
119
+
120
+ export const isTruthyValue = value =>
121
+ value === true || value === '1' || value === 1 || value === 'true';
122
+
123
+ export const isMissingConfigValue = value =>
124
+ value === undefined || value === null || value === '';
125
+
126
+ export const parseConfigsObject = configs => {
127
+ if (!configs) return {};
128
+
129
+ if (typeof configs === 'string') {
130
+ try {
131
+ const parsed = JSON.parse(configs);
132
+ return parsed && typeof parsed === 'object' ? parsed : {};
133
+ } catch {
134
+ return {};
135
+ }
136
+ }
137
+
138
+ return typeof configs === 'object' ? {...configs} : {};
139
+ };
140
+
141
+ export const normalizeDisplaySize = value => {
142
+ const normalizedValue = String(value ?? '').trim();
143
+
144
+ if (normalizedValue === '') {
145
+ return DISPLAY_SIZE_DEFAULT;
146
+ }
147
+
148
+ const numericValue = Number(normalizedValue);
149
+
150
+ if (!Number.isFinite(numericValue)) {
151
+ return DISPLAY_SIZE_DEFAULT;
152
+ }
153
+
154
+ return Math.min(
155
+ DISPLAY_SIZE_MAX,
156
+ Math.max(DISPLAY_SIZE_MIN, Math.round(numericValue)),
157
+ );
158
+ };
159
+
160
+ export const resolveDisplaySize = configs =>
161
+ normalizeDisplaySize(
162
+ parseConfigsObject(configs)?.[DISPLAY_SIZE_CONFIG_KEY],
163
+ );
164
+
165
+ export const isDisplaySideBreakEnabled = configs =>
166
+ isTruthyValue(
167
+ parseConfigsObject(configs)?.[DISPLAY_SIDE_BREAK_CONFIG_KEY],
168
+ );
169
+
170
+ export const normalizePosOperationMode = value => {
171
+ const normalizedValue = String(value || '')
172
+ .trim()
173
+ .toLowerCase()
174
+ .normalize('NFD')
175
+ .replace(/[\u0300-\u036f]/g, '')
176
+ .replace(/[\s_]/g, '-');
177
+
178
+ if (
179
+ [
180
+ POS_OPERATION_MODE_COUNTER,
181
+ 'balcao',
182
+ 'counter-service',
183
+ 'front-counter',
184
+ ].includes(normalizedValue)
185
+ ) {
186
+ return POS_OPERATION_MODE_COUNTER;
187
+ }
188
+
189
+ if (
190
+ [
191
+ POS_OPERATION_MODE_WAITER,
192
+ 'garcon',
193
+ 'garcom',
194
+ 'table-service',
195
+ ].includes(normalizedValue)
196
+ ) {
197
+ return POS_OPERATION_MODE_WAITER;
198
+ }
199
+
200
+ if (
201
+ [
202
+ POS_OPERATION_MODE_TOTEM,
203
+ ].includes(normalizedValue)
204
+ ) {
205
+ return POS_OPERATION_MODE_TOTEM;
206
+ }
207
+
208
+ if (
209
+ [
210
+ POS_OPERATION_MODE_SINGLE_ITEM,
211
+ 'single-sale',
212
+ 'single-item-sale',
213
+ 'sale-unit',
214
+ 'sale-single',
215
+ 'venda-unitaria',
216
+ 'venda-unica',
217
+ 'item-unico',
218
+ 'unit-sale',
219
+ 'unitary-sale',
220
+ ].includes(normalizedValue)
221
+ ) {
222
+ return POS_OPERATION_MODE_SINGLE_ITEM;
223
+ }
224
+
225
+ if (
226
+ [
227
+ POS_OPERATION_MODE_CASHIER,
228
+ 'pdv',
229
+ 'pos',
230
+ 'checkout',
231
+ 'cashier-pos',
232
+ ].includes(normalizedValue)
233
+ ) {
234
+ return POS_OPERATION_MODE_CASHIER;
235
+ }
236
+
237
+ return '';
238
+ };
@@ -209,13 +209,11 @@ export default function Connections() {
209
209
  showColumnFiltersButton
210
210
  onRowPress={openWhatsAppChannel}
211
211
  renderCard={renderConnectionCard}
212
- searchProps={{
213
- compact: true,
214
- placeholder:
215
- tt('connections_label', 'search') || 'Buscar conexao, telefone ou status',
216
- searchKey: 'search',
217
- storeName: 'connections',
218
- }}
212
+ showSearch
213
+ searchPlaceholder={
214
+ tt('connections_label', 'search') || 'Buscar conexao, telefone ou status'
215
+ }
216
+ searchKey="search
219
217
  totalItemsLabel="connections"
220
218
  visibleColumnsPreferenceKey="connections"
221
219
  accentColor={brandColors.primary}
@@ -0,0 +1,32 @@
1
+ import React, {useRef, useState} from 'react';
2
+ import {Text, View} from 'react-native';
3
+ import {ORDER_CHARGE_ENABLED_CONFIG_KEY, isOrderChargeEnabled} from '../../../config/deviceConfigBootstrap';
4
+ import styles from '../../DeviceDetailPage.styles';
5
+
6
+ export default function DeviceChargePermissionSection({configs, actionsRef, currentCompany,
7
+ deviceString, deviceType, refreshCurrentConfig, showSystemError, renderSwitchRow, brandColors}) {
8
+ const [saving, setSaving] = useState(false);
9
+ const pending = useRef(false);
10
+ async function save(value) {
11
+ if (pending.current || !currentCompany?.id || !deviceString) return;
12
+ pending.current = true;
13
+ setSaving(true);
14
+ try {
15
+ await actionsRef.current.deviceConfigActions.addDeviceConfigs({device: deviceString,
16
+ people: `/people/${currentCompany.id}`, type: deviceType,
17
+ configs: JSON.stringify({[ORDER_CHARGE_ENABLED_CONFIG_KEY]: value})});
18
+ await refreshCurrentConfig();
19
+ } catch (error) {
20
+ showSystemError(error, 'Não foi possível salvar a permissão de cobrança.');
21
+ } finally {pending.current = false; setSaving(false);}
22
+ }
23
+ const enabled = isOrderChargeEnabled(configs);
24
+ return <View style={styles.configCard}>
25
+ {renderSwitchRow({label: 'Permitir cobrança neste device', value: enabled,
26
+ valueLabel: enabled ? 'Permitida' : 'Desabilitada',
27
+ disabled: saving || !deviceString || !currentCompany?.id, onValueChange: save})}
28
+ <Text style={[styles.deviceString, {color: brandColors.textSecondary}]}>
29
+ A cobrança usa os meios de pagamento e o destino configurados. O sistema valida a autorização para receber aqui ou encaminhar a outro device.
30
+ </Text>
31
+ </View>;
32
+ }
@@ -11,6 +11,7 @@ import {
11
11
  POS_CHECK_ORDER_TYPE_NONE,
12
12
  POS_CHECK_ORDER_TYPE_TAB,
13
13
  POS_CHECK_ORDER_TYPE_TABLE,
14
+ POS_CHECK_ORDER_TYPE_TABLE_TAB,
14
15
  POS_CHECK_ORDER_TYPE_STAMP,
15
16
  POS_CHECK_ORDER_MANAGEMENT_MODE_MANAGE,
16
17
  POS_CHECK_ORDER_MANAGEMENT_MODE_EXISTING_ONLY,
@@ -164,6 +165,10 @@ export default function DeviceDetailPdvConfigSection(ctx) {
164
165
  label: global.t?.t('orders', 'title', 'table') || 'Table',
165
166
  value: POS_CHECK_ORDER_TYPE_TABLE,
166
167
  },
168
+ {
169
+ label: 'Mesa + Comanda',
170
+ value: POS_CHECK_ORDER_TYPE_TABLE_TAB,
171
+ },
167
172
  {
168
173
  label: global.t?.t('orders', 'title', 'stamp') || 'Stamp',
169
174
  value: POS_CHECK_ORDER_TYPE_STAMP,
@@ -188,6 +193,8 @@ export default function DeviceDetailPdvConfigSection(ctx) {
188
193
  ? POS_CHECK_ORDER_TYPE_TAB
189
194
  : value === POS_CHECK_ORDER_TYPE_TABLE
190
195
  ? POS_CHECK_ORDER_TYPE_TABLE
196
+ : value === POS_CHECK_ORDER_TYPE_TABLE_TAB
197
+ ? POS_CHECK_ORDER_TYPE_TABLE_TAB
191
198
  : value === POS_CHECK_ORDER_TYPE_STAMP
192
199
  ? loyaltyCouponsEnabled
193
200
  ? POS_CHECK_ORDER_TYPE_STAMP
@@ -10,6 +10,7 @@ import styles from '../../DeviceDetailPage.styles';
10
10
  import DeviceDetailHeader from './DeviceDetailHeader';
11
11
  import CopyDeviceConfigModal from '@controleonline/ui-common/src/react/components/CopyDeviceConfigModal';
12
12
  import useDeviceDetailCopyConfig from './useDeviceDetailCopyConfig';
13
+ import DeviceChargePermissionSection from './DeviceChargePermissionSection';
13
14
  import DeviceDetailPdvConfigSection from './DeviceDetailPdvConfigSection';
14
15
  import DeviceDetailOrdersPrintSection from './DeviceDetailOrdersPrintSection';
15
16
  import DeviceDetailAlertsCommandsSection from './DeviceDetailAlertsCommandsSection';
@@ -150,7 +151,10 @@ const DeviceDetailScreen = () => {
150
151
  <DeviceDetailMovementSections {...detailCtx} />
151
152
 
152
153
  {showPdvOperationTab && (
153
- <DeviceDetailPdvConfigSection {...detailCtx} />
154
+ <>
155
+ <DeviceDetailPdvConfigSection {...detailCtx} />
156
+ <DeviceChargePermissionSection {...detailCtx} />
157
+ </>
154
158
  )}
155
159
 
156
160
  {(showPdvOrdersTab || shouldShowOrderVisibility || detailCtx.isDisplayDevice) && (
@@ -1,6 +1,6 @@
1
1
  import {normalizeEntityId} from '@controleonline/ui-common/src/react/utils/paymentDevices';
2
2
  import {isPosCashRegisterOpen} from '@controleonline/ui-common/src/react/config/deviceConfigBootstrap';
3
- import {Alert, Platform} from 'react-native';
3
+ import {confirmAction} from '../../../utils/confirmAction';
4
4
  import {PAYMENT_ICONS} from './deviceDetailConstants';
5
5
 
6
6
  export const paymentIcon = label => {
@@ -44,16 +44,7 @@ export const getProductShowcaseLabel = showcase => {
44
44
 
45
45
  export const getIsOpen = configs => isPosCashRegisterOpen(configs);
46
46
 
47
- export const confirm = (msg, cb) => {
48
- if (Platform.OS === 'web' && typeof window !== 'undefined' && window.confirm) {
49
- if (window.confirm(msg)) cb();
50
- } else {
51
- Alert.alert('Confirmação', msg, [
52
- {text: 'Cancelar', style: 'cancel'},
53
- {text: 'Confirmar', onPress: cb},
54
- ]);
55
- }
56
- };
47
+ export const confirm = confirmAction;
57
48
 
58
49
  export const getDeviceSwitchProps = ({disabled = false, palette, value = false}) => {
59
50
  const offTrackColor = disabled ? palette.switchDisabledTrack : palette.switchOffTrack;
@@ -23,6 +23,10 @@ import {
23
23
  } from '@controleonline/../../src/styles/branding';
24
24
 
25
25
  import styles from './IntegrationConfigPage.styles';
26
+ import {
27
+ normalizeTextValue, isConnected, formatApiError, resolveFrontOAuthRedirectUri,
28
+ replaceFrontOAuthStatus, formatOAuthStatusError,
29
+ } from './mercadoLivreIntegrationHelpers';
26
30
 
27
31
  const PROVIDER = {
28
32
  key: 'mercadolivre',
@@ -45,81 +49,6 @@ const shadowStyle = Platform.select({
45
49
  web: {boxShadow: '0 10px 24px rgba(15,23,42,0.08)'},
46
50
  });
47
51
 
48
- const normalizeTextValue = value => String(value ?? '').trim();
49
-
50
- const isConnected = value =>
51
- value === true ||
52
- value === 1 ||
53
- value === '1' ||
54
- String(value).trim().toLowerCase() === 'true';
55
-
56
- const formatApiError = error =>
57
- error?.message ||
58
- error?.description ||
59
- error?.error ||
60
- tt('marketplace_error', 'mercadoLivreLoad') ||
61
- 'Nao foi possivel carregar a integracao Mercado Livre.';
62
-
63
- const MERCADO_LIVRE_OAUTH_QUERY_KEYS = [
64
- 'code',
65
- 'state',
66
- 'error',
67
- 'error_description',
68
- 'mercadolivre_connected',
69
- 'mercadolivre_error',
70
- 'mercadolivre_message',
71
- ];
72
-
73
- const resolveFrontOAuthRedirectUri = () => {
74
- if (Platform.OS !== 'web' || typeof window === 'undefined') {
75
- return undefined;
76
- }
77
-
78
- const url = new URL(window.location.href);
79
- MERCADO_LIVRE_OAUTH_QUERY_KEYS.forEach(key => url.searchParams.delete(key));
80
- url.hash = '';
81
-
82
- return url.toString();
83
- };
84
-
85
- const replaceFrontOAuthStatus = statusParams => {
86
- if (Platform.OS !== 'web' || typeof window === 'undefined') {
87
- return;
88
- }
89
-
90
- const url = new URL(window.location.href);
91
- MERCADO_LIVRE_OAUTH_QUERY_KEYS.forEach(key => url.searchParams.delete(key));
92
- Object.entries(statusParams || {}).forEach(([key, value]) => {
93
- if (value !== undefined && value !== null && value !== '') {
94
- url.searchParams.set(key, String(value));
95
- }
96
- });
97
- window.history.replaceState({}, document.title, url.toString());
98
- };
99
-
100
- const formatOAuthStatusError = (error, message) => {
101
- const messageText = normalizeTextValue(message);
102
- if (messageText) {
103
- return `${
104
- tt('marketplace_error', 'mercadoLivreConnectWithMessage') ||
105
- 'Nao foi possivel conectar o Mercado Livre:'
106
- } ${messageText}`;
107
- }
108
-
109
- const errorText = normalizeTextValue(error);
110
- if (!errorText) {
111
- return (
112
- tt('marketplace_error', 'mercadoLivreConnect') ||
113
- 'Nao foi possivel conectar o Mercado Livre.'
114
- );
115
- }
116
-
117
- return `${
118
- tt('marketplace_error', 'mercadoLivreConnect') ||
119
- 'Nao foi possivel conectar o Mercado Livre.'
120
- } (${errorText}).`;
121
- };
122
-
123
52
  export default function MercadoLivreIntegrationPage() {
124
53
  const peopleStore = useStore('people');
125
54
  const themeStore = useStore('theme');
@@ -513,6 +442,7 @@ export default function MercadoLivreIntegrationPage() {
513
442
  </View>
514
443
 
515
444
  <TouchableOpacity
445
+ accessibilityRole="button"
516
446
  style={[
517
447
  styles.saveButton,
518
448
  {backgroundColor: PROVIDER.button},
@@ -0,0 +1,79 @@
1
+ import {Platform} from 'react-native';
2
+
3
+ const tt = (type, key) => global.t?.t('configs', type, key);
4
+
5
+ export const normalizeTextValue = value => String(value ?? '').trim();
6
+
7
+ export const isConnected = value =>
8
+ value === true ||
9
+ value === 1 ||
10
+ value === '1' ||
11
+ String(value).trim().toLowerCase() === 'true';
12
+
13
+ export const formatApiError = error =>
14
+ error?.message ||
15
+ error?.description ||
16
+ error?.error ||
17
+ tt('marketplace_error', 'mercadoLivreLoad') ||
18
+ 'Nao foi possivel carregar a integracao Mercado Livre.';
19
+
20
+ const MERCADO_LIVRE_OAUTH_QUERY_KEYS = [
21
+ 'code',
22
+ 'state',
23
+ 'error',
24
+ 'error_description',
25
+ 'mercadolivre_connected',
26
+ 'mercadolivre_error',
27
+ 'mercadolivre_message',
28
+ ];
29
+
30
+ export const resolveFrontOAuthRedirectUri = () => {
31
+ if (Platform.OS !== 'web' || typeof window === 'undefined') {
32
+ return undefined;
33
+ }
34
+
35
+ const url = new URL(window.location.href);
36
+ MERCADO_LIVRE_OAUTH_QUERY_KEYS.forEach(key => url.searchParams.delete(key));
37
+ url.hash = '';
38
+
39
+ return url.toString();
40
+ };
41
+
42
+ export const replaceFrontOAuthStatus = statusParams => {
43
+ if (Platform.OS !== 'web' || typeof window === 'undefined') {
44
+ return;
45
+ }
46
+
47
+ const url = new URL(window.location.href);
48
+ MERCADO_LIVRE_OAUTH_QUERY_KEYS.forEach(key => url.searchParams.delete(key));
49
+ Object.entries(statusParams || {}).forEach(([key, value]) => {
50
+ if (value !== undefined && value !== null && value !== '') {
51
+ url.searchParams.set(key, String(value));
52
+ }
53
+ });
54
+ window.history.replaceState({}, document.title, url.toString());
55
+ };
56
+
57
+ export const formatOAuthStatusError = (error, message) => {
58
+ const messageText = normalizeTextValue(message);
59
+ if (messageText) {
60
+ return `${
61
+ tt('marketplace_error', 'mercadoLivreConnectWithMessage') ||
62
+ 'Nao foi possivel conectar o Mercado Livre:'
63
+ } ${messageText}`;
64
+ }
65
+
66
+ const errorText = normalizeTextValue(error);
67
+ if (!errorText) {
68
+ return (
69
+ tt('marketplace_error', 'mercadoLivreConnect') ||
70
+ 'Nao foi possivel conectar o Mercado Livre.'
71
+ );
72
+ }
73
+
74
+ return `${
75
+ tt('marketplace_error', 'mercadoLivreConnect') ||
76
+ 'Nao foi possivel conectar o Mercado Livre.'
77
+ } (${errorText}).`;
78
+ };
79
+
@@ -0,0 +1,12 @@
1
+ import {Alert, Platform} from 'react-native';
2
+
3
+ export const confirmAction = (message, onConfirm) => {
4
+ if (Platform.OS === 'web' && typeof window !== 'undefined' && window.confirm) {
5
+ if (window.confirm(message)) onConfirm();
6
+ } else {
7
+ Alert.alert('Confirmação', message, [
8
+ {text: 'Cancelar', style: 'cancel'},
9
+ {text: 'Confirmar', onPress: onConfirm},
10
+ ]);
11
+ }
12
+ };
@@ -141,9 +141,23 @@ const installSession = async (page, {companyId = 3} = {}) => {
141
141
  active: 1,
142
142
  mycompany: companyId,
143
143
  roles: ['ROLE_ADMIN'],
144
+ name: 'Test User',
145
+ realname: 'Test User',
146
+ username: 'tester',
144
147
  }),
145
148
  );
146
149
  set('config', JSON.stringify({language: 'pt-br'}));
150
+ // Resolve menu labels locally; the mock API does not provide translations.
151
+ const labels = {
152
+ home: 'Início', opportunities: 'Oportunidades',
153
+ customers: 'Clientes', profile: 'Perfil',
154
+ };
155
+ const companyMessages = {
156
+ menu: {menu: labels}, configs: {toolbar: labels},
157
+ };
158
+ set('translates', JSON.stringify({
159
+ 'pt-br': {companies: {3: companyMessages, 9: companyMessages}},
160
+ }));
147
161
  set('app-type', 'MANAGER');
148
162
  set(
149
163
  'device',
@@ -64,6 +64,13 @@ const mockConnectionsApi = async page => {
64
64
  return route.fulfill({status: 204, headers: CORS_HEADERS, body: ''});
65
65
  }
66
66
 
67
+ if (pathname === 'people/companies/my') {
68
+ return route.fulfill({
69
+ status: 200, headers: jsonHeaders(),
70
+ body: JSON.stringify(collection([company])),
71
+ });
72
+ }
73
+
67
74
  if (pathname === 'companies' || pathname.startsWith('people/')) {
68
75
  return route.fulfill({
69
76
  status: 200,
@@ -103,9 +110,20 @@ const mockConnectionsApi = async page => {
103
110
  active: 1,
104
111
  mycompany: 3,
105
112
  roles: ['ROLE_ADMIN'],
113
+ name: 'Test User',
114
+ realname: 'Test User',
115
+ username: 'tester',
106
116
  }),
107
117
  );
108
118
  set('config', JSON.stringify({language: 'pt-br'}));
119
+ set('translates', JSON.stringify({
120
+ 'pt-br': {companies: {3: {configs: {
121
+ connections_title: {connections: 'Conexões'},
122
+ connections_label: {
123
+ whatsApp: 'WhatsApp', search: 'Buscar conexao, telefone ou status',
124
+ },
125
+ }}}},
126
+ }));
109
127
  set('app-type', 'ERP');
110
128
  set(
111
129
  'device',
@@ -1,3 +1,4 @@
1
+ const {installManagerSession} = require('./managerSession.fixture');
1
2
  /**
2
3
  * Smoke browser: Manager /device-detail alias save updates header without refresh.
3
4
  * fluxo: manager-devices
@@ -86,6 +87,7 @@ const MODULES_MAX_500 = [
86
87
  ];
87
88
 
88
89
  const mockDevicesApi = async (page) => {
90
+ await installManagerSession(page);
89
91
  const company = {
90
92
  id: 3,
91
93
  name: 'Teste',
@@ -1,3 +1,4 @@
1
+ const {installManagerSession} = require('./managerSession.fixture');
1
2
  /**
2
3
  * Smoke browser: Manager /device-detail copy configs from another device.
3
4
  * fluxo: manager-devices
@@ -110,6 +111,7 @@ const otherTenant = createDeviceConfig({
110
111
  });
111
112
 
112
113
  const mockCopyApi = async (page) => {
114
+ await installManagerSession(page);
113
115
  const company = {
114
116
  id: 3,
115
117
  name: 'Teste',
@@ -328,6 +330,10 @@ test.describe('device-detail copy config (browser smoke #629)', () => {
328
330
  await expect(page.getByTestId('copy-device-config-hint')).toContainText(
329
331
  DEST_ALIAS,
330
332
  );
333
+ // The translated prefix cannot supply a fixture alias: target identity is dynamic.
334
+ await expect(page.getByTestId('copy-device-config-hint')).not.toContainText(
335
+ SRC_ALIAS,
336
+ );
331
337
  await shot('02-copy-modal-picker.png');
332
338
 
333
339
  await expect(
@@ -1,3 +1,4 @@
1
+ const {installManagerSession} = require('./managerSession.fixture');
1
2
  /**
2
3
  * Smoke browser: DeviceConfig PDV detail opens without currentCompany ReferenceError.
3
4
  * fluxo: device-configuracao
@@ -91,6 +92,7 @@ const createDeviceConfig = ({id, type, device = currentDevice}) => ({
91
92
  });
92
93
 
93
94
  const mockDevicesApi = async page => {
95
+ await installManagerSession(page);
94
96
  const company = {
95
97
  id: 3,
96
98
  name: 'Teste',