@controleonline/ui-common 1.2.70 → 1.2.75

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 (188) hide show
  1. package/.github/actions/workers/manager/action.yml +147 -0
  2. package/.github/actions/workers/qa/action.yml +44 -0
  3. package/.github/actions/workers/security/action.yml +44 -0
  4. package/.github/actions/workers/technical-documenter/action.yml +51 -0
  5. package/.github/workflows/manager-worker.yml +168 -0
  6. package/.github/workflows/publish-npm.yml +45 -0
  7. package/.gitmodules +3 -0
  8. package/AGENTS.md +6 -0
  9. package/README.md +14 -0
  10. package/package.json +32 -28
  11. package/src/api/fetch.js +1 -1
  12. package/src/api/index.js +328 -58
  13. package/src/react/components/AddImportModal.js +95 -111
  14. package/src/react/components/AnimatedModal.js +171 -0
  15. package/src/react/components/AnimatedModal.styles.js +21 -0
  16. package/src/react/components/AppTypeSwitcher.js +164 -0
  17. package/src/react/components/AppTypeSwitcher.styles.js +109 -0
  18. package/src/react/components/BackgroundRuntimeBridge.js +5 -4
  19. package/src/react/components/BottomNavigationBar.config.js +211 -231
  20. package/src/react/components/BottomNavigationBar.js +86 -31
  21. package/src/react/components/BottomNavigationBar.styles.js +118 -110
  22. package/src/react/components/CategoryForm.js +52 -35
  23. package/src/react/components/DefaultProvider.native.js +194 -156
  24. package/src/react/components/DefaultProvider.web.js +188 -112
  25. package/src/react/components/DeliveryPushBridge.native.js +2 -2
  26. package/src/react/components/DeviceAlertSoundService.js +3 -3
  27. package/src/react/components/KioskModeBridge.js +2 -2
  28. package/src/react/components/LauncherModeBridge.js +2 -2
  29. package/src/react/components/ManagerPushBridge.native.js +2 -2
  30. package/src/react/components/NetworkPrinterPreviewModal.js +249 -0
  31. package/src/react/components/NetworkPrinterPreviewModal.styles.js +159 -0
  32. package/src/react/components/RemoteCheckoutService.js +28 -20
  33. package/src/react/components/RuntimeBottomNavigationBar.js +146 -119
  34. package/src/react/components/RuntimeInfoFooter.js +137 -45
  35. package/src/react/components/RuntimeInfoFooter.styles.js +3 -0
  36. package/src/react/components/StateStore.js +258 -0
  37. package/src/react/components/UnifiedPaymentBar.js +7 -4
  38. package/src/react/components/UserAvatar.js +160 -0
  39. package/src/react/components/WebsocketListener.native.js +11 -11
  40. package/src/react/components/integrations/IntegrationHero/index.js +48 -0
  41. package/src/react/components/integrations/IntegrationHero/styles.js +57 -0
  42. package/src/react/components/integrations/IntegrationTabs/index.js +89 -0
  43. package/src/react/components/integrations/IntegrationTabs/styles.js +44 -0
  44. package/src/react/config/deviceConfigBootstrap.js +71 -30
  45. package/src/react/css/orders.js +72 -0
  46. package/src/react/pages/Connections.js +229 -0
  47. package/src/react/pages/Connections.styles.js +222 -0
  48. package/src/react/pages/DeviceDetailPage.js +2814 -0
  49. package/src/react/pages/DeviceDetailPage.styles.js +308 -0
  50. package/src/react/pages/Devices/currentDevice.js +194 -0
  51. package/src/react/pages/Devices/deviceTypes/DeviceGroupCard.js +257 -0
  52. package/src/react/pages/Devices/deviceTypes/all.js +23 -0
  53. package/src/react/pages/Devices/deviceTypes/device.js +25 -0
  54. package/src/react/pages/Devices/deviceTypes/deviceListHelpers.js +178 -0
  55. package/src/react/pages/Devices/deviceTypes/display.js +26 -0
  56. package/src/react/pages/Devices/deviceTypes/index.js +145 -0
  57. package/src/react/pages/Devices/deviceTypes/ipCamera.js +35 -0
  58. package/src/react/pages/Devices/deviceTypes/pdv.js +88 -0
  59. package/src/react/pages/Devices/deviceTypes/printer.js +37 -0
  60. package/src/react/pages/Devices/deviceTypes/shared.js +453 -0
  61. package/src/react/pages/Devices/deviceTypes/useDeviceNetworkConnectivity.js +145 -0
  62. package/src/react/pages/Devices.js +183 -0
  63. package/src/react/pages/Devices.styles.js +353 -0
  64. package/src/react/pages/Food99IntegrationPage/components/Food99CatalogTab.js +219 -0
  65. package/src/react/pages/Food99IntegrationPage/components/Food99OverviewTab.js +134 -0
  66. package/src/react/pages/Food99IntegrationPage/components/Food99PreviewModal.js +173 -0
  67. package/src/react/pages/Food99IntegrationPage/components/Food99ProductCard.js +1 -0
  68. package/src/react/pages/Food99IntegrationPage/components/Food99QuickWalletModal.js +181 -0
  69. package/src/react/pages/Food99IntegrationPage/components/Food99SettingsTab.js +306 -0
  70. package/src/react/pages/Food99IntegrationPage/components/Food99StoreTab.js +417 -0
  71. package/src/react/pages/Food99IntegrationPage/index.js +1530 -0
  72. package/src/react/pages/Food99IntegrationPage/styles.js +792 -0
  73. package/src/react/pages/Food99IntegrationPage/utils.js +155 -0
  74. package/src/react/pages/Food99IntegrationPage.js +1 -0
  75. package/src/react/pages/Food99IntegrationPage.styles.js +1 -0
  76. package/src/react/pages/GenericLogPage.js +42 -167
  77. package/src/react/pages/IFoodIntegrationPage/components/IFoodCatalogTab.js +8 -0
  78. package/src/react/pages/IFoodIntegrationPage/components/IFoodOperationsTab.js +420 -0
  79. package/src/react/pages/IFoodIntegrationPage/components/IFoodOverviewTab.js +118 -0
  80. package/src/react/pages/IFoodIntegrationPage/components/IFoodPreviewModal.js +103 -0
  81. package/src/react/pages/IFoodIntegrationPage/components/IFoodProductCard.js +1 -0
  82. package/src/react/pages/IFoodIntegrationPage/components/IFoodStoreTab.js +150 -0
  83. package/src/react/pages/IFoodIntegrationPage/index.js +1298 -0
  84. package/src/react/pages/IFoodIntegrationPage/styles.js +764 -0
  85. package/src/react/pages/IFoodIntegrationPage/utils.js +91 -0
  86. package/src/react/pages/IFoodIntegrationPage.js +1 -0
  87. package/src/react/pages/IFoodIntegrationPage.styles.js +1 -0
  88. package/src/react/pages/Imports.js +214 -166
  89. package/src/react/pages/IntegrationConfigPage.js +744 -0
  90. package/src/react/pages/IntegrationConfigPage.styles.js +269 -0
  91. package/src/react/pages/Integrations.js +299 -0
  92. package/src/react/pages/Integrations.styles.js +130 -0
  93. package/src/react/pages/ManagerOrderNotificationsPage.js +642 -0
  94. package/src/react/pages/MarketplaceIntegrationPage/components/MarketplaceProductCard.js +205 -0
  95. package/src/react/pages/MarketplaceIntegrationPage/components/RemoteMarketplaceMenuTab.js +335 -0
  96. package/src/react/pages/MarketplaceIntegrationPage/index.js +60 -0
  97. package/src/react/pages/MarketplaceIntegrationPage/utils.js +137 -0
  98. package/src/react/pages/MercadoLivreIntegrationPage.js +538 -0
  99. package/src/react/pages/PrinterDeviceDetailPage.js +1916 -0
  100. package/src/react/pages/PrinterDeviceDetailPage.styles.js +223 -0
  101. package/src/react/pages/PrinterDeviceFormPage.js +521 -0
  102. package/src/react/pages/PrinterDeviceFormPage.styles.js +159 -0
  103. package/src/react/pages/SettingsPage/PaymentTypesByWalletTab.js +484 -454
  104. package/src/react/pages/SettingsPage/index.js +1266 -1167
  105. package/src/react/pages/WhatsAppConnectionPage.js +459 -0
  106. package/src/react/pages/WhatsAppConnectionPage.styles.js +284 -0
  107. package/src/react/pages/integrationsCatalog.js +399 -0
  108. package/src/react/print/providers/local.js +1 -1
  109. package/src/react/router/publicRoutes.js +25 -0
  110. package/src/react/services/Cielo/Checkout.js +39 -0
  111. package/src/react/services/Cielo/Cielo.js +193 -0
  112. package/src/react/services/Cielo/Print.js +7 -0
  113. package/src/react/services/InfinitePay/Checkout.js +33 -0
  114. package/src/react/services/InfinitePay/InfinitePay.js +24 -0
  115. package/src/react/{utils → services}/paymentGatewayExecution.js +91 -91
  116. package/src/react/styles/global.js +115 -0
  117. package/src/react/utils/categoryContexts.js +46 -0
  118. package/src/react/utils/entityDisplay.js +222 -217
  119. package/src/react/utils/fileUrl.js +201 -16
  120. package/src/react/utils/integrationPage.js +16 -0
  121. package/src/react/utils/menuNavigation.js +31 -0
  122. package/src/react/utils/orderIdentity.js +277 -0
  123. package/src/react/utils/remotePayment.js +115 -61
  124. package/src/react/utils/runtimeFooter.js +40 -22
  125. package/src/react/utils/runtimeLanguage.js +50 -9
  126. package/src/react/utils/runtimeMenu.js +190 -98
  127. package/src/react/utils/runtimeZoom.js +43 -0
  128. package/src/react/utils/shopConfig.js +72 -34
  129. package/src/react/utils/shopFranchises.js +182 -23
  130. package/src/react/utils/socketRuntimePipeline.js +14 -14
  131. package/src/react/utils/storeColumns.js +23 -4
  132. package/src/react/utils/userAvatar.js +104 -0
  133. package/src/store/categories/index.js +48 -20
  134. package/src/store/configs/index.js +2 -2
  135. package/src/store/device_config/index.js +62 -21
  136. package/src/store/entity_log/index.js +22 -1
  137. package/src/store/imports/customActions.js +51 -0
  138. package/src/store/imports/index.js +89 -88
  139. package/src/store/status/index.js +11 -11
  140. package/src/store/themes/index.js +22 -0
  141. package/src/store/timezones/index.js +3 -1
  142. package/src/tests/browser/manager/connections.spec.js +143 -0
  143. package/src/tests/browser/manager/devices-current.spec.js +343 -0
  144. package/src/tests/browser/manager/mercado-livre-integration.spec.js +268 -0
  145. package/src/tests/react/api/deviceHeaders.test.js +114 -0
  146. package/src/tests/react/api/loadSmokeIndex.test.js +75 -0
  147. package/src/tests/react/components/AddImportModal.test.js +166 -0
  148. package/src/tests/react/components/BottomNavigationBar.config.test.js +20 -0
  149. package/src/tests/react/components/UserAvatar.test.js +52 -0
  150. package/src/tests/react/config/deviceConfigBootstrap.test.js +47 -0
  151. package/src/tests/react/pages/DevicePickerStyles.test.js +47 -0
  152. package/src/tests/react/pages/Devices/deviceListHelpers.test.js +39 -0
  153. package/src/tests/react/pages/DevicesCurrentDevice.test.js +198 -0
  154. package/src/tests/react/pages/connectionsHelpers.test.js +34 -0
  155. package/src/tests/react/pages/mercadoLivreIntegrationPageContract.test.mjs +16 -0
  156. package/src/tests/react/print/localPrintProvider.test.js +1 -1
  157. package/src/tests/react/services/Cielo.test.js +190 -0
  158. package/src/tests/react/store/categoriesOrdering.test.js +26 -0
  159. package/src/tests/react/utils/appDomain.test.js +62 -0
  160. package/src/tests/react/utils/entityDisplay.test.js +2 -2
  161. package/src/tests/react/utils/fileUrl.test.js +62 -0
  162. package/src/tests/react/utils/formatter.test.mjs +33 -0
  163. package/src/tests/react/utils/importStatus.test.js +2 -2
  164. package/src/tests/react/utils/orderIdentity.test.js +139 -0
  165. package/src/tests/react/utils/remotePayment.test.js +62 -0
  166. package/src/tests/react/utils/runtimeFooter.test.js +32 -9
  167. package/src/tests/react/utils/runtimeLanguage.test.js +80 -10
  168. package/src/tests/react/utils/runtimeMenu.test.js +195 -111
  169. package/src/tests/react/utils/runtimeZoom.test.js +38 -0
  170. package/src/tests/react/utils/shopConfig.test.js +75 -0
  171. package/src/tests/react/utils/shopFranchises.test.js +122 -12
  172. package/src/tests/react/utils/translate.discovery.test.mjs +170 -0
  173. package/src/tests/react/utils/translate.test.helpers.mjs +152 -0
  174. package/src/tests/react/utils/translate.test.mjs +252 -396
  175. package/src/tests/react/utils/userAvatar.test.js +63 -0
  176. package/src/utils/appDomain.js +15 -4
  177. package/src/utils/formatter.js +122 -16
  178. package/src/utils/integrationConfigs.js +1 -0
  179. package/src/utils/translate.js +277 -398
  180. package/src/utils/translateDiscovery.js +96 -0
  181. package/src/utils/translateNormalize.js +33 -0
  182. package/src/utils/translatePending.js +92 -0
  183. package/src/utils/translateStorage.js +142 -0
  184. package/.github/agents/developer.agent.md +0 -30
  185. package/.github/agents/devops.agent.md +0 -30
  186. package/.github/agents/qa.agent.md +0 -30
  187. package/.github/agents/security.agent.md +0 -30
  188. package/.github/workflows/npmjs.yml +0 -35
