@controleonline/ui-common 1.2.71 → 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 (150) 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 +3 -2
  11. package/src/api/fetch.js +1 -1
  12. package/src/api/index.js +92 -22
  13. package/src/react/components/AddImportModal.js +94 -110
  14. package/src/react/components/BottomNavigationBar.config.js +211 -231
  15. package/src/react/components/CategoryForm.js +52 -35
  16. package/src/react/components/DefaultProvider.native.js +131 -95
  17. package/src/react/components/DefaultProvider.web.js +148 -74
  18. package/src/react/components/NetworkPrinterPreviewModal.js +249 -0
  19. package/src/react/components/NetworkPrinterPreviewModal.styles.js +159 -0
  20. package/src/react/components/RuntimeInfoFooter.js +100 -36
  21. package/src/react/components/RuntimeInfoFooter.styles.js +3 -0
  22. package/src/react/components/UnifiedPaymentBar.js +7 -4
  23. package/src/react/components/UserAvatar.js +160 -0
  24. package/src/react/components/integrations/IntegrationHero/index.js +48 -0
  25. package/src/react/components/integrations/IntegrationHero/styles.js +57 -0
  26. package/src/react/components/integrations/IntegrationTabs/index.js +89 -0
  27. package/src/react/components/integrations/IntegrationTabs/styles.js +44 -0
  28. package/src/react/config/deviceConfigBootstrap.js +5 -3
  29. package/src/react/pages/Connections.js +229 -0
  30. package/src/react/pages/Connections.styles.js +222 -0
  31. package/src/react/pages/DeviceDetailPage.js +2814 -0
  32. package/src/react/pages/DeviceDetailPage.styles.js +308 -0
  33. package/src/react/pages/Devices/currentDevice.js +194 -0
  34. package/src/react/pages/Devices/deviceTypes/DeviceGroupCard.js +257 -0
  35. package/src/react/pages/Devices/deviceTypes/all.js +23 -0
  36. package/src/react/pages/Devices/deviceTypes/device.js +25 -0
  37. package/src/react/pages/Devices/deviceTypes/deviceListHelpers.js +178 -0
  38. package/src/react/pages/Devices/deviceTypes/display.js +26 -0
  39. package/src/react/pages/Devices/deviceTypes/index.js +145 -0
  40. package/src/react/pages/Devices/deviceTypes/ipCamera.js +35 -0
  41. package/src/react/pages/Devices/deviceTypes/pdv.js +88 -0
  42. package/src/react/pages/Devices/deviceTypes/printer.js +37 -0
  43. package/src/react/pages/Devices/deviceTypes/shared.js +453 -0
  44. package/src/react/pages/Devices/deviceTypes/useDeviceNetworkConnectivity.js +145 -0
  45. package/src/react/pages/Devices.js +183 -0
  46. package/src/react/pages/Devices.styles.js +353 -0
  47. package/src/react/pages/Food99IntegrationPage/components/Food99CatalogTab.js +219 -0
  48. package/src/react/pages/Food99IntegrationPage/components/Food99OverviewTab.js +134 -0
  49. package/src/react/pages/Food99IntegrationPage/components/Food99PreviewModal.js +173 -0
  50. package/src/react/pages/Food99IntegrationPage/components/Food99ProductCard.js +1 -0
  51. package/src/react/pages/Food99IntegrationPage/components/Food99QuickWalletModal.js +181 -0
  52. package/src/react/pages/Food99IntegrationPage/components/Food99SettingsTab.js +306 -0
  53. package/src/react/pages/Food99IntegrationPage/components/Food99StoreTab.js +417 -0
  54. package/src/react/pages/Food99IntegrationPage/index.js +1530 -0
  55. package/src/react/pages/Food99IntegrationPage/styles.js +792 -0
  56. package/src/react/pages/Food99IntegrationPage/utils.js +155 -0
  57. package/src/react/pages/Food99IntegrationPage.js +1 -0
  58. package/src/react/pages/Food99IntegrationPage.styles.js +1 -0
  59. package/src/react/pages/GenericLogPage.js +42 -167
  60. package/src/react/pages/IFoodIntegrationPage/components/IFoodCatalogTab.js +8 -0
  61. package/src/react/pages/IFoodIntegrationPage/components/IFoodOperationsTab.js +420 -0
  62. package/src/react/pages/IFoodIntegrationPage/components/IFoodOverviewTab.js +118 -0
  63. package/src/react/pages/IFoodIntegrationPage/components/IFoodPreviewModal.js +103 -0
  64. package/src/react/pages/IFoodIntegrationPage/components/IFoodProductCard.js +1 -0
  65. package/src/react/pages/IFoodIntegrationPage/components/IFoodStoreTab.js +150 -0
  66. package/src/react/pages/IFoodIntegrationPage/index.js +1298 -0
  67. package/src/react/pages/IFoodIntegrationPage/styles.js +764 -0
  68. package/src/react/pages/IFoodIntegrationPage/utils.js +91 -0
  69. package/src/react/pages/IFoodIntegrationPage.js +1 -0
  70. package/src/react/pages/IFoodIntegrationPage.styles.js +1 -0
  71. package/src/react/pages/Imports.js +214 -166
  72. package/src/react/pages/IntegrationConfigPage.js +744 -0
  73. package/src/react/pages/IntegrationConfigPage.styles.js +269 -0
  74. package/src/react/pages/Integrations.js +299 -0
  75. package/src/react/pages/Integrations.styles.js +130 -0
  76. package/src/react/pages/ManagerOrderNotificationsPage.js +642 -0
  77. package/src/react/pages/MarketplaceIntegrationPage/components/MarketplaceProductCard.js +205 -0
  78. package/src/react/pages/MarketplaceIntegrationPage/components/RemoteMarketplaceMenuTab.js +335 -0
  79. package/src/react/pages/MarketplaceIntegrationPage/index.js +60 -0
  80. package/src/react/pages/MarketplaceIntegrationPage/utils.js +137 -0
  81. package/src/react/pages/MercadoLivreIntegrationPage.js +538 -0
  82. package/src/react/pages/PrinterDeviceDetailPage.js +1916 -0
  83. package/src/react/pages/PrinterDeviceDetailPage.styles.js +223 -0
  84. package/src/react/pages/PrinterDeviceFormPage.js +521 -0
  85. package/src/react/pages/PrinterDeviceFormPage.styles.js +159 -0
  86. package/src/react/pages/WhatsAppConnectionPage.js +459 -0
  87. package/src/react/pages/WhatsAppConnectionPage.styles.js +284 -0
  88. package/src/react/pages/integrationsCatalog.js +399 -0
  89. package/src/react/services/Cielo/Cielo.js +193 -193
  90. package/src/react/utils/categoryContexts.js +46 -0
  91. package/src/react/utils/entityDisplay.js +222 -217
  92. package/src/react/utils/fileUrl.js +39 -20
  93. package/src/react/utils/integrationPage.js +16 -0
  94. package/src/react/utils/runtimeFooter.js +40 -22
  95. package/src/react/utils/runtimeLanguage.js +50 -9
  96. package/src/react/utils/runtimeMenu.js +191 -131
  97. package/src/react/utils/runtimeZoom.js +43 -0
  98. package/src/react/utils/shopConfig.js +27 -15
  99. package/src/react/utils/shopFranchises.js +126 -1
  100. package/src/react/utils/storeColumns.js +23 -4
  101. package/src/react/utils/userAvatar.js +104 -0
  102. package/src/store/categories/index.js +48 -20
  103. package/src/store/device_config/index.js +62 -21
  104. package/src/store/entity_log/index.js +22 -1
  105. package/src/store/imports/customActions.js +51 -0
  106. package/src/store/imports/index.js +89 -88
  107. package/src/store/status/index.js +11 -11
  108. package/src/store/themes/index.js +22 -0
  109. package/src/store/timezones/index.js +3 -1
  110. package/src/tests/browser/manager/connections.spec.js +143 -0
  111. package/src/tests/browser/manager/devices-current.spec.js +343 -0
  112. package/src/tests/browser/manager/mercado-livre-integration.spec.js +268 -0
  113. package/src/tests/react/api/deviceHeaders.test.js +114 -0
  114. package/src/tests/react/components/AddImportModal.test.js +166 -0
  115. package/src/tests/react/components/BottomNavigationBar.config.test.js +20 -0
  116. package/src/tests/react/components/UserAvatar.test.js +52 -0
  117. package/src/tests/react/pages/DevicePickerStyles.test.js +47 -0
  118. package/src/tests/react/pages/Devices/deviceListHelpers.test.js +39 -0
  119. package/src/tests/react/pages/DevicesCurrentDevice.test.js +198 -0
  120. package/src/tests/react/pages/connectionsHelpers.test.js +34 -0
  121. package/src/tests/react/pages/mercadoLivreIntegrationPageContract.test.mjs +16 -0
  122. package/src/tests/react/services/Cielo.test.js +190 -190
  123. package/src/tests/react/store/categoriesOrdering.test.js +26 -0
  124. package/src/tests/react/utils/appDomain.test.js +62 -0
  125. package/src/tests/react/utils/entityDisplay.test.js +2 -2
  126. package/src/tests/react/utils/fileUrl.test.js +3 -3
  127. package/src/tests/react/utils/formatter.test.mjs +33 -0
  128. package/src/tests/react/utils/runtimeFooter.test.js +29 -4
  129. package/src/tests/react/utils/runtimeLanguage.test.js +79 -9
  130. package/src/tests/react/utils/runtimeMenu.test.js +198 -134
  131. package/src/tests/react/utils/runtimeZoom.test.js +38 -0
  132. package/src/tests/react/utils/shopConfig.test.js +10 -9
  133. package/src/tests/react/utils/shopFranchises.test.js +43 -0
  134. package/src/tests/react/utils/translate.discovery.test.mjs +170 -0
  135. package/src/tests/react/utils/translate.test.helpers.mjs +152 -0
  136. package/src/tests/react/utils/translate.test.mjs +141 -349
  137. package/src/tests/react/utils/userAvatar.test.js +63 -0
  138. package/src/utils/appDomain.js +15 -4
  139. package/src/utils/formatter.js +104 -16
  140. package/src/utils/integrationConfigs.js +80 -80
  141. package/src/utils/translate.js +222 -438
  142. package/src/utils/translateDiscovery.js +96 -0
  143. package/src/utils/translateNormalize.js +33 -0
  144. package/src/utils/translatePending.js +92 -0
  145. package/src/utils/translateStorage.js +142 -0
  146. package/.github/agents/developer.agent.md +0 -30
  147. package/.github/agents/devops.agent.md +0 -30
  148. package/.github/agents/qa.agent.md +0 -30
  149. package/.github/agents/security.agent.md +0 -30
  150. package/.github/workflows/npmjs.yml +0 -35
