@controleonline/ui-common 1.2.71 → 1.2.76

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 +164 -104
  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 +2865 -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,137 @@
1
+ const normalizeProviderKey = value =>
2
+ String(value || '')
3
+ .trim()
4
+ .toLowerCase();
5
+
6
+ const normalizeIfFoodChildren = product => {
7
+ const options = Array.isArray(product?.options) ? product.options : [];
8
+
9
+ if (options.length === 0) {
10
+ return [];
11
+ }
12
+
13
+ return [
14
+ {
15
+ id: `${product.id}-ifood-options`,
16
+ name: 'Complementos',
17
+ required: false,
18
+ minimum: 0,
19
+ maximum: 0,
20
+ items: options.map(option => ({
21
+ id:
22
+ option.ifood_option_id ||
23
+ option.id ||
24
+ option.code ||
25
+ `${product.id}-option`,
26
+ name: option.name || 'Complemento',
27
+ code: option.ifood_option_id || option.id || option.code || '',
28
+ description: option.description || '',
29
+ price: option.price,
30
+ })),
31
+ },
32
+ ];
33
+ };
34
+
35
+ const normalizeFood99Children = product =>
36
+ Array.isArray(product?.children) ? product.children : [];
37
+
38
+ const normalizeSelectedProductSet = value => {
39
+ if (value instanceof Set) {
40
+ return new Set(Array.from(value).map(item => String(item)));
41
+ }
42
+
43
+ if (Array.isArray(value)) {
44
+ return new Set(value.map(item => String(item)));
45
+ }
46
+
47
+ return new Set();
48
+ };
49
+
50
+ export const normalizeMarketplaceCatalogProduct = (
51
+ product,
52
+ providerKey = '99food',
53
+ ) => {
54
+ const normalizedProviderKey = normalizeProviderKey(providerKey);
55
+ const isIfood = normalizedProviderKey === 'ifood';
56
+ const code = isIfood
57
+ ? product?.ifood_item_id || product?.food99_code || product?.id
58
+ : product?.food99_code || product?.ifood_item_id || product?.id;
59
+
60
+ return {
61
+ id: product?.id,
62
+ name: String(product?.name || ''),
63
+ categoryName: String(
64
+ product?.category?.name || product?.category_name || 'Sem categoria',
65
+ ),
66
+ productType: String(product?.type || product?.product_type || 'produto'),
67
+ description: String(product?.description || ''),
68
+ price: Number(product?.price || 0),
69
+ code: code === null || code === undefined ? '' : String(code),
70
+ codeLabel: 'Codigo',
71
+ noCodeLabel: 'Sem codigo',
72
+ published: Boolean(product?.published_remotely || product?.published),
73
+ publishedLabel: 'Ja publicado no catalogo',
74
+ childrenSectionLabel: 'Filhos consultados',
75
+ childItemCodeLabel: 'Codigo',
76
+ childItemNoCodeLabel: 'Sem codigo',
77
+ eligible: Boolean(product?.eligible),
78
+ blockers: Array.isArray(product?.blockers) ? product.blockers : [],
79
+ children: isIfood
80
+ ? normalizeIfFoodChildren(product)
81
+ : normalizeFood99Children(product),
82
+ sourceProduct: product,
83
+ };
84
+ };
85
+
86
+ export const normalizeMarketplaceCatalogTabProps = (
87
+ props = {},
88
+ {providerKey = '99food', defaultMinimumRequiredItems} = {},
89
+ ) => {
90
+ const selectedEligibleProducts = Array.isArray(props.selectedEligibleProducts)
91
+ ? props.selectedEligibleProducts
92
+ : Array.isArray(props.selectedEligible)
93
+ ? props.selectedEligible
94
+ : [];
95
+ const filteredProducts = Array.isArray(props.filteredProducts)
96
+ ? props.filteredProducts
97
+ : [];
98
+ const selectedProductSet =
99
+ props.selectedProductSet instanceof Set
100
+ ? normalizeSelectedProductSet(props.selectedProductSet)
101
+ : normalizeSelectedProductSet(props.selectedIds);
102
+ const minimumRequiredItems = Number(
103
+ props.minimumRequiredItems ??
104
+ props.productsResponse?.minimum_required_items ??
105
+ defaultMinimumRequiredItems ??
106
+ 1,
107
+ );
108
+ const selectionSummaryTone =
109
+ props.selectionSummaryTone || props.accentColor;
110
+ const productsResponse = {
111
+ ...(props.productsResponse || {}),
112
+ eligible_product_count: Number(
113
+ props.productsResponse?.eligible_product_count ??
114
+ props.eligibleCount ??
115
+ selectedEligibleProducts.length ??
116
+ 0,
117
+ ),
118
+ minimum_required_items: minimumRequiredItems,
119
+ };
120
+
121
+ return {
122
+ ...props,
123
+ providerKey,
124
+ selectionSummaryTone,
125
+ minimumRequiredItems,
126
+ productsResponse,
127
+ selectedEligibleProducts,
128
+ filteredProducts,
129
+ selectedProductSet,
130
+ previewLoading: Boolean(props.previewLoading),
131
+ onPreview: props.onPreview || props.onOpenPreview || (() => {}),
132
+ onToggleProduct:
133
+ props.onToggleProduct || props.onProductCardPress || (() => {}),
134
+ onMarkCardPressHandled:
135
+ props.onMarkCardPressHandled || props.onBlockCardPress || (() => {}),
136
+ };
137
+ };
@@ -0,0 +1,538 @@
1
+ import React, {useCallback, useEffect, useMemo, useState} from 'react';
2
+ import {
3
+ ActivityIndicator,
4
+ Linking,
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 styles from './IntegrationConfigPage.styles';
26
+
27
+ const PROVIDER = {
28
+ key: 'mercadolivre',
29
+ label: 'Mercado Livre',
30
+ accent: '#8A6A00',
31
+ button: '#111827',
32
+ icon: 'shopping-bag',
33
+ };
34
+
35
+ const tt = (type, key) => global.t?.t('configs', type, key);
36
+
37
+ const shadowStyle = Platform.select({
38
+ ios: {
39
+ shadowColor: '#0F172A',
40
+ shadowOffset: {width: 0, height: 8},
41
+ shadowOpacity: 0.08,
42
+ shadowRadius: 16,
43
+ },
44
+ android: {elevation: 3},
45
+ web: {boxShadow: '0 10px 24px rgba(15,23,42,0.08)'},
46
+ });
47
+
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
+ export default function MercadoLivreIntegrationPage() {
124
+ const peopleStore = useStore('people');
125
+ const themeStore = useStore('theme');
126
+ const {currentCompany} = peopleStore.getters;
127
+ const {colors: themeColors} = themeStore.getters;
128
+ const {showError, showSuccess} = useToastMessage();
129
+
130
+ const providerId = currentCompany?.id;
131
+ const brandColors = useMemo(
132
+ () =>
133
+ resolveThemePalette(
134
+ {
135
+ ...themeColors,
136
+ ...(currentCompany?.theme?.colors || {}),
137
+ },
138
+ colors,
139
+ ),
140
+ [themeColors, currentCompany?.id],
141
+ );
142
+
143
+ const [loading, setLoading] = useState(true);
144
+ const [refreshing, setRefreshing] = useState(false);
145
+ const [authorizing, setAuthorizing] = useState(false);
146
+ const [importing, setImporting] = useState(false);
147
+ const [detail, setDetail] = useState(null);
148
+ const [selectedShowcaseId, setSelectedShowcaseId] = useState(null);
149
+
150
+ const loadPageData = useCallback(
151
+ async ({showLoading = true} = {}) => {
152
+ if (!providerId) {
153
+ setDetail(null);
154
+ setLoading(false);
155
+ return;
156
+ }
157
+
158
+ if (showLoading) {
159
+ setLoading(true);
160
+ }
161
+
162
+ try {
163
+ const detailResponse = await api.fetch('/marketplace/integrations/mercadolivre/detail', {
164
+ params: {
165
+ provider_id: providerId,
166
+ },
167
+ });
168
+
169
+ setDetail(detailResponse);
170
+
171
+ const showcases = Array.isArray(detailResponse?.showcases)
172
+ ? detailResponse.showcases
173
+ : [];
174
+ setSelectedShowcaseId(current => {
175
+ if (current && showcases.some(showcase => String(showcase.id) === String(current))) {
176
+ return current;
177
+ }
178
+
179
+ const preferred =
180
+ showcases.find(showcase =>
181
+ normalizeTextValue(showcase.domain).startsWith('loja.'),
182
+ ) || showcases.find(showcase => showcase.integration_key === 'shop') || showcases[0];
183
+
184
+ return preferred?.id || null;
185
+ });
186
+ } catch (error) {
187
+ showError(formatApiError(error));
188
+ setDetail(null);
189
+ } finally {
190
+ if (showLoading) {
191
+ setLoading(false);
192
+ }
193
+ }
194
+ },
195
+ [providerId, showError],
196
+ );
197
+
198
+ useFocusEffect(
199
+ useCallback(() => {
200
+ loadPageData();
201
+ }, [loadPageData]),
202
+ );
203
+
204
+ useEffect(() => {
205
+ if (Platform.OS !== 'web' || typeof window === 'undefined') {
206
+ return;
207
+ }
208
+
209
+ const params = new URLSearchParams(window.location.search || '');
210
+ const alreadyConnected = params.get('mercadolivre_connected') === '1';
211
+ const oauthStatusError = params.get('mercadolivre_error');
212
+ const oauthStatusMessage = params.get('mercadolivre_message');
213
+
214
+ if (alreadyConnected) {
215
+ showSuccess(
216
+ tt('marketplace_success', 'mercadoLivreConnected') ||
217
+ 'Mercado Livre conectado com sucesso.',
218
+ );
219
+ replaceFrontOAuthStatus({});
220
+ loadPageData({showLoading: false});
221
+ return;
222
+ }
223
+
224
+ if (oauthStatusError) {
225
+ showError(formatOAuthStatusError(oauthStatusError, oauthStatusMessage));
226
+ replaceFrontOAuthStatus({});
227
+ loadPageData({showLoading: false});
228
+ }
229
+ }, [loadPageData, showError, showSuccess]);
230
+
231
+ const onRefresh = useCallback(async () => {
232
+ setRefreshing(true);
233
+ try {
234
+ await loadPageData({showLoading: false});
235
+ } finally {
236
+ setRefreshing(false);
237
+ }
238
+ }, [loadPageData]);
239
+
240
+ const connectMercadoLivre = useCallback(async () => {
241
+ if (!providerId) {
242
+ showError(
243
+ tt('marketplace_error', 'activeCompanyMissing') ||
244
+ 'Nao foi possivel identificar a empresa ativa.',
245
+ );
246
+ return;
247
+ }
248
+
249
+ if (detail?.oauth?.client_configured === false) {
250
+ showError(
251
+ tt('marketplace_error', 'mercadoLivreClientMissing') ||
252
+ 'Configure o Client ID e Secret do app Mercado Livre antes de conectar.',
253
+ );
254
+ return;
255
+ }
256
+
257
+ setAuthorizing(true);
258
+ try {
259
+ const response = await api.fetch('/marketplace/integrations/mercadolivre/authorization-page', {
260
+ method: 'POST',
261
+ body: {
262
+ provider_id: providerId,
263
+ return_url: resolveFrontOAuthRedirectUri(),
264
+ },
265
+ });
266
+
267
+ const authorizationUrl =
268
+ response?.authorization_url || response?.url || response?.auth_url || '';
269
+ if (!authorizationUrl) {
270
+ showError(
271
+ response?.message ||
272
+ tt('marketplace_error', 'mercadoLivreAuthorizationUrlMissing') ||
273
+ 'URL de autorizacao do Mercado Livre indisponivel.',
274
+ );
275
+ return;
276
+ }
277
+
278
+ await Linking.openURL(authorizationUrl);
279
+ } catch (error) {
280
+ showError(
281
+ error?.message ||
282
+ tt('marketplace_error', 'mercadoLivreLoginStart') ||
283
+ 'Nao foi possivel iniciar o login do Mercado Livre.',
284
+ );
285
+ } finally {
286
+ setAuthorizing(false);
287
+ }
288
+ }, [detail?.oauth?.client_configured, providerId, showError]);
289
+
290
+ const importProducts = useCallback(async () => {
291
+ if (!providerId || !selectedShowcaseId) {
292
+ showError(
293
+ tt('marketplace_error', 'showcaseRequiredForImport') ||
294
+ 'Selecione a vitrine que recebera os produtos importados.',
295
+ );
296
+ return;
297
+ }
298
+
299
+ setImporting(true);
300
+ try {
301
+ const response = await api.fetch('/marketplace/integrations/mercadolivre/products/import', {
302
+ method: 'POST',
303
+ body: {
304
+ provider_id: providerId,
305
+ showcase_id: selectedShowcaseId,
306
+ limit: 50,
307
+ },
308
+ });
309
+
310
+ showSuccess(
311
+ response?.integration_id
312
+ ? `${
313
+ tt('marketplace_success', 'productImportQueuedWithId') ||
314
+ 'Importacao enviada para a fila #'
315
+ }${response.integration_id}.`
316
+ : tt('marketplace_success', 'productImportQueued') ||
317
+ 'Importacao enviada para a fila.',
318
+ );
319
+ await loadPageData({showLoading: false});
320
+ } catch (error) {
321
+ showError(
322
+ error?.message ||
323
+ error?.error ||
324
+ tt('marketplace_error', 'productImport') ||
325
+ 'Nao foi possivel importar os produtos.',
326
+ );
327
+ } finally {
328
+ setImporting(false);
329
+ }
330
+ }, [loadPageData, providerId, selectedShowcaseId, showError, showSuccess]);
331
+
332
+ if (!providerId) {
333
+ return (
334
+ <SafeAreaView style={styles.container} edges={['bottom']}>
335
+ <View style={styles.centerState}>
336
+ <Icon name="building" size={32} color="#94A3B8" />
337
+ <Text style={styles.centerStateTitle}>
338
+ {tt('marketplace_title', 'selectCompany') || 'Selecione uma empresa'}
339
+ </Text>
340
+ <Text style={styles.centerStateText}>
341
+ {tt('marketplace_message', 'activeCompanyRequired') ||
342
+ 'A integracao depende da empresa ativa.'}
343
+ </Text>
344
+ </View>
345
+ </SafeAreaView>
346
+ );
347
+ }
348
+
349
+ if (loading) {
350
+ return (
351
+ <SafeAreaView style={[styles.container, {backgroundColor: brandColors.background}]} edges={['bottom']}>
352
+ <View style={styles.centerState}>
353
+ <ActivityIndicator size="large" color={PROVIDER.accent} />
354
+ <Text style={styles.centerStateTitle}>
355
+ {tt('marketplace_title', 'loadingMercadoLivre') || 'Carregando Mercado Livre'}
356
+ </Text>
357
+ <Text style={styles.centerStateText}>
358
+ {tt('marketplace_message', 'loadingMercadoLivre') ||
359
+ 'Buscando credenciais, webhook e vitrines disponiveis.'}
360
+ </Text>
361
+ </View>
362
+ </SafeAreaView>
363
+ );
364
+ }
365
+
366
+ const integration = detail?.integration || {};
367
+ const connected = isConnected(integration.connected);
368
+ const oauthClientConfigured = detail?.oauth?.client_configured !== false;
369
+ const statusTone = connected ? '#16A34A' : '#e67e22';
370
+ const showcases = Array.isArray(detail?.showcases) ? detail.showcases : [];
371
+
372
+ return (
373
+ <SafeAreaView style={[styles.container, {backgroundColor: brandColors.background}]} edges={['bottom']}>
374
+ <ScrollView
375
+ contentContainerStyle={styles.scroll}
376
+ showsVerticalScrollIndicator={false}
377
+ refreshControl={
378
+ <RefreshControl refreshing={refreshing} onRefresh={onRefresh} tintColor={PROVIDER.accent} />
379
+ }>
380
+ <View style={[styles.heroCard, shadowStyle, {backgroundColor: '#FFE600'}]}>
381
+ <View style={styles.heroCopy}>
382
+ <Text style={[styles.heroEyebrow, {color: 'rgba(17,24,39,0.64)'}]}>
383
+ {tt('marketplace_label', 'integration') || 'INTEGRACAO'}
384
+ </Text>
385
+ <Text style={[styles.heroTitle, {color: '#111827'}]}>{PROVIDER.label}</Text>
386
+ <Text style={[styles.heroText, {color: 'rgba(17,24,39,0.76)'}]}>
387
+ {tt('marketplace_message', 'mercadoLivreHero') ||
388
+ 'Receba notificacoes de pedidos e importe anuncios para a vitrine escolhida.'}
389
+ </Text>
390
+ </View>
391
+ <View style={styles.heroBadge}>
392
+ <Icon name={PROVIDER.icon} size={22} color={PROVIDER.button} />
393
+ </View>
394
+ </View>
395
+
396
+ <View style={[styles.statusCard, shadowStyle]}>
397
+ <View style={styles.statusHeader}>
398
+ <View style={styles.statusCopy}>
399
+ <Text style={styles.sectionTitle}>
400
+ {tt('marketplace_title', 'status') || 'Status'}
401
+ </Text>
402
+ <Text style={styles.sectionSubtitle}>
403
+ {integration.user_id
404
+ ? `Seller/User ID ${integration.user_id}`
405
+ : tt('marketplace_message', 'connectSellerAccount') ||
406
+ 'Conecte a conta vendedora para habilitar importacao e webhook.'}
407
+ </Text>
408
+ </View>
409
+ <View style={[styles.statusBadge, {backgroundColor: withOpacity(statusTone, 0.12)}]}>
410
+ <Text style={[styles.statusBadgeText, {color: statusTone}]}>
411
+ {connected
412
+ ? tt('marketplace_status', 'connected') || 'Conectado'
413
+ : tt('marketplace_status', 'pending') || 'Pendente'}
414
+ </Text>
415
+ </View>
416
+ </View>
417
+ </View>
418
+
419
+ <View style={[styles.formCard, shadowStyle]}>
420
+ <Text style={styles.cardTitle}>
421
+ {tt('marketplace_title', 'mercadoLivreAccount') || 'Conta Mercado Livre'}
422
+ </Text>
423
+ <Text style={styles.cardSubtitle}>
424
+ {oauthClientConfigured
425
+ ? tt('marketplace_message', 'mercadoLivreOauthFlow') ||
426
+ 'O login abre o Mercado Livre, autoriza a conta vendedora e retorna para esta tela.'
427
+ : tt('marketplace_message', 'mercadoLivreClientConfigRequired') ||
428
+ 'Configure o Client ID e Secret do app Mercado Livre nesta empresa para habilitar o login.'}
429
+ </Text>
430
+
431
+ <TouchableOpacity
432
+ style={[
433
+ styles.saveButton,
434
+ {backgroundColor: PROVIDER.button},
435
+ (!oauthClientConfigured || authorizing) && styles.saveButtonDisabled,
436
+ ]}
437
+ disabled={!oauthClientConfigured || authorizing}
438
+ activeOpacity={0.9}
439
+ onPress={connectMercadoLivre}>
440
+ {authorizing ? (
441
+ <ActivityIndicator color="#FFFFFF" />
442
+ ) : (
443
+ <Icon name="external-link" size={16} color="#FFFFFF" />
444
+ )}
445
+ <Text style={styles.saveButtonText}>
446
+ {connected
447
+ ? tt('marketplace_button', 'reconnectMercadoLivre') ||
448
+ 'Reconectar Mercado Livre'
449
+ : tt('marketplace_button', 'connectMercadoLivre') ||
450
+ 'Conectar com Mercado Livre'}
451
+ </Text>
452
+ </TouchableOpacity>
453
+ </View>
454
+
455
+ <View style={[styles.formCard, shadowStyle]}>
456
+ <Text style={styles.cardTitle}>
457
+ {tt('marketplace_title', 'webhook') || 'Webhook'}
458
+ </Text>
459
+ <Text style={styles.cardSubtitle}>
460
+ {detail?.webhook?.url ||
461
+ tt('marketplace_message', 'webhookUnavailable') ||
462
+ 'Webhook indisponivel.'}
463
+ </Text>
464
+ </View>
465
+
466
+ <View style={[styles.formCard, shadowStyle]}>
467
+ <Text style={styles.cardTitle}>
468
+ {tt('marketplace_title', 'importProducts') || 'Importar produtos'}
469
+ </Text>
470
+ <Text style={styles.cardSubtitle}>
471
+ {tt('marketplace_message', 'selectShowcaseForImport') ||
472
+ 'Selecione a vitrine que recebera os produtos importados do Mercado Livre.'}
473
+ </Text>
474
+
475
+ <View style={styles.fieldList}>
476
+ {showcases.length === 0 ? (
477
+ <View style={styles.fieldGroup}>
478
+ <Text style={styles.fieldLabel}>
479
+ {tt('marketplace_label', 'noActiveShowcaseFound') ||
480
+ 'Nenhuma vitrine ativa encontrada'}
481
+ </Text>
482
+ <Text style={styles.fieldKey}>
483
+ {tt('marketplace_message', 'createShowcaseBeforeImport') ||
484
+ 'Crie ou ative uma vitrine antes de importar produtos.'}
485
+ </Text>
486
+ </View>
487
+ ) : (
488
+ showcases.map(showcase => {
489
+ const selected = String(showcase.id) === String(selectedShowcaseId);
490
+ return (
491
+ <TouchableOpacity
492
+ key={showcase.id}
493
+ activeOpacity={0.88}
494
+ onPress={() => setSelectedShowcaseId(showcase.id)}
495
+ style={[
496
+ styles.input,
497
+ {
498
+ borderColor: selected ? PROVIDER.button : '#E2E8F0',
499
+ backgroundColor: selected ? withOpacity('#FFE600', 0.22) : '#F8FAFC',
500
+ },
501
+ ]}>
502
+ <Text style={styles.fieldLabel}>{showcase.name}</Text>
503
+ <Text style={styles.fieldKey}>
504
+ {showcase.domain ||
505
+ showcase.integration_key ||
506
+ tt('marketplace_label', 'showcaseWithoutDomain') ||
507
+ 'Vitrine sem dominio'}
508
+ </Text>
509
+ </TouchableOpacity>
510
+ );
511
+ })
512
+ )}
513
+ </View>
514
+
515
+ <TouchableOpacity
516
+ style={[
517
+ styles.saveButton,
518
+ {backgroundColor: PROVIDER.button},
519
+ (!selectedShowcaseId || importing) && styles.saveButtonDisabled,
520
+ ]}
521
+ disabled={!selectedShowcaseId || importing}
522
+ activeOpacity={0.9}
523
+ onPress={importProducts}>
524
+ {importing ? (
525
+ <ActivityIndicator color="#FFFFFF" />
526
+ ) : (
527
+ <Icon name="download-cloud" size={16} color="#FFFFFF" />
528
+ )}
529
+ <Text style={styles.saveButtonText}>
530
+ {tt('marketplace_button', 'queueProductImport') ||
531
+ 'Enviar importacao para a fila'}
532
+ </Text>
533
+ </TouchableOpacity>
534
+ </View>
535
+ </ScrollView>
536
+ </SafeAreaView>
537
+ );
538
+ }