@@ -0,0 +1,258 @@
1
+ import React from 'react';
2
+ import {ActivityIndicator, Text, View} from 'react-native';
3
+ import globalStyles from '../styles/global';
4
+ import {useStores} from '@store';
5
+
6
+ const resolveStateMessage = (value, fallback) => {
7
+ if (value === null || value === undefined || value === false) {
8
+ return '';
9
+ }
10
+
11
+ if (typeof value === 'boolean') {
12
+ return fallback;
13
+ }
14
+
15
+ if (typeof value === 'string') {
16
+ return value.trim() || fallback;
17
+ }
18
+
19
+ return fallback;
20
+ };
21
+
22
+ const StateStore = ({
23
+ store,
24
+ stores = [],
25
+ mode = 'default',
26
+ loading = false,
27
+ saving = false,
28
+ loadingText = 'Carregando...',
29
+ savingText = 'Salvando...',
30
+ title = '',
31
+ subtitle = '',
32
+ showSpinner = false,
33
+ spinnerColor = '#0EA5E9',
34
+ spinnerSize = 'small',
35
+ align = 'center',
36
+ compact = false,
37
+ containerStyle = null,
38
+ contentStyle = null,
39
+ messageStyle = null,
40
+ titleStyle = null,
41
+ subtitleStyle = null,
42
+ }) => {
43
+ const styles = globalStyles();
44
+ const allStores = typeof useStores === 'function' ? useStores(state => state) : null;
45
+ const storeNames = [
46
+ ...(Array.isArray(store) ? store : store ? [store] : []),
47
+ ...(Array.isArray(stores) ? stores : []),
48
+ ].filter(Boolean);
49
+ const isCompactMode = mode === 'compact';
50
+ const isDisplayMode = mode === 'display';
51
+ const isCompactLayout = compact || isCompactMode;
52
+ const containerBaseStyle = isCompactLayout
53
+ ? styles.state.compactContainer
54
+ : isDisplayMode
55
+ ? styles.state.displayContainer || styles.state.container
56
+ : styles.state.container;
57
+ const contentBaseStyle = isCompactLayout
58
+ ? styles.state.compactContent
59
+ : [
60
+ styles.state.content,
61
+ styles.state.loadingContainer,
62
+ isDisplayMode ? styles.state.displayContent : null,
63
+ ].filter(Boolean);
64
+ const textAlign = align === 'left' ? 'left' : 'center';
65
+ const alignItems = align === 'left' ? 'flex-start' : 'center';
66
+ const shouldShowSpinner = showSpinner || isCompactLayout || isDisplayMode;
67
+
68
+ if (!allStores) {
69
+ const runtimeLoadingText = resolveStateMessage(loading, loadingText);
70
+ const runtimeSavingText = resolveStateMessage(saving, savingText);
71
+ const runtimeEntries = [
72
+ runtimeLoadingText ? {kind: 'loading', label: runtimeLoadingText} : null,
73
+ runtimeSavingText ? {kind: 'saving', label: runtimeSavingText} : null,
74
+ ].filter(Boolean);
75
+
76
+ if (runtimeEntries.length === 0) {
77
+ return null;
78
+ }
79
+
80
+ return (
81
+ <View style={[containerBaseStyle, containerStyle]}>
82
+ <View style={[contentBaseStyle, {alignItems}, contentStyle]}>
83
+ {(showSpinner || title || subtitle) ? (
84
+ <View
85
+ style={{
86
+ alignItems: alignItems === 'flex-start' ? 'flex-start' : 'center',
87
+ flexDirection: 'row',
88
+ gap: 10,
89
+ justifyContent: alignItems === 'flex-start' ? 'flex-start' : 'center',
90
+ marginBottom: 12,
91
+ width: '100%',
92
+ }}
93
+ >
94
+ {showSpinner ? (
95
+ <ActivityIndicator size={spinnerSize} color={spinnerColor} />
96
+ ) : null}
97
+ {(title || subtitle) ? (
98
+ <View style={{flex: 1, gap: 2}}>
99
+ {title ? (
100
+ <Text
101
+ style={[
102
+ {
103
+ color: '#0F172A',
104
+ fontSize: 14,
105
+ fontWeight: '800',
106
+ textAlign,
107
+ },
108
+ titleStyle,
109
+ ]}
110
+ >
111
+ {title}
112
+ </Text>
113
+ ) : null}
114
+ {subtitle ? (
115
+ <Text
116
+ style={[
117
+ {
118
+ color: '#64748B',
119
+ fontSize: 12,
120
+ lineHeight: 16,
121
+ textAlign,
122
+ },
123
+ subtitleStyle,
124
+ ]}
125
+ >
126
+ {subtitle}
127
+ </Text>
128
+ ) : null}
129
+ </View>
130
+ ) : null}
131
+ </View>
132
+ ) : null}
133
+ {runtimeEntries.map(entry => (
134
+ <Text
135
+ key={`${entry.kind}-${entry.label}`}
136
+ style={[
137
+ styles.state.messageText,
138
+ {textAlign},
139
+ messageStyle,
140
+ ]}
141
+ >
142
+ {entry.label}
143
+ </Text>
144
+ ))}
145
+ </View>
146
+ </View>
147
+ );
148
+ }
149
+
150
+ const storeEntries = storeNames
151
+ .map(storeName => {
152
+ const currentStore = allStores?.[storeName] || {};
153
+ const getters = currentStore?.getters || {};
154
+
155
+ if (getters.isLoading === true) {
156
+ return {
157
+ kind: 'loading',
158
+ label: `Carregando${storeName ? `: ${storeName}` : ''}`,
159
+ };
160
+ }
161
+
162
+ if (getters.isSaving === true) {
163
+ return {
164
+ kind: 'saving',
165
+ label: `Salvando${storeName ? `: ${storeName}` : ''}`,
166
+ };
167
+ }
168
+
169
+ return null;
170
+ })
171
+ .filter(Boolean);
172
+
173
+ const runtimeEntries = [
174
+ resolveStateMessage(loading, loadingText)
175
+ ? {kind: 'loading', label: resolveStateMessage(loading, loadingText)}
176
+ : null,
177
+ resolveStateMessage(saving, savingText)
178
+ ? {kind: 'saving', label: resolveStateMessage(saving, savingText)}
179
+ : null,
180
+ ].filter(Boolean);
181
+
182
+ const entries = [...storeEntries, ...runtimeEntries];
183
+
184
+ if (entries.length === 0) {
185
+ return null;
186
+ }
187
+
188
+ return (
189
+ <View style={[containerBaseStyle, containerStyle]}>
190
+ <View style={[contentBaseStyle, {alignItems}, contentStyle]}>
191
+ {(shouldShowSpinner || title || subtitle) ? (
192
+ <View
193
+ style={{
194
+ alignItems: alignItems === 'flex-start' ? 'flex-start' : 'center',
195
+ flexDirection: 'row',
196
+ gap: 10,
197
+ justifyContent: alignItems === 'flex-start' ? 'flex-start' : 'center',
198
+ marginBottom: 12,
199
+ width: '100%',
200
+ }}
201
+ >
202
+ {shouldShowSpinner ? (
203
+ <ActivityIndicator size={spinnerSize} color={spinnerColor} />
204
+ ) : null}
205
+ {(title || subtitle) ? (
206
+ <View style={{flex: 1, gap: 2}}>
207
+ {title ? (
208
+ <Text
209
+ style={[
210
+ {
211
+ color: '#0F172A',
212
+ fontSize: 14,
213
+ fontWeight: '800',
214
+ textAlign,
215
+ },
216
+ titleStyle,
217
+ ]}
218
+ >
219
+ {title}
220
+ </Text>
221
+ ) : null}
222
+ {subtitle ? (
223
+ <Text
224
+ style={[
225
+ {
226
+ color: '#64748B',
227
+ fontSize: 12,
228
+ lineHeight: 16,
229
+ textAlign,
230
+ },
231
+ subtitleStyle,
232
+ ]}
233
+ >
234
+ {subtitle}
235
+ </Text>
236
+ ) : null}
237
+ </View>
238
+ ) : null}
239
+ </View>
240
+ ) : null}
241
+ {entries.map(entry => (
242
+ <Text
243
+ key={`${entry.kind}-${entry.label}`}
244
+ style={[
245
+ styles.state.messageText,
246
+ {textAlign},
247
+ messageStyle,
248
+ ]}
249
+ >
250
+ {entry.label}
251
+ </Text>
252
+ ))}
253
+ </View>
254
+ </View>
255
+ );
256
+ };
257
+
258
+ export default StateStore;
@@ -115,10 +115,13 @@ export default function UnifiedPaymentBar({
115
115
  );
116
116
 
117
117
  return (
118
- <TouchableOpacity
119
- key={key}
120
- disabled={disabled}
121
- onPress={action?.onPress}
118
+ <TouchableOpacity
119
+ key={key}
120
+ accessibilityLabel={action?.accessibilityLabel || action?.label}
121
+ accessibilityRole="button"
122
+ accessibilityState={{disabled}}
123
+ disabled={disabled}
124
+ onPress={action?.onPress}
122
125
  style={[
123
126
  styles.actionButton,
124
127
  visibleActions.length === 1 && styles.actionButtonSingle,
@@ -0,0 +1,160 @@
1
+ import React, {useEffect, useMemo, useState} from 'react';
2
+ import {Image, Platform, StyleSheet, Text, View} from 'react-native';
3
+ import {env as APP_ENV} from '@env';
4
+ import {getGravatarUrl, getUserInitials} from '../utils/userAvatar';
5
+
6
+ const normalizeUrl = value => String(value || '').trim();
7
+
8
+ const readSessionToken = () => {
9
+ if (typeof localStorage === 'undefined' || !localStorage?.getItem) {
10
+ return '';
11
+ }
12
+
13
+ try {
14
+ const session = JSON.parse(localStorage.getItem('session') || '{}');
15
+ return normalizeUrl(session?.api_key || session?.token);
16
+ } catch {
17
+ return '';
18
+ }
19
+ };
20
+
21
+ const useDisplayUri = uri => {
22
+ const [displayUri, setDisplayUri] = useState(normalizeUrl(uri));
23
+
24
+ useEffect(() => {
25
+ let cancelled = false;
26
+ let objectUrl = '';
27
+ const next = normalizeUrl(uri);
28
+
29
+ const run = async () => {
30
+ if (!next) {
31
+ setDisplayUri('');
32
+ return;
33
+ }
34
+
35
+ const isBackendDownload = /\/files\/[^/?#]+\/download/i.test(next);
36
+ const token = readSessionToken();
37
+
38
+ if (!isBackendDownload || Platform.OS !== 'web' || !token) {
39
+ setDisplayUri(next);
40
+ return;
41
+ }
42
+
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;
59
+ }
60
+
61
+ const blob = await response.blob();
62
+ objectUrl = URL.createObjectURL(blob);
63
+ if (!cancelled) setDisplayUri(objectUrl);
64
+ } catch {
65
+ if (!cancelled) setDisplayUri(next);
66
+ }
67
+ };
68
+
69
+ run();
70
+
71
+ return () => {
72
+ cancelled = true;
73
+ if (objectUrl) URL.revokeObjectURL(objectUrl);
74
+ };
75
+ }, [uri]);
76
+
77
+ return displayUri;
78
+ };
79
+
80
+ const UserAvatar = ({
81
+ imageUrl,
82
+ email,
83
+ name,
84
+ size = 40,
85
+ backgroundColor,
86
+ borderColor,
87
+ borderWidth = 1,
88
+ textColor,
89
+ style,
90
+ useGravatar = true,
91
+ }) => {
92
+ const sources = useMemo(
93
+ () =>
94
+ [
95
+ normalizeUrl(imageUrl),
96
+ useGravatar ? getGravatarUrl(email, Math.max(size * 2, 80)) : '',
97
+ ].filter(Boolean),
98
+ [email, imageUrl, size, useGravatar],
99
+ );
100
+ const [sourceIndex, setSourceIndex] = useState(0);
101
+
102
+ useEffect(() => {
103
+ setSourceIndex(0);
104
+ }, [sources]);
105
+
106
+ const currentSource = sources[sourceIndex];
107
+ const displayUri = useDisplayUri(currentSource);
108
+
109
+ const containerStyle = [
110
+ styles.container,
111
+ style,
112
+ {
113
+ width: size,
114
+ height: size,
115
+ borderRadius: size / 2,
116
+ backgroundColor,
117
+ borderColor,
118
+ borderWidth,
119
+ },
120
+ ];
121
+
122
+ return (
123
+ <View style={containerStyle}>
124
+ {displayUri ? (
125
+ <Image
126
+ source={{uri: displayUri}}
127
+ style={styles.image}
128
+ onError={() => setSourceIndex(index => index + 1)}
129
+ />
130
+ ) : (
131
+ <Text
132
+ style={[
133
+ styles.initials,
134
+ {color: textColor, fontSize: Math.max(Math.round(size * 0.38), 14)},
135
+ ]}>
136
+ {getUserInitials({name, email})}
137
+ </Text>
138
+ )}
139
+ </View>
140
+ );
141
+ };
142
+
143
+ const styles = StyleSheet.create({
144
+ container: {
145
+ alignItems: 'center',
146
+ justifyContent: 'center',
147
+ overflow: 'hidden',
148
+ },
149
+ image: {
150
+ width: '100%',
151
+ height: '100%',
152
+ },
153
+ initials: {
154
+ fontWeight: '700',
155
+ letterSpacing: 0.5,
156
+ textAlign: 'center',
157
+ },
158
+ });
159
+
160
+ export default UserAvatar;
@@ -74,12 +74,12 @@ export const WebsocketListener = () => {
74
74
  : 'socket:open'
75
75
  : `socket:${normalizedStatus || 'off'}`;
76
76
 
77
- const indicatorColor =
78
- socketConnected && socketIdentified
79
- ? '#10b981'
80
- : (socketConnected || ['connecting', 'identifying', 'open', 'reconnecting'].includes(normalizedStatus))
81
- ? '#e67e22'
82
- : '#c10015';
77
+ const indicatorTone =
78
+ socketConnected && socketIdentified
79
+ ? 'success'
80
+ : (socketConnected || ['connecting', 'identifying', 'open', 'reconnecting'].includes(normalizedStatus))
81
+ ? 'warning'
82
+ : 'error';
83
83
  const lastSocketStores = Array.isArray(state?.lastStores)
84
84
  ? state.lastStores.filter(Boolean).join(', ')
85
85
  : '';
@@ -88,11 +88,11 @@ export const WebsocketListener = () => {
88
88
  : '';
89
89
 
90
90
  runtimeDebugActions.setFooterEntry({
91
- key: 'socket',
92
- order: 10,
93
- indicatorColor,
94
- updatedAt: state?.updatedAt || new Date().toISOString(),
95
- lines: [
91
+ key: 'socket',
92
+ order: 10,
93
+ indicatorTone,
94
+ updatedAt: state?.updatedAt || new Date().toISOString(),
95
+ lines: [
96
96
  `Realtime ${stateLabel} | empresa: ${normalizeText(
97
97
  currentCompany?.id,
98
98
  ) || '--'} | device: ${truncateText(state?.device || device?.id || '--', 34)}`,
@@ -0,0 +1,48 @@
1
+ import React from 'react';
2
+ import { Image, Text, View } from 'react-native';
3
+ import Icon from 'react-native-vector-icons/Feather';
4
+ import { colors } from '@controleonline/../../src/styles/colors';
5
+
6
+ import createStyles from './styles';
7
+
8
+ // Hero visual compartilhado pelas telas de integração.
9
+ export default function IntegrationHero({
10
+ shadowStyle,
11
+ accentColor,
12
+ eyebrow,
13
+ title,
14
+ description,
15
+ logo,
16
+ iconName = 'layers',
17
+ palette = colors,
18
+ }) {
19
+ const styles = createStyles(palette);
20
+
21
+ return (
22
+ <View
23
+ style={[
24
+ styles.card,
25
+ shadowStyle,
26
+ { backgroundColor: palette.actionBackground || accentColor },
27
+ ]}
28
+ >
29
+ <View style={styles.copy}>
30
+ <Text style={styles.eyebrow}>{eyebrow}</Text>
31
+ <Text style={styles.title}>{title}</Text>
32
+ <Text style={styles.description}>{description}</Text>
33
+ </View>
34
+
35
+ <View style={styles.badge}>
36
+ {logo ? (
37
+ <Image source={logo} style={styles.logo} resizeMode="contain" />
38
+ ) : (
39
+ <Icon
40
+ name={iconName}
41
+ size={20}
42
+ color={palette.cardIconColor || palette.iconColor || accentColor}
43
+ />
44
+ )}
45
+ </View>
46
+ </View>
47
+ );
48
+ }
@@ -0,0 +1,57 @@
1
+ import { StyleSheet } from 'react-native';
2
+ import {colors} from '@controleonline/../../src/styles/colors';
3
+ import {withOpacity} from '@controleonline/../../src/styles/branding';
4
+
5
+ const createStyles = (palette = colors) =>
6
+ StyleSheet.create({
7
+ card: {
8
+ borderRadius: 24,
9
+ padding: 22,
10
+ marginBottom: 20,
11
+ overflow: 'hidden',
12
+ flexDirection: 'row',
13
+ justifyContent: 'space-between',
14
+ alignItems: 'flex-start',
15
+ },
16
+ copy: {
17
+ flex: 1,
18
+ paddingRight: 16,
19
+ },
20
+ eyebrow: {
21
+ fontSize: 11,
22
+ fontWeight: '800',
23
+ color: withOpacity(palette.actionText || palette.white, 0.72),
24
+ textTransform: 'uppercase',
25
+ letterSpacing: 1.2,
26
+ },
27
+ title: {
28
+ marginTop: 10,
29
+ fontSize: 28,
30
+ fontWeight: '800',
31
+ color: palette.actionText || palette.white,
32
+ letterSpacing: -0.6,
33
+ },
34
+ description: {
35
+ marginTop: 10,
36
+ fontSize: 14,
37
+ lineHeight: 22,
38
+ color: withOpacity(palette.actionText || palette.white, 0.86),
39
+ },
40
+ badge: {
41
+ position: 'absolute',
42
+ top: 18,
43
+ right: 18,
44
+ width: 48,
45
+ height: 48,
46
+ borderRadius: 16,
47
+ backgroundColor: palette.cardIconBackground || palette.iconBackground || palette.white,
48
+ alignItems: 'center',
49
+ justifyContent: 'center',
50
+ },
51
+ logo: {
52
+ width: 24,
53
+ height: 24,
54
+ },
55
+ });
56
+
57
+ export default createStyles;
@@ -0,0 +1,89 @@
1
+ import React from 'react';
2
+ import { ScrollView, Text, TouchableOpacity, View } from 'react-native';
3
+
4
+ import { colors } from '@controleonline/../../src/styles/colors';
5
+
6
+ import createStyles from './styles';
7
+
8
+ // Abas horizontais simples para reduzir densidade visual das páginas.
9
+ export default function IntegrationTabs({ tabs, activeKey, onChange, accentColor, palette = colors }) {
10
+ const styles = createStyles(palette);
11
+ const resolvedPrimaryBackground =
12
+ palette.buttonBackground ||
13
+ accentColor ||
14
+ palette.primary;
15
+ const resolvedPrimaryBorder =
16
+ palette.buttonBorder ||
17
+ resolvedPrimaryBackground;
18
+ const resolvedPrimaryText =
19
+ palette.buttonText ||
20
+ palette.white;
21
+ const resolvedSecondaryBackground =
22
+ palette.buttonBackgroundSecondary ||
23
+ palette.white;
24
+ const resolvedSecondaryBorder =
25
+ palette.buttonBorderSecondary ||
26
+ palette.border;
27
+ const resolvedSecondaryText =
28
+ palette.buttonTextSecondary ||
29
+ palette.textSecondary;
30
+ const resolvedSecondaryIcon =
31
+ palette.buttonIconSecondary ||
32
+ resolvedSecondaryText;
33
+
34
+ return (
35
+ <ScrollView
36
+ horizontal
37
+ showsHorizontalScrollIndicator={false}
38
+ contentContainerStyle={styles.scrollContent}>
39
+ {tabs.map(tab => {
40
+ const active = tab.key === activeKey;
41
+ const tabColors = active
42
+ ? {
43
+ backgroundColor: resolvedPrimaryBackground,
44
+ borderColor: resolvedPrimaryBorder,
45
+ textColor: resolvedPrimaryText,
46
+ badgeBackground: palette.buttonPressedBackground || resolvedPrimaryBorder,
47
+ badgeText: resolvedPrimaryText,
48
+ }
49
+ : {
50
+ backgroundColor: resolvedSecondaryBackground,
51
+ borderColor: resolvedSecondaryBorder,
52
+ textColor: resolvedSecondaryText,
53
+ badgeBackground: palette.badgeBackground || resolvedSecondaryBackground,
54
+ badgeText: resolvedSecondaryIcon,
55
+ };
56
+
57
+ return (
58
+ <TouchableOpacity
59
+ key={tab.key}
60
+ activeOpacity={0.85}
61
+ style={[
62
+ styles.tabButton,
63
+ {
64
+ backgroundColor: tabColors.backgroundColor,
65
+ borderColor: tabColors.borderColor,
66
+ },
67
+ ]}
68
+ onPress={() => onChange(tab.key)}>
69
+ <Text style={[styles.tabLabel, { color: tabColors.textColor }]}>
70
+ {tab.label}
71
+ </Text>
72
+
73
+ {tab.badge !== undefined && tab.badge !== null && tab.badge !== '' && (
74
+ <View
75
+ style={[
76
+ styles.tabBadge,
77
+ { backgroundColor: tabColors.badgeBackground },
78
+ ]}>
79
+ <Text style={[styles.tabBadgeText, { color: tabColors.badgeText }]}>
80
+ {tab.badge}
81
+ </Text>
82
+ </View>
83
+ )}
84
+ </TouchableOpacity>
85
+ );
86
+ })}
87
+ </ScrollView>
88
+ );
89
+ }