@@ -0,0 +1,269 @@
1
+ import { StyleSheet } from 'react-native';
2
+
3
+ const styles = StyleSheet.create({
4
+ container: {
5
+ flex: 1,
6
+ backgroundColor: '#F8FAFC',
7
+ },
8
+ scroll: {
9
+ paddingHorizontal: 20,
10
+ paddingTop: 16,
11
+ paddingBottom: 32,
12
+ gap: 16,
13
+ },
14
+ centerState: {
15
+ flex: 1,
16
+ alignItems: 'center',
17
+ justifyContent: 'center',
18
+ paddingHorizontal: 32,
19
+ gap: 10,
20
+ },
21
+ centerStateTitle: {
22
+ fontSize: 20,
23
+ fontWeight: '700',
24
+ color: '#0F172A',
25
+ textAlign: 'center',
26
+ },
27
+ centerStateText: {
28
+ fontSize: 14,
29
+ color: '#64748B',
30
+ textAlign: 'center',
31
+ lineHeight: 20,
32
+ },
33
+ heroCard: {
34
+ borderRadius: 24,
35
+ padding: 20,
36
+ flexDirection: 'row',
37
+ justifyContent: 'space-between',
38
+ alignItems: 'flex-start',
39
+ },
40
+ heroCopy: {
41
+ flex: 1,
42
+ paddingRight: 16,
43
+ },
44
+ heroEyebrow: {
45
+ fontSize: 12,
46
+ fontWeight: '700',
47
+ color: 'rgba(255,255,255,0.78)',
48
+ textTransform: 'uppercase',
49
+ letterSpacing: 1,
50
+ marginBottom: 8,
51
+ },
52
+ heroTitle: {
53
+ fontSize: 26,
54
+ fontWeight: '800',
55
+ color: '#FFFFFF',
56
+ marginBottom: 8,
57
+ letterSpacing: -0.6,
58
+ },
59
+ heroText: {
60
+ fontSize: 14,
61
+ lineHeight: 21,
62
+ color: 'rgba(255,255,255,0.88)',
63
+ },
64
+ heroBadge: {
65
+ width: 52,
66
+ height: 52,
67
+ borderRadius: 18,
68
+ backgroundColor: '#FFFFFF',
69
+ alignItems: 'center',
70
+ justifyContent: 'center',
71
+ },
72
+ statusCard: {
73
+ borderRadius: 22,
74
+ backgroundColor: '#FFFFFF',
75
+ padding: 16,
76
+ },
77
+ statusHeader: {
78
+ flexDirection: 'row',
79
+ alignItems: 'flex-start',
80
+ justifyContent: 'space-between',
81
+ gap: 12,
82
+ },
83
+ statusCopy: {
84
+ flex: 1,
85
+ },
86
+ sectionTitle: {
87
+ fontSize: 16,
88
+ fontWeight: '800',
89
+ color: '#0F172A',
90
+ marginBottom: 4,
91
+ },
92
+ sectionSubtitle: {
93
+ fontSize: 13,
94
+ lineHeight: 19,
95
+ color: '#64748B',
96
+ },
97
+ statusBadge: {
98
+ borderRadius: 999,
99
+ paddingHorizontal: 12,
100
+ paddingVertical: 8,
101
+ },
102
+ statusBadgeText: {
103
+ fontSize: 12,
104
+ fontWeight: '700',
105
+ },
106
+ formCard: {
107
+ borderRadius: 22,
108
+ backgroundColor: '#FFFFFF',
109
+ padding: 16,
110
+ gap: 14,
111
+ },
112
+ cardTitle: {
113
+ fontSize: 20,
114
+ fontWeight: '800',
115
+ color: '#0F172A',
116
+ },
117
+ cardSubtitle: {
118
+ fontSize: 13,
119
+ lineHeight: 20,
120
+ color: '#64748B',
121
+ },
122
+ fieldList: {
123
+ gap: 14,
124
+ },
125
+ fieldGroup: {
126
+ gap: 8,
127
+ },
128
+ fieldLabel: {
129
+ fontSize: 14,
130
+ fontWeight: '800',
131
+ color: '#0F172A',
132
+ },
133
+ fieldKey: {
134
+ fontSize: 12,
135
+ fontWeight: '600',
136
+ color: '#64748B',
137
+ },
138
+ input: {
139
+ minHeight: 50,
140
+ borderRadius: 16,
141
+ borderWidth: 1,
142
+ borderColor: '#E2E8F0',
143
+ backgroundColor: '#F8FAFC',
144
+ paddingHorizontal: 14,
145
+ paddingVertical: 12,
146
+ fontSize: 15,
147
+ fontWeight: '600',
148
+ color: '#0F172A',
149
+ },
150
+ inputDisabled: {
151
+ opacity: 0.7,
152
+ },
153
+ embeddedHeader: {
154
+ borderRadius: 18,
155
+ backgroundColor: '#FFFFFF',
156
+ borderWidth: 1,
157
+ borderColor: '#E2E8F0',
158
+ paddingHorizontal: 16,
159
+ paddingVertical: 14,
160
+ marginBottom: 4,
161
+ },
162
+ embeddedTitle: {
163
+ fontSize: 18,
164
+ fontWeight: '800',
165
+ color: '#0F172A',
166
+ },
167
+ subTabRow: {
168
+ flexDirection: 'row',
169
+ flexWrap: 'wrap',
170
+ gap: 8,
171
+ marginBottom: 4,
172
+ },
173
+ subTabButton: {
174
+ paddingHorizontal: 12,
175
+ paddingVertical: 8,
176
+ borderRadius: 999,
177
+ borderWidth: 1,
178
+ borderColor: '#E2E8F0',
179
+ backgroundColor: '#F8FAFC',
180
+ },
181
+ subTabButtonActive: {
182
+ borderColor: '#166534',
183
+ backgroundColor: '#DCFCE7',
184
+ },
185
+ subTabLabel: {
186
+ fontSize: 12,
187
+ fontWeight: '700',
188
+ color: '#64748B',
189
+ },
190
+ subTabLabelActive: {
191
+ color: '#166534',
192
+ },
193
+ tabDescription: {
194
+ fontSize: 13,
195
+ fontWeight: '500',
196
+ color: '#64748B',
197
+ lineHeight: 18,
198
+ marginBottom: 8,
199
+ },
200
+ fileFieldWrap: {
201
+ gap: 8,
202
+ },
203
+ filePickerButton: {
204
+ minHeight: 48,
205
+ borderRadius: 14,
206
+ borderWidth: 1,
207
+ borderColor: '#BBF7D0',
208
+ backgroundColor: '#F0FDF4',
209
+ paddingHorizontal: 14,
210
+ flexDirection: 'row',
211
+ alignItems: 'center',
212
+ gap: 10,
213
+ },
214
+ filePickerButtonText: {
215
+ fontSize: 14,
216
+ fontWeight: '700',
217
+ color: '#166534',
218
+ },
219
+ fieldHint: {
220
+ marginTop: 8,
221
+ fontSize: 12,
222
+ fontWeight: '500',
223
+ color: '#64748B',
224
+ lineHeight: 18,
225
+ },
226
+ selectList: {
227
+ gap: 8,
228
+ },
229
+ selectOption: {
230
+ minHeight: 44,
231
+ borderRadius: 14,
232
+ borderWidth: 1,
233
+ borderColor: '#E2E8F0',
234
+ backgroundColor: '#F8FAFC',
235
+ paddingHorizontal: 14,
236
+ justifyContent: 'center',
237
+ },
238
+ selectOptionActive: {
239
+ borderColor: '#166534',
240
+ backgroundColor: '#DCFCE7',
241
+ },
242
+ selectOptionText: {
243
+ fontSize: 14,
244
+ fontWeight: '600',
245
+ color: '#0F172A',
246
+ },
247
+ selectOptionTextActive: {
248
+ color: '#166534',
249
+ },
250
+ saveButton: {
251
+ minHeight: 52,
252
+ borderRadius: 18,
253
+ alignItems: 'center',
254
+ justifyContent: 'center',
255
+ flexDirection: 'row',
256
+ gap: 10,
257
+ paddingHorizontal: 18,
258
+ },
259
+ saveButtonDisabled: {
260
+ opacity: 0.65,
261
+ },
262
+ saveButtonText: {
263
+ fontSize: 15,
264
+ fontWeight: '800',
265
+ color: '#FFFFFF',
266
+ },
267
+ });
268
+
269
+ export default styles;
@@ -0,0 +1,299 @@
1
+ import React, {useCallback, useMemo, useState} from 'react';
2
+ import {
3
+ ActivityIndicator,
4
+ Image,
5
+ Platform,
6
+ RefreshControl,
7
+ ScrollView,
8
+ Text,
9
+ TouchableOpacity,
10
+ View,
11
+ } from 'react-native';
12
+ import {SafeAreaView} from 'react-native-safe-area-context';
13
+ import {useFocusEffect} from '@react-navigation/native';
14
+ import Icon from 'react-native-vector-icons/Feather';
15
+
16
+ import {api} from '@controleonline/ui-common/src/api';
17
+ import useToastMessage from '@controleonline/ui-crm/src/react/hooks/useToastMessage';
18
+ import {useStore} from '@store';
19
+ import {colors} from '@controleonline/../../src/styles/colors';
20
+ import {
21
+ resolveThemePalette,
22
+ withOpacity,
23
+ } from '@controleonline/../../src/styles/branding';
24
+
25
+ import {
26
+ INTEGRATION_LIST,
27
+ parseIntegrationCollection,
28
+ } from './integrationsCatalog';
29
+ import { createStyles } from './Integrations.styles';
30
+
31
+ const tt = (type, key) => global.t?.t('configs', type, key);
32
+
33
+ const resolveIntegrationColors = brandColors => ({
34
+ ...brandColors,
35
+ background: brandColors.background || '#F8FAFC',
36
+ cardBackground: brandColors.cardBackground || brandColors.surface || '#FFFFFF',
37
+ cardBorder: brandColors.cardBorder || brandColors.border || '#E2E8F0',
38
+ cardIconBackground: brandColors.cardIconBackground || brandColors.iconBackground || '#EEF2FF',
39
+ cardIconColor: brandColors.cardIconColor || brandColors.iconColor || '#0F172A',
40
+ cardText: brandColors.cardText || brandColors.textPrimary || brandColors.text || '#0F172A',
41
+ text: brandColors.textPrimary || brandColors.text || '#0F172A',
42
+ mutedText: brandColors.textMuted || brandColors.textSecondary || '#64748B',
43
+ badgeBackground: brandColors.badgeBackground || brandColors.buttonBackgroundSecondary || '#F8FAFC',
44
+ badgeBorder: brandColors.badgeBorder || brandColors.buttonBorderSecondary || '#E2E8F0',
45
+ badgeText: brandColors.badgeText || brandColors.textSecondary || '#64748B',
46
+ badgeSelectedBackground: brandColors.badgeSelectedBackground || brandColors.buttonBackground || '#EFF6FF',
47
+ badgeSelectedBorder: brandColors.badgeSelectedBorder || brandColors.buttonBorder || '#BFDBFE',
48
+ badgeSelectedText: brandColors.badgeSelectedText || brandColors.buttonText || '#0F172A',
49
+ textSuccess: brandColors.textSuccess || '#16A34A',
50
+ textWarning: brandColors.textWarning || '#B45309',
51
+ iconColor: brandColors.iconColor || brandColors.text || '#0F172A',
52
+ });
53
+
54
+ const formatApiError = error => {
55
+ if (!error) return tt('integrations_error', 'load') || 'Nao foi possivel carregar as integracoes.';
56
+ if (typeof error === 'string') return error;
57
+ return (
58
+ error?.message ||
59
+ error?.description ||
60
+ error?.errmsg ||
61
+ tt('integrations_error', 'load') ||
62
+ 'Nao foi possivel carregar as integracoes.'
63
+ );
64
+ };
65
+
66
+ const isConnectedValue = value =>
67
+ value === true ||
68
+ value === 1 ||
69
+ value === '1' ||
70
+ String(value).trim().toLowerCase() === 'true';
71
+
72
+ const renderIntegrationIcon = (integration, resolvedStyles, iconColor) => {
73
+ if (integration.logo) {
74
+ return (
75
+ <Image
76
+ source={integration.logo}
77
+ style={resolvedStyles.integrationLogo}
78
+ resizeMode="contain"
79
+ />
80
+ );
81
+ }
82
+
83
+ return (
84
+ <Icon
85
+ name={integration.icon || 'box'}
86
+ size={20}
87
+ color={iconColor}
88
+ />
89
+ );
90
+ };
91
+
92
+ export default function IntegrationsPage({navigation}) {
93
+ const peopleStore = useStore('people');
94
+ const themeStore = useStore('theme');
95
+ const {currentCompany} = peopleStore.getters;
96
+ const {colors: themeColors} = themeStore.getters;
97
+ const {showError, showInfo} = useToastMessage();
98
+
99
+ const brandColors = useMemo(
100
+ () =>
101
+ resolveThemePalette(
102
+ {
103
+ ...themeColors,
104
+ ...(currentCompany?.theme?.colors || {}),
105
+ },
106
+ colors,
107
+ ),
108
+ [themeColors, currentCompany?.id],
109
+ );
110
+ const integrationColors = useMemo(
111
+ () => resolveIntegrationColors(brandColors),
112
+ [brandColors],
113
+ );
114
+ const styles = useMemo(
115
+ () => createStyles(integrationColors),
116
+ [integrationColors],
117
+ );
118
+
119
+ const [loading, setLoading] = useState(true);
120
+ const [refreshing, setRefreshing] = useState(false);
121
+ const [integrationItems, setIntegrationItems] = useState([]);
122
+
123
+ const providerId = currentCompany?.id;
124
+
125
+ const integrationCards = useMemo(() => {
126
+ const responseMap = new Map(
127
+ (integrationItems || []).map(item => [item?.key, item]),
128
+ );
129
+
130
+ return INTEGRATION_LIST.map(item => {
131
+ const responseItem = responseMap.get(item.key);
132
+
133
+ return {
134
+ ...item,
135
+ connected: isConnectedValue(responseItem?.connected),
136
+ };
137
+ });
138
+ }, [integrationItems]);
139
+
140
+ const loadIntegrations = useCallback(async () => {
141
+ if (!providerId) {
142
+ setLoading(false);
143
+ return;
144
+ }
145
+
146
+ try {
147
+ const response = await api.fetch('/marketplace/integrations', {
148
+ params: {provider_id: providerId},
149
+ });
150
+
151
+ setIntegrationItems(parseIntegrationCollection(response));
152
+ } catch (error) {
153
+ showError(formatApiError(error));
154
+ setIntegrationItems([]);
155
+ } finally {
156
+ setLoading(false);
157
+ }
158
+ }, [providerId, showError]);
159
+
160
+ useFocusEffect(
161
+ useCallback(() => {
162
+ setLoading(true);
163
+ loadIntegrations();
164
+ }, [loadIntegrations]),
165
+ );
166
+
167
+ const onRefresh = useCallback(async () => {
168
+ setRefreshing(true);
169
+ try {
170
+ await loadIntegrations();
171
+ } finally {
172
+ setRefreshing(false);
173
+ }
174
+ }, [loadIntegrations]);
175
+
176
+ const handleOpenIntegration = useCallback(
177
+ integration => {
178
+ if (!integration.route) {
179
+ showInfo(tt('integrations_text', 'unavailable') || 'Essa integracao ainda nao esta disponivel.');
180
+ return;
181
+ }
182
+
183
+ navigation.navigate(integration.route, {
184
+ providerKey: integration.routeParams?.providerKey || integration.key,
185
+ });
186
+ },
187
+ [navigation, showInfo],
188
+ );
189
+
190
+ if (!providerId) {
191
+ return (
192
+ <SafeAreaView style={styles.container} edges={['bottom']}>
193
+ <View style={styles.centerState}>
194
+ <Icon name="building" size={32} color={integrationColors.iconColor} />
195
+ <Text style={styles.centerStateTitle}>{tt('integrations_title', 'selectCompany') || 'Selecione uma empresa'}</Text>
196
+ <Text style={styles.centerStateText}>
197
+ {tt('integrations_text', 'selectCompany') || 'O hub de integracoes depende da empresa ativa.'}
198
+ </Text>
199
+ </View>
200
+ </SafeAreaView>
201
+ );
202
+ }
203
+
204
+ if (loading) {
205
+ return (
206
+ <SafeAreaView
207
+ style={[styles.container, {backgroundColor: brandColors.background}]}
208
+ edges={['bottom']}>
209
+ <View style={styles.centerState}>
210
+ <ActivityIndicator size="large" color={brandColors.primary} />
211
+ <Text style={styles.centerStateTitle}>{tt('integrations_title', 'loading') || 'Carregando integracoes'}</Text>
212
+ <Text style={styles.centerStateText}>
213
+ {tt('integrations_text', 'loading') || 'Buscando o status de conexao da empresa ativa.'}
214
+ </Text>
215
+ </View>
216
+ </SafeAreaView>
217
+ );
218
+ }
219
+
220
+ return (
221
+ <SafeAreaView
222
+ style={[styles.container, {backgroundColor: brandColors.background}]}
223
+ edges={['bottom']}>
224
+ <ScrollView
225
+ contentContainerStyle={styles.scroll}
226
+ showsVerticalScrollIndicator={false}
227
+ refreshControl={
228
+ <RefreshControl
229
+ refreshing={refreshing}
230
+ onRefresh={onRefresh}
231
+ tintColor={brandColors.primary}
232
+ />
233
+ }>
234
+ <View style={styles.pageHeader}>
235
+ <Text style={styles.pageTitle}>{tt('integrations_title', 'integrations') || 'Integracoes'}</Text>
236
+ <Text style={styles.pageSubtitle}>
237
+ {tt('integrations_text', 'pageSubtitle') ||
238
+ 'Toque em uma integracao para abrir a configuracao. O status mostra se a empresa ativa ja tem as credenciais necessarias.'}
239
+ </Text>
240
+ </View>
241
+
242
+ <View style={styles.integrationGrid}>
243
+ {integrationCards.map(integration => {
244
+ const connected = Boolean(integration.connected);
245
+ const statusTone = connected
246
+ ? (integrationColors.textSuccess || integrationColors.badgeSelectedText)
247
+ : (integrationColors.textWarning || integrationColors.badgeText);
248
+ const statusText = connected
249
+ ? tt('integrations_status', 'connected') || 'Conectado'
250
+ : tt('integrations_status', 'pending') || 'Pendente';
251
+ const statusBackgroundColor = connected
252
+ ? (integrationColors.badgeSelectedBackground || withOpacity(statusTone, 0.12))
253
+ : (integrationColors.badgeBackground || withOpacity(statusTone, 0.12));
254
+ const statusBorderColor = connected
255
+ ? (integrationColors.badgeSelectedBorder || withOpacity(statusTone, 0.22))
256
+ : (integrationColors.badgeBorder || withOpacity(statusTone, 0.22));
257
+
258
+ return (
259
+ <TouchableOpacity
260
+ key={integration.key}
261
+ style={styles.integrationCard}
262
+ activeOpacity={0.9}
263
+ onPress={() => handleOpenIntegration(integration)}>
264
+ <View style={styles.integrationTopRow}>
265
+ <View style={styles.integrationHeaderLeft}>
266
+ <View style={styles.integrationIconWrap}>
267
+ {renderIntegrationIcon(integration, styles, integrationColors.cardIconColor)}
268
+ </View>
269
+ <Text style={styles.integrationTitle} numberOfLines={1}>
270
+ {integration.label}
271
+ </Text>
272
+ </View>
273
+
274
+ <View
275
+ style={[
276
+ styles.integrationStatus,
277
+ {
278
+ backgroundColor: statusBackgroundColor,
279
+ borderColor: statusBorderColor,
280
+ },
281
+ ]}>
282
+ <Text
283
+ style={[
284
+ styles.integrationStatusText,
285
+ {color: statusTone},
286
+ ]}>
287
+ {statusText}
288
+ </Text>
289
+ </View>
290
+ </View>
291
+ </TouchableOpacity>
292
+ );
293
+ })}
294
+ </View>
295
+ </ScrollView>
296
+ </SafeAreaView>
297
+ );
298
+ }
299
+ // TODO(store-first): quando este arquivo for mexido, mover a leitura para stores, remover api.fetch e evitar repassar dados em objetos quando o store ja resolver isso.
@@ -0,0 +1,130 @@
1
+ import { Platform, StyleSheet } from 'react-native';
2
+ import { colors } from '@controleonline/../../src/styles/colors';
3
+ import { withOpacity } from '@controleonline/../../src/styles/branding';
4
+
5
+ const buildCardShadow = palette =>
6
+ Platform.select({
7
+ ios: {
8
+ shadowColor: palette.text,
9
+ shadowOffset: { width: 0, height: 8 },
10
+ shadowOpacity: 0.08,
11
+ shadowRadius: 16,
12
+ },
13
+ android: { elevation: 3 },
14
+ web: { boxShadow: `0 10px 24px ${withOpacity(palette.text, 0.08)}` },
15
+ });
16
+
17
+ export const createStyles = (palette = colors) => {
18
+ const cardShadow = buildCardShadow(palette);
19
+
20
+ return StyleSheet.create({
21
+ container: {
22
+ flex: 1,
23
+ backgroundColor: palette.background,
24
+ },
25
+ scroll: {
26
+ paddingHorizontal: 20,
27
+ paddingTop: 16,
28
+ paddingBottom: 32,
29
+ gap: 18,
30
+ },
31
+ centerState: {
32
+ flex: 1,
33
+ alignItems: 'center',
34
+ justifyContent: 'center',
35
+ paddingHorizontal: 32,
36
+ gap: 10,
37
+ },
38
+ centerStateTitle: {
39
+ fontSize: 20,
40
+ fontWeight: '700',
41
+ color: palette.text,
42
+ textAlign: 'center',
43
+ },
44
+ centerStateText: {
45
+ fontSize: 14,
46
+ color: palette.mutedText,
47
+ textAlign: 'center',
48
+ lineHeight: 20,
49
+ },
50
+ pageHeader: {
51
+ gap: 6,
52
+ },
53
+ pageTitle: {
54
+ fontSize: 28,
55
+ fontWeight: '800',
56
+ color: palette.text,
57
+ letterSpacing: -0.6,
58
+ },
59
+ pageSubtitle: {
60
+ fontSize: 14,
61
+ lineHeight: 20,
62
+ color: palette.mutedText,
63
+ maxWidth: 760,
64
+ },
65
+ integrationGrid: {
66
+ flexDirection: 'row',
67
+ flexWrap: 'wrap',
68
+ gap: 12,
69
+ },
70
+ integrationCard: {
71
+ flexGrow: 1,
72
+ flexBasis: '48%',
73
+ minWidth: 220,
74
+ borderRadius: 18,
75
+ padding: 14,
76
+ backgroundColor: palette.cardBackground,
77
+ borderColor: palette.cardBorder,
78
+ borderWidth: 1,
79
+ ...cardShadow,
80
+ },
81
+ integrationTopRow: {
82
+ flexDirection: 'row',
83
+ alignItems: 'center',
84
+ justifyContent: 'space-between',
85
+ gap: 12,
86
+ marginBottom: 0,
87
+ },
88
+ integrationHeaderLeft: {
89
+ flexDirection: 'row',
90
+ alignItems: 'center',
91
+ gap: 12,
92
+ flex: 1,
93
+ minWidth: 0,
94
+ },
95
+ integrationIconWrap: {
96
+ width: 44,
97
+ height: 44,
98
+ borderRadius: 14,
99
+ alignItems: 'center',
100
+ justifyContent: 'center',
101
+ backgroundColor: palette.cardIconBackground,
102
+ },
103
+ integrationLogo: {
104
+ width: 24,
105
+ height: 24,
106
+ },
107
+ integrationStatus: {
108
+ borderRadius: 999,
109
+ paddingHorizontal: 10,
110
+ paddingVertical: 6,
111
+ backgroundColor: palette.badgeBackground,
112
+ borderWidth: 1,
113
+ borderColor: palette.badgeBorder,
114
+ },
115
+ integrationStatusText: {
116
+ fontSize: 12,
117
+ fontWeight: '700',
118
+ color: palette.badgeText,
119
+ },
120
+ integrationTitle: {
121
+ fontSize: 16,
122
+ fontWeight: '800',
123
+ color: palette.cardText,
124
+ flexShrink: 1,
125
+ },
126
+ });
127
+ };
128
+
129
+ const styles = createStyles(colors);
130
+ export default styles;