@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.
- package/.github/actions/workers/manager/action.yml +147 -0
- package/.github/actions/workers/qa/action.yml +44 -0
- package/.github/actions/workers/security/action.yml +44 -0
- package/.github/actions/workers/technical-documenter/action.yml +51 -0
- package/.github/workflows/manager-worker.yml +168 -0
- package/.github/workflows/publish-npm.yml +45 -0
- package/.gitmodules +3 -0
- package/AGENTS.md +6 -0
- package/README.md +14 -0
- package/package.json +3 -2
- package/src/api/fetch.js +1 -1
- package/src/api/index.js +92 -22
- package/src/react/components/AddImportModal.js +94 -110
- package/src/react/components/BottomNavigationBar.config.js +211 -231
- package/src/react/components/CategoryForm.js +52 -35
- package/src/react/components/DefaultProvider.native.js +131 -95
- package/src/react/components/DefaultProvider.web.js +148 -74
- package/src/react/components/NetworkPrinterPreviewModal.js +249 -0
- package/src/react/components/NetworkPrinterPreviewModal.styles.js +159 -0
- package/src/react/components/RuntimeInfoFooter.js +164 -104
- package/src/react/components/RuntimeInfoFooter.styles.js +3 -0
- package/src/react/components/UnifiedPaymentBar.js +7 -4
- package/src/react/components/UserAvatar.js +160 -0
- package/src/react/components/integrations/IntegrationHero/index.js +48 -0
- package/src/react/components/integrations/IntegrationHero/styles.js +57 -0
- package/src/react/components/integrations/IntegrationTabs/index.js +89 -0
- package/src/react/components/integrations/IntegrationTabs/styles.js +44 -0
- package/src/react/config/deviceConfigBootstrap.js +5 -3
- package/src/react/pages/Connections.js +229 -0
- package/src/react/pages/Connections.styles.js +222 -0
- package/src/react/pages/DeviceDetailPage.js +2865 -0
- package/src/react/pages/DeviceDetailPage.styles.js +308 -0
- package/src/react/pages/Devices/currentDevice.js +194 -0
- package/src/react/pages/Devices/deviceTypes/DeviceGroupCard.js +257 -0
- package/src/react/pages/Devices/deviceTypes/all.js +23 -0
- package/src/react/pages/Devices/deviceTypes/device.js +25 -0
- package/src/react/pages/Devices/deviceTypes/deviceListHelpers.js +178 -0
- package/src/react/pages/Devices/deviceTypes/display.js +26 -0
- package/src/react/pages/Devices/deviceTypes/index.js +145 -0
- package/src/react/pages/Devices/deviceTypes/ipCamera.js +35 -0
- package/src/react/pages/Devices/deviceTypes/pdv.js +88 -0
- package/src/react/pages/Devices/deviceTypes/printer.js +37 -0
- package/src/react/pages/Devices/deviceTypes/shared.js +453 -0
- package/src/react/pages/Devices/deviceTypes/useDeviceNetworkConnectivity.js +145 -0
- package/src/react/pages/Devices.js +183 -0
- package/src/react/pages/Devices.styles.js +353 -0
- package/src/react/pages/Food99IntegrationPage/components/Food99CatalogTab.js +219 -0
- package/src/react/pages/Food99IntegrationPage/components/Food99OverviewTab.js +134 -0
- package/src/react/pages/Food99IntegrationPage/components/Food99PreviewModal.js +173 -0
- package/src/react/pages/Food99IntegrationPage/components/Food99ProductCard.js +1 -0
- package/src/react/pages/Food99IntegrationPage/components/Food99QuickWalletModal.js +181 -0
- package/src/react/pages/Food99IntegrationPage/components/Food99SettingsTab.js +306 -0
- package/src/react/pages/Food99IntegrationPage/components/Food99StoreTab.js +417 -0
- package/src/react/pages/Food99IntegrationPage/index.js +1530 -0
- package/src/react/pages/Food99IntegrationPage/styles.js +792 -0
- package/src/react/pages/Food99IntegrationPage/utils.js +155 -0
- package/src/react/pages/Food99IntegrationPage.js +1 -0
- package/src/react/pages/Food99IntegrationPage.styles.js +1 -0
- package/src/react/pages/GenericLogPage.js +42 -167
- package/src/react/pages/IFoodIntegrationPage/components/IFoodCatalogTab.js +8 -0
- package/src/react/pages/IFoodIntegrationPage/components/IFoodOperationsTab.js +420 -0
- package/src/react/pages/IFoodIntegrationPage/components/IFoodOverviewTab.js +118 -0
- package/src/react/pages/IFoodIntegrationPage/components/IFoodPreviewModal.js +103 -0
- package/src/react/pages/IFoodIntegrationPage/components/IFoodProductCard.js +1 -0
- package/src/react/pages/IFoodIntegrationPage/components/IFoodStoreTab.js +150 -0
- package/src/react/pages/IFoodIntegrationPage/index.js +1298 -0
- package/src/react/pages/IFoodIntegrationPage/styles.js +764 -0
- package/src/react/pages/IFoodIntegrationPage/utils.js +91 -0
- package/src/react/pages/IFoodIntegrationPage.js +1 -0
- package/src/react/pages/IFoodIntegrationPage.styles.js +1 -0
- package/src/react/pages/Imports.js +214 -166
- package/src/react/pages/IntegrationConfigPage.js +744 -0
- package/src/react/pages/IntegrationConfigPage.styles.js +269 -0
- package/src/react/pages/Integrations.js +299 -0
- package/src/react/pages/Integrations.styles.js +130 -0
- package/src/react/pages/ManagerOrderNotificationsPage.js +642 -0
- package/src/react/pages/MarketplaceIntegrationPage/components/MarketplaceProductCard.js +205 -0
- package/src/react/pages/MarketplaceIntegrationPage/components/RemoteMarketplaceMenuTab.js +335 -0
- package/src/react/pages/MarketplaceIntegrationPage/index.js +60 -0
- package/src/react/pages/MarketplaceIntegrationPage/utils.js +137 -0
- package/src/react/pages/MercadoLivreIntegrationPage.js +538 -0
- package/src/react/pages/PrinterDeviceDetailPage.js +1916 -0
- package/src/react/pages/PrinterDeviceDetailPage.styles.js +223 -0
- package/src/react/pages/PrinterDeviceFormPage.js +521 -0
- package/src/react/pages/PrinterDeviceFormPage.styles.js +159 -0
- package/src/react/pages/WhatsAppConnectionPage.js +459 -0
- package/src/react/pages/WhatsAppConnectionPage.styles.js +284 -0
- package/src/react/pages/integrationsCatalog.js +399 -0
- package/src/react/services/Cielo/Cielo.js +193 -193
- package/src/react/utils/categoryContexts.js +46 -0
- package/src/react/utils/entityDisplay.js +222 -217
- package/src/react/utils/fileUrl.js +39 -20
- package/src/react/utils/integrationPage.js +16 -0
- package/src/react/utils/runtimeFooter.js +40 -22
- package/src/react/utils/runtimeLanguage.js +50 -9
- package/src/react/utils/runtimeMenu.js +191 -131
- package/src/react/utils/runtimeZoom.js +43 -0
- package/src/react/utils/shopConfig.js +27 -15
- package/src/react/utils/shopFranchises.js +126 -1
- package/src/react/utils/storeColumns.js +23 -4
- package/src/react/utils/userAvatar.js +104 -0
- package/src/store/categories/index.js +48 -20
- package/src/store/device_config/index.js +62 -21
- package/src/store/entity_log/index.js +22 -1
- package/src/store/imports/customActions.js +51 -0
- package/src/store/imports/index.js +89 -88
- package/src/store/status/index.js +11 -11
- package/src/store/themes/index.js +22 -0
- package/src/store/timezones/index.js +3 -1
- package/src/tests/browser/manager/connections.spec.js +143 -0
- package/src/tests/browser/manager/devices-current.spec.js +343 -0
- package/src/tests/browser/manager/mercado-livre-integration.spec.js +268 -0
- package/src/tests/react/api/deviceHeaders.test.js +114 -0
- package/src/tests/react/components/AddImportModal.test.js +166 -0
- package/src/tests/react/components/BottomNavigationBar.config.test.js +20 -0
- package/src/tests/react/components/UserAvatar.test.js +52 -0
- package/src/tests/react/pages/DevicePickerStyles.test.js +47 -0
- package/src/tests/react/pages/Devices/deviceListHelpers.test.js +39 -0
- package/src/tests/react/pages/DevicesCurrentDevice.test.js +198 -0
- package/src/tests/react/pages/connectionsHelpers.test.js +34 -0
- package/src/tests/react/pages/mercadoLivreIntegrationPageContract.test.mjs +16 -0
- package/src/tests/react/services/Cielo.test.js +190 -190
- package/src/tests/react/store/categoriesOrdering.test.js +26 -0
- package/src/tests/react/utils/appDomain.test.js +62 -0
- package/src/tests/react/utils/entityDisplay.test.js +2 -2
- package/src/tests/react/utils/fileUrl.test.js +3 -3
- package/src/tests/react/utils/formatter.test.mjs +33 -0
- package/src/tests/react/utils/runtimeFooter.test.js +29 -4
- package/src/tests/react/utils/runtimeLanguage.test.js +79 -9
- package/src/tests/react/utils/runtimeMenu.test.js +198 -134
- package/src/tests/react/utils/runtimeZoom.test.js +38 -0
- package/src/tests/react/utils/shopConfig.test.js +10 -9
- package/src/tests/react/utils/shopFranchises.test.js +43 -0
- package/src/tests/react/utils/translate.discovery.test.mjs +170 -0
- package/src/tests/react/utils/translate.test.helpers.mjs +152 -0
- package/src/tests/react/utils/translate.test.mjs +141 -349
- package/src/tests/react/utils/userAvatar.test.js +63 -0
- package/src/utils/appDomain.js +15 -4
- package/src/utils/formatter.js +104 -16
- package/src/utils/integrationConfigs.js +80 -80
- package/src/utils/translate.js +222 -438
- package/src/utils/translateDiscovery.js +96 -0
- package/src/utils/translateNormalize.js +33 -0
- package/src/utils/translatePending.js +92 -0
- package/src/utils/translateStorage.js +142 -0
- package/.github/agents/developer.agent.md +0 -30
- package/.github/agents/devops.agent.md +0 -30
- package/.github/agents/qa.agent.md +0 -30
- package/.github/agents/security.agent.md +0 -30
- 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
|
+
}
|