@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,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
+ }