@controleonline/ui-common 1.2.71 → 1.2.75
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +100 -36
- 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 +2814 -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,744 @@
|
|
|
1
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import { ActivityIndicator, Linking, Platform, RefreshControl, ScrollView, Text, TextInput, TouchableOpacity, View } from 'react-native';
|
|
3
|
+
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
4
|
+
import { useFocusEffect } from '@react-navigation/native';
|
|
5
|
+
import Icon from 'react-native-vector-icons/Feather';
|
|
6
|
+
|
|
7
|
+
import { api } from '@controleonline/ui-common/src/api';
|
|
8
|
+
import useToastMessage from '@controleonline/ui-crm/src/react/hooks/useToastMessage';
|
|
9
|
+
import { useStore } from '@store';
|
|
10
|
+
import { colors } from '@controleonline/../../src/styles/colors';
|
|
11
|
+
import {
|
|
12
|
+
resolveThemePalette,
|
|
13
|
+
withOpacity,
|
|
14
|
+
} from '@controleonline/../../src/styles/branding';
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
getIntegrationConfig,
|
|
18
|
+
getIntegrationByKey,
|
|
19
|
+
parseIntegrationCollection,
|
|
20
|
+
} from './integrationsCatalog';
|
|
21
|
+
import DefaultUpload from '@controleonline/ui-default/src/react/components/upload/DefaultUpload';
|
|
22
|
+
import { extractFileId, toFileIri, uploadFileToApi } from '@controleonline/ui-default/src/react/components/upload/fileUpload';
|
|
23
|
+
import styles from './IntegrationConfigPage.styles';
|
|
24
|
+
|
|
25
|
+
const ROUTE_PROVIDER_MAP = {
|
|
26
|
+
UberIntegrationPage: 'uber',
|
|
27
|
+
AsaasIntegrationPage: 'asaas',
|
|
28
|
+
ClickSignIntegrationPage: 'clicksign',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const shadowStyle = Platform.select({
|
|
32
|
+
ios: {
|
|
33
|
+
shadowColor: '#0F172A',
|
|
34
|
+
shadowOffset: { width: 0, height: 8 },
|
|
35
|
+
shadowOpacity: 0.08,
|
|
36
|
+
shadowRadius: 16,
|
|
37
|
+
},
|
|
38
|
+
android: { elevation: 3 },
|
|
39
|
+
web: { boxShadow: '0 10px 24px rgba(15,23,42,0.08)' },
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const routeNameToPath = routeName =>
|
|
43
|
+
String(routeName || '')
|
|
44
|
+
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
45
|
+
.toLowerCase();
|
|
46
|
+
|
|
47
|
+
const formatApiError = error => {
|
|
48
|
+
if (!error) return 'Nao foi possivel carregar a configuracao da integracao.';
|
|
49
|
+
if (typeof error === 'string') return error;
|
|
50
|
+
return error?.message || error?.description || error?.errmsg || 'Nao foi possivel carregar a configuracao da integracao.';
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const normalizeSourceConfigs = source => {
|
|
54
|
+
if (Array.isArray(source)) {
|
|
55
|
+
return source.reduce((accumulator, item) => {
|
|
56
|
+
const key = String(item?.configKey || '').trim();
|
|
57
|
+
if (key) {
|
|
58
|
+
accumulator[key] = item?.configValue;
|
|
59
|
+
}
|
|
60
|
+
return accumulator;
|
|
61
|
+
}, {});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (source && typeof source === 'object') {
|
|
65
|
+
return source;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {};
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const normalizeTextValue = value => {
|
|
72
|
+
let text = String(value ?? '').trim();
|
|
73
|
+
// Strip accidental surrounding quotes from double-encoded config values
|
|
74
|
+
if (
|
|
75
|
+
(text.startsWith('"') && text.endsWith('"') && text.length >= 2) ||
|
|
76
|
+
(text.startsWith("'") && text.endsWith("'") && text.length >= 2)
|
|
77
|
+
) {
|
|
78
|
+
text = text.slice(1, -1).trim();
|
|
79
|
+
}
|
|
80
|
+
return text;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const isConnectedValue = value =>
|
|
84
|
+
value === true ||
|
|
85
|
+
value === 1 ||
|
|
86
|
+
value === '1' ||
|
|
87
|
+
String(value).trim().toLowerCase() === 'true';
|
|
88
|
+
|
|
89
|
+
const toConfigRequestValue = value => {
|
|
90
|
+
// Plain strings must NOT be JSON.stringify'd here — the HTTP JSON body
|
|
91
|
+
// already serializes them. Stringify caused values to be stored with
|
|
92
|
+
// literal surrounding quotes (e.g. ""whsec_...""), breaking webhook auth.
|
|
93
|
+
if (value === undefined || value === null) {
|
|
94
|
+
return '';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (typeof value === 'string') {
|
|
98
|
+
return normalizeTextValue(value);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (typeof value === 'object') {
|
|
102
|
+
return JSON.stringify(value);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return String(value);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
const getConfigFields = providerConfig => {
|
|
110
|
+
if (!providerConfig) return [];
|
|
111
|
+
if (Array.isArray(providerConfig.tabs) && providerConfig.tabs.length > 0) {
|
|
112
|
+
return providerConfig.tabs.flatMap(tab => tab.fields || []);
|
|
113
|
+
}
|
|
114
|
+
return providerConfig.fields || [];
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const buildFieldValues = (providerConfig, source) => {
|
|
118
|
+
const sourceMap = normalizeSourceConfigs(source);
|
|
119
|
+
|
|
120
|
+
return getConfigFields(providerConfig).reduce((accumulator, field) => {
|
|
121
|
+
accumulator[field.key] = normalizeTextValue(sourceMap[field.key]);
|
|
122
|
+
return accumulator;
|
|
123
|
+
}, {});
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const extractAuthorizationUrl = response => {
|
|
127
|
+
const candidate =
|
|
128
|
+
response?.member?.[0]?.authorization_url ||
|
|
129
|
+
response?.member?.[0]?.auth_url ||
|
|
130
|
+
response?.member?.[0]?.url ||
|
|
131
|
+
response?.authorization_url ||
|
|
132
|
+
response?.auth_url ||
|
|
133
|
+
response?.url ||
|
|
134
|
+
response?.data?.authorization_url ||
|
|
135
|
+
response?.data?.auth_url ||
|
|
136
|
+
response?.data?.url;
|
|
137
|
+
|
|
138
|
+
return normalizeTextValue(candidate);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const formatUberOAuthError = error => {
|
|
142
|
+
const normalized = normalizeTextValue(error).toLowerCase();
|
|
143
|
+
|
|
144
|
+
if (normalized === 'invalid_scope') {
|
|
145
|
+
return 'O Uber nao liberou o scope pos_provisioning para este app. Esse app precisa estar aprovado/whitelisted no dashboard do Uber.';
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (normalized === 'access_denied') {
|
|
149
|
+
return 'O login do Uber foi cancelado.';
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return normalizeTextValue(error) || 'Nao foi possivel concluir a conexao com o Uber.';
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const openAuthorizationUrl = async authUrl => {
|
|
156
|
+
if (Platform.OS === 'web' && typeof window !== 'undefined' && typeof window.location?.assign === 'function') {
|
|
157
|
+
window.location.assign(authUrl);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
await Linking.openURL(authUrl);
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
export default function IntegrationConfigPage({ route, navigation, embedded = false }) {
|
|
165
|
+
const peopleStore = useStore('people');
|
|
166
|
+
const themeStore = useStore('theme');
|
|
167
|
+
const configsStore = useStore('configs');
|
|
168
|
+
const { currentCompany } = peopleStore.getters;
|
|
169
|
+
const { colors: themeColors } = themeStore.getters;
|
|
170
|
+
const configActions = configsStore.actions;
|
|
171
|
+
const { isSaving } = configsStore.getters;
|
|
172
|
+
const { showError, showSuccess } = useToastMessage();
|
|
173
|
+
const oauthNoticeRef = useRef('');
|
|
174
|
+
|
|
175
|
+
const providerKey = useMemo(
|
|
176
|
+
() =>
|
|
177
|
+
route?.params?.providerKey ||
|
|
178
|
+
ROUTE_PROVIDER_MAP[route?.name] ||
|
|
179
|
+
'',
|
|
180
|
+
[route?.name, route?.params?.providerKey],
|
|
181
|
+
);
|
|
182
|
+
const providerConfig = useMemo(
|
|
183
|
+
() => getIntegrationConfig(providerKey),
|
|
184
|
+
[providerKey],
|
|
185
|
+
);
|
|
186
|
+
const configFields = useMemo(() => getConfigFields(providerConfig), [providerConfig]);
|
|
187
|
+
const fiscalTabs = providerConfig?.tabs || [];
|
|
188
|
+
const [activeFiscalTab, setActiveFiscalTab] = useState(
|
|
189
|
+
() => fiscalTabs[0]?.key || 'general',
|
|
190
|
+
);
|
|
191
|
+
const activeTabDef = useMemo(
|
|
192
|
+
() => fiscalTabs.find(tab => tab.key === activeFiscalTab) || fiscalTabs[0] || null,
|
|
193
|
+
[activeFiscalTab, fiscalTabs],
|
|
194
|
+
);
|
|
195
|
+
const visibleFields = useMemo(
|
|
196
|
+
() => (activeTabDef?.fields?.length ? activeTabDef.fields : configFields),
|
|
197
|
+
[activeTabDef, configFields],
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
useEffect(() => {
|
|
201
|
+
if (fiscalTabs.length && !fiscalTabs.some(tab => tab.key === activeFiscalTab)) {
|
|
202
|
+
setActiveFiscalTab(fiscalTabs[0].key);
|
|
203
|
+
}
|
|
204
|
+
}, [activeFiscalTab, fiscalTabs]);
|
|
205
|
+
const returnPath = useMemo(() => {
|
|
206
|
+
const routePath = normalizeTextValue(
|
|
207
|
+
route?.params?.return_path || route?.params?.returnPath || '',
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
if (routePath) {
|
|
211
|
+
return routePath.startsWith('/') ? routePath : `/${routePath}`;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const normalizedRoutePath = routeNameToPath(route?.name);
|
|
215
|
+
return normalizedRoutePath ? `/${normalizedRoutePath}` : '/uber-integration-page';
|
|
216
|
+
}, [route?.name, route?.params?.returnPath, route?.params?.return_path]);
|
|
217
|
+
|
|
218
|
+
const brandColors = useMemo(
|
|
219
|
+
() =>
|
|
220
|
+
resolveThemePalette(
|
|
221
|
+
{
|
|
222
|
+
...themeColors,
|
|
223
|
+
...(currentCompany?.theme?.colors || {}),
|
|
224
|
+
},
|
|
225
|
+
colors,
|
|
226
|
+
),
|
|
227
|
+
[themeColors, currentCompany?.id],
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
const [loading, setLoading] = useState(true);
|
|
231
|
+
const [refreshing, setRefreshing] = useState(false);
|
|
232
|
+
const [authLoading, setAuthLoading] = useState(false);
|
|
233
|
+
const [configValues, setConfigValues] = useState({});
|
|
234
|
+
const [integrationSummary, setIntegrationSummary] = useState(null);
|
|
235
|
+
|
|
236
|
+
const providerId = currentCompany?.id;
|
|
237
|
+
const providerIri = useMemo(
|
|
238
|
+
() => (providerId ? `/people/${String(providerId).replace(/\D/g, '')}` : ''),
|
|
239
|
+
[providerId],
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
const syncConfigValues = useCallback(
|
|
243
|
+
source => {
|
|
244
|
+
if (!providerConfig) {
|
|
245
|
+
setConfigValues({});
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
setConfigValues(buildFieldValues(providerConfig, source));
|
|
250
|
+
},
|
|
251
|
+
[providerConfig],
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
useEffect(() => {
|
|
255
|
+
syncConfigValues(currentCompany?.configs);
|
|
256
|
+
}, [currentCompany?.configs, syncConfigValues]);
|
|
257
|
+
|
|
258
|
+
useEffect(() => {
|
|
259
|
+
const oauthStatus = normalizeTextValue(route?.params?.oauth_status).toLowerCase();
|
|
260
|
+
const oauthError = normalizeTextValue(route?.params?.oauth_error);
|
|
261
|
+
const oauthKey = `${oauthStatus}|${oauthError}`;
|
|
262
|
+
|
|
263
|
+
if (!oauthStatus || oauthNoticeRef.current === oauthKey) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
oauthNoticeRef.current = oauthKey;
|
|
268
|
+
|
|
269
|
+
if (oauthStatus === 'success') {
|
|
270
|
+
showSuccess('Uber conectado com sucesso.');
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (oauthStatus === 'error') {
|
|
275
|
+
showError(formatUberOAuthError(oauthError));
|
|
276
|
+
}
|
|
277
|
+
}, [route?.params?.oauth_error, route?.params?.oauth_status, showError, showSuccess]);
|
|
278
|
+
|
|
279
|
+
const loadPageData = useCallback(async ({ showLoading = true } = {}) => {
|
|
280
|
+
if (!providerIri || !providerConfig) {
|
|
281
|
+
setIntegrationSummary(null);
|
|
282
|
+
setLoading(false);
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (showLoading) {
|
|
287
|
+
setLoading(true);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
try {
|
|
291
|
+
const integrationPromise = api.fetch('/marketplace/integrations', {
|
|
292
|
+
params: {
|
|
293
|
+
provider_id: providerId,
|
|
294
|
+
},
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
if (configFields.length > 0) {
|
|
298
|
+
const [configResponse, integrationResponse] = await Promise.all([
|
|
299
|
+
api.fetch('/configs', {
|
|
300
|
+
params: {
|
|
301
|
+
people: providerIri,
|
|
302
|
+
},
|
|
303
|
+
}),
|
|
304
|
+
integrationPromise,
|
|
305
|
+
]);
|
|
306
|
+
|
|
307
|
+
syncConfigValues(parseIntegrationCollection(configResponse));
|
|
308
|
+
setIntegrationSummary(
|
|
309
|
+
getIntegrationByKey(integrationResponse, providerConfig.key),
|
|
310
|
+
);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const integrationResponse = await integrationPromise;
|
|
315
|
+
setIntegrationSummary(
|
|
316
|
+
getIntegrationByKey(integrationResponse, providerConfig.key),
|
|
317
|
+
);
|
|
318
|
+
} catch (error) {
|
|
319
|
+
showError(formatApiError(error));
|
|
320
|
+
syncConfigValues(currentCompany?.configs);
|
|
321
|
+
setIntegrationSummary(null);
|
|
322
|
+
} finally {
|
|
323
|
+
if (showLoading) {
|
|
324
|
+
setLoading(false);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}, [
|
|
328
|
+
configFields.length,
|
|
329
|
+
currentCompany?.configs,
|
|
330
|
+
providerConfig,
|
|
331
|
+
providerIri,
|
|
332
|
+
providerId,
|
|
333
|
+
showError,
|
|
334
|
+
syncConfigValues,
|
|
335
|
+
]);
|
|
336
|
+
|
|
337
|
+
useFocusEffect(
|
|
338
|
+
useCallback(() => {
|
|
339
|
+
loadPageData();
|
|
340
|
+
}, [loadPageData]),
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
const onRefresh = useCallback(async () => {
|
|
344
|
+
setRefreshing(true);
|
|
345
|
+
try {
|
|
346
|
+
await loadPageData({ showLoading: false });
|
|
347
|
+
} finally {
|
|
348
|
+
setRefreshing(false);
|
|
349
|
+
}
|
|
350
|
+
}, [loadPageData]);
|
|
351
|
+
|
|
352
|
+
const updateField = useCallback((fieldKey, value) => {
|
|
353
|
+
setConfigValues(currentValues => ({
|
|
354
|
+
...currentValues,
|
|
355
|
+
[fieldKey]: value,
|
|
356
|
+
}));
|
|
357
|
+
}, []);
|
|
358
|
+
|
|
359
|
+
const handleOAuthConnect = useCallback(async () => {
|
|
360
|
+
if (!providerIri || !providerConfig || !providerConfig.oauthConnect) {
|
|
361
|
+
showError('Nao foi possivel identificar a integracao selecionada.');
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
setAuthLoading(true);
|
|
366
|
+
try {
|
|
367
|
+
const response = await api.fetch(providerConfig.authorizationEndpoint, {
|
|
368
|
+
method: 'POST',
|
|
369
|
+
body: {
|
|
370
|
+
provider_id: providerId,
|
|
371
|
+
return_path: returnPath,
|
|
372
|
+
},
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
const authUrl = extractAuthorizationUrl(response);
|
|
376
|
+
if (!authUrl) {
|
|
377
|
+
showError('Nao foi possivel iniciar o login do Uber.');
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
await openAuthorizationUrl(authUrl);
|
|
382
|
+
showSuccess('Abrindo login do Uber.');
|
|
383
|
+
} catch (error) {
|
|
384
|
+
showError(error?.message || 'Nao foi possivel iniciar o login do Uber.');
|
|
385
|
+
} finally {
|
|
386
|
+
setAuthLoading(false);
|
|
387
|
+
}
|
|
388
|
+
}, [providerConfig, providerId, providerIri, returnPath, showError, showSuccess]);
|
|
389
|
+
|
|
390
|
+
const requiredKeys = providerConfig?.requiredKeys || [];
|
|
391
|
+
const connected = integrationSummary && typeof integrationSummary.connected !== 'undefined'
|
|
392
|
+
? isConnectedValue(integrationSummary.connected)
|
|
393
|
+
: requiredKeys.length > 0
|
|
394
|
+
? requiredKeys.every(fieldKey => normalizeTextValue(configValues[fieldKey]) !== '')
|
|
395
|
+
: false;
|
|
396
|
+
const statusTone = connected ? '#16A34A' : '#e67e22';
|
|
397
|
+
const statusText = connected ? 'Conectado' : 'Pendente';
|
|
398
|
+
const editable = Boolean(providerIri && providerConfig && !isSaving && !loading && !providerConfig.oauthConnect);
|
|
399
|
+
const actionLoading = providerConfig?.oauthConnect ? authLoading : isSaving;
|
|
400
|
+
const actionDisabled = providerConfig?.oauthConnect ? authLoading || loading : !editable;
|
|
401
|
+
|
|
402
|
+
const saveIntegration = useCallback(async () => {
|
|
403
|
+
if (!providerIri || !providerConfig || providerConfig.oauthConnect) {
|
|
404
|
+
showError('Nao foi possivel identificar a integracao selecionada.');
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const configs = configFields.map(field => ({
|
|
409
|
+
configKey: field.key,
|
|
410
|
+
configValue: toConfigRequestValue(normalizeTextValue(configValues[field.key])),
|
|
411
|
+
}));
|
|
412
|
+
|
|
413
|
+
try {
|
|
414
|
+
await configActions.addManyConfigs({
|
|
415
|
+
configs,
|
|
416
|
+
people: providerIri,
|
|
417
|
+
module: 4,
|
|
418
|
+
visibility: 'public',
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
showSuccess(`${providerConfig.label} salvo com sucesso.`);
|
|
422
|
+
await loadPageData({ showLoading: false });
|
|
423
|
+
} catch (error) {
|
|
424
|
+
showError(error?.message || 'Nao foi possivel salvar a integracao.');
|
|
425
|
+
}
|
|
426
|
+
}, [
|
|
427
|
+
configActions,
|
|
428
|
+
configFields,
|
|
429
|
+
configValues,
|
|
430
|
+
loadPageData,
|
|
431
|
+
providerConfig,
|
|
432
|
+
providerIri,
|
|
433
|
+
showError,
|
|
434
|
+
showSuccess,
|
|
435
|
+
]);
|
|
436
|
+
|
|
437
|
+
if (!providerConfig) {
|
|
438
|
+
return (
|
|
439
|
+
<SafeAreaView style={styles.container} edges={['bottom']}>
|
|
440
|
+
<View style={styles.centerState}>
|
|
441
|
+
<Icon name="alert-triangle" size={32} color="#e67e22" />
|
|
442
|
+
<Text style={styles.centerStateTitle}>Integracao indisponivel</Text>
|
|
443
|
+
<Text style={styles.centerStateText}>
|
|
444
|
+
A tela solicitada nao possui configuracao cadastrada.
|
|
445
|
+
</Text>
|
|
446
|
+
</View>
|
|
447
|
+
</SafeAreaView>
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (!providerId) {
|
|
452
|
+
return (
|
|
453
|
+
<SafeAreaView style={styles.container} edges={['bottom']}>
|
|
454
|
+
<View style={styles.centerState}>
|
|
455
|
+
<Icon name="building" size={32} color="#94A3B8" />
|
|
456
|
+
<Text style={styles.centerStateTitle}>Selecione uma empresa</Text>
|
|
457
|
+
<Text style={styles.centerStateText}>
|
|
458
|
+
A configuracao da integracao depende da empresa ativa.
|
|
459
|
+
</Text>
|
|
460
|
+
</View>
|
|
461
|
+
</SafeAreaView>
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
if (loading) {
|
|
466
|
+
return (
|
|
467
|
+
<SafeAreaView style={[styles.container, { backgroundColor: brandColors.background }]} edges={['bottom']}>
|
|
468
|
+
<View style={styles.centerState}>
|
|
469
|
+
<ActivityIndicator size="large" color={providerConfig.accent} />
|
|
470
|
+
<Text style={styles.centerStateTitle}>Carregando integracao</Text>
|
|
471
|
+
<Text style={styles.centerStateText}>
|
|
472
|
+
Buscando as credenciais salvas para a empresa ativa.
|
|
473
|
+
</Text>
|
|
474
|
+
</View>
|
|
475
|
+
</SafeAreaView>
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
return (
|
|
480
|
+
<SafeAreaView style={[styles.container, { backgroundColor: brandColors.background }]} edges={['bottom']}>
|
|
481
|
+
<ScrollView
|
|
482
|
+
contentContainerStyle={styles.scroll}
|
|
483
|
+
showsVerticalScrollIndicator={false}
|
|
484
|
+
refreshControl={
|
|
485
|
+
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} tintColor={providerConfig.accent} />
|
|
486
|
+
}>
|
|
487
|
+
{!embedded ? (
|
|
488
|
+
<View style={[styles.heroCard, shadowStyle, { backgroundColor: providerConfig.accent }]}>
|
|
489
|
+
<View style={styles.heroCopy}>
|
|
490
|
+
<Text style={styles.heroEyebrow}>INTEGRACAO</Text>
|
|
491
|
+
<Text style={styles.heroTitle}>{providerConfig.label}</Text>
|
|
492
|
+
<Text style={styles.heroText}>
|
|
493
|
+
{providerConfig.description}
|
|
494
|
+
</Text>
|
|
495
|
+
</View>
|
|
496
|
+
<View style={styles.heroBadge}>
|
|
497
|
+
<Icon name={providerConfig.icon} size={22} color={providerConfig.accent} />
|
|
498
|
+
</View>
|
|
499
|
+
</View>
|
|
500
|
+
) : (
|
|
501
|
+
<View style={[styles.embeddedHeader, shadowStyle]}>
|
|
502
|
+
<Text style={styles.embeddedTitle}>Configuracoes fiscais</Text>
|
|
503
|
+
</View>
|
|
504
|
+
)}
|
|
505
|
+
|
|
506
|
+
{!embedded ? (
|
|
507
|
+
<View style={[styles.statusCard, shadowStyle]}>
|
|
508
|
+
<View style={styles.statusHeader}>
|
|
509
|
+
<View style={styles.statusCopy}>
|
|
510
|
+
<Text style={styles.sectionTitle}>Status</Text>
|
|
511
|
+
<Text style={styles.sectionSubtitle}>
|
|
512
|
+
{providerConfig.oauthConnect
|
|
513
|
+
? 'A integracao fica conectada quando o login do Uber termina e o store e salvo automaticamente.'
|
|
514
|
+
: 'A integracao so aparece como conectada quando todos os campos obrigatorios foram salvos na empresa ativa.'}
|
|
515
|
+
</Text>
|
|
516
|
+
</View>
|
|
517
|
+
|
|
518
|
+
<View
|
|
519
|
+
style={[
|
|
520
|
+
styles.statusBadge,
|
|
521
|
+
{ backgroundColor: withOpacity(statusTone, 0.12) },
|
|
522
|
+
]}>
|
|
523
|
+
<Text style={[styles.statusBadgeText, { color: statusTone }]}>
|
|
524
|
+
{statusText}
|
|
525
|
+
</Text>
|
|
526
|
+
</View>
|
|
527
|
+
</View>
|
|
528
|
+
</View>
|
|
529
|
+
) : null}
|
|
530
|
+
|
|
531
|
+
<View style={[styles.formCard, shadowStyle]}>
|
|
532
|
+
<Text style={styles.cardTitle}>
|
|
533
|
+
{providerConfig.oauthConnect
|
|
534
|
+
? 'Conexao'
|
|
535
|
+
: fiscalTabs.length
|
|
536
|
+
? (activeTabDef?.label || 'Configuracoes')
|
|
537
|
+
: 'Credenciais'}
|
|
538
|
+
</Text>
|
|
539
|
+
{!embedded ? (
|
|
540
|
+
<Text style={styles.cardSubtitle}>
|
|
541
|
+
{providerConfig.oauthConnect
|
|
542
|
+
? 'Use o login oficial do Uber. A store sera localizada e gravada automaticamente na empresa ativa.'
|
|
543
|
+
: 'Salve as credenciais na empresa ativa. O hub de integracoes volta a mostrar o status correto quando voce retornar para a lista.'}
|
|
544
|
+
</Text>
|
|
545
|
+
) : null}
|
|
546
|
+
|
|
547
|
+
{providerConfig.oauthConnect ? (
|
|
548
|
+
<View style={styles.fieldList}>
|
|
549
|
+
<View style={styles.fieldGroup}>
|
|
550
|
+
<Text style={styles.fieldLabel}>Uber OAuth</Text>
|
|
551
|
+
<Text style={styles.fieldKey}>
|
|
552
|
+
Nao ha campos manuais. O login autoriza o app e salva o store automaticamente.
|
|
553
|
+
</Text>
|
|
554
|
+
</View>
|
|
555
|
+
</View>
|
|
556
|
+
) : (
|
|
557
|
+
<View style={styles.fieldList}>
|
|
558
|
+
{fiscalTabs.length > 0 ? (
|
|
559
|
+
<View style={styles.subTabRow}>
|
|
560
|
+
{fiscalTabs.map(tab => {
|
|
561
|
+
const selected = tab.key === (activeTabDef?.key || activeFiscalTab);
|
|
562
|
+
return (
|
|
563
|
+
<TouchableOpacity
|
|
564
|
+
key={tab.key}
|
|
565
|
+
style={[styles.subTabButton, selected && styles.subTabButtonActive]}
|
|
566
|
+
activeOpacity={0.85}
|
|
567
|
+
onPress={() => setActiveFiscalTab(tab.key)}>
|
|
568
|
+
<Text
|
|
569
|
+
style={[
|
|
570
|
+
styles.subTabLabel,
|
|
571
|
+
selected && styles.subTabLabelActive,
|
|
572
|
+
]}>
|
|
573
|
+
{tab.label}
|
|
574
|
+
</Text>
|
|
575
|
+
</TouchableOpacity>
|
|
576
|
+
);
|
|
577
|
+
})}
|
|
578
|
+
</View>
|
|
579
|
+
) : null}
|
|
580
|
+
{activeTabDef?.description ? (
|
|
581
|
+
<Text style={styles.tabDescription}>{activeTabDef.description}</Text>
|
|
582
|
+
) : null}
|
|
583
|
+
{visibleFields.map(field => (
|
|
584
|
+
<View key={field.key} style={styles.fieldGroup}>
|
|
585
|
+
<Text style={styles.fieldLabel}>{field.label}</Text>
|
|
586
|
+
{!embedded ? <Text style={styles.fieldKey}>{field.key}</Text> : null}
|
|
587
|
+
{field.type === 'select' ? (
|
|
588
|
+
<View style={styles.selectList}>
|
|
589
|
+
{(field.options || []).map(option => {
|
|
590
|
+
const selected = String(configValues[field.key] || '') === String(option.value);
|
|
591
|
+
return (
|
|
592
|
+
<TouchableOpacity
|
|
593
|
+
key={String(option.value)}
|
|
594
|
+
style={[
|
|
595
|
+
styles.selectOption,
|
|
596
|
+
selected && styles.selectOptionActive,
|
|
597
|
+
!editable && styles.inputDisabled,
|
|
598
|
+
]}
|
|
599
|
+
disabled={!editable}
|
|
600
|
+
activeOpacity={0.85}
|
|
601
|
+
onPress={() => updateField(field.key, String(option.value))}>
|
|
602
|
+
<Text
|
|
603
|
+
style={[
|
|
604
|
+
styles.selectOptionText,
|
|
605
|
+
selected && styles.selectOptionTextActive,
|
|
606
|
+
]}>
|
|
607
|
+
{option.label}
|
|
608
|
+
</Text>
|
|
609
|
+
</TouchableOpacity>
|
|
610
|
+
);
|
|
611
|
+
})}
|
|
612
|
+
</View>
|
|
613
|
+
) : field.type === 'file' ? (
|
|
614
|
+
<View style={styles.fileFieldWrap}>
|
|
615
|
+
{configValues[field.key] ? (
|
|
616
|
+
<Text style={styles.fieldHint}>
|
|
617
|
+
Arquivo vinculado (id: {String(configValues[field.key]).replace(/\D/g, '') || configValues[field.key]})
|
|
618
|
+
</Text>
|
|
619
|
+
) : (
|
|
620
|
+
<Text style={styles.fieldHint}>Nenhum certificado vinculado.</Text>
|
|
621
|
+
)}
|
|
622
|
+
<DefaultUpload
|
|
623
|
+
relationStoreName="people"
|
|
624
|
+
relationField="people"
|
|
625
|
+
relationResource="people"
|
|
626
|
+
entityId={providerId}
|
|
627
|
+
companyId={providerId}
|
|
628
|
+
context={field.fileContext || 'company_certificate'}
|
|
629
|
+
libraryContexts={[field.fileContext || 'company_certificate']}
|
|
630
|
+
acceptedTypes={field.accept || '.pfx,.p12,application/x-pkcs12'}
|
|
631
|
+
fileType=""
|
|
632
|
+
fileTypeLabel="certificado"
|
|
633
|
+
title={field.label}
|
|
634
|
+
triggerLabel="Gerenciar certificado"
|
|
635
|
+
managerTitle="Gerenciador de arquivos"
|
|
636
|
+
searchPlaceholder="Buscar certificado"
|
|
637
|
+
uploadButtonLabel="Enviar certificado"
|
|
638
|
+
emptyAttachmentLabel="Nenhum certificado anexado."
|
|
639
|
+
emptyLibraryLabel="Nenhum arquivo encontrado."
|
|
640
|
+
uploadSuccessMessage="Certificado enviado."
|
|
641
|
+
attachSuccessMessage="Certificado vinculado."
|
|
642
|
+
removeSuccessMessage="Certificado removido."
|
|
643
|
+
showInlineContent={false}
|
|
644
|
+
uploadResultAlreadyAttached
|
|
645
|
+
requireEntity={false}
|
|
646
|
+
onUploadFile={async ({ file, companyId, context, entityId }) => {
|
|
647
|
+
const uploaded = await uploadFileToApi({
|
|
648
|
+
file,
|
|
649
|
+
context: context || field.fileContext || 'company_certificate',
|
|
650
|
+
peopleId: companyId || providerId,
|
|
651
|
+
entityId: entityId || providerId,
|
|
652
|
+
});
|
|
653
|
+
const id = extractFileId(uploaded);
|
|
654
|
+
const iri = toFileIri(uploaded);
|
|
655
|
+
const value = id ? String(id) : iri || '';
|
|
656
|
+
if (!value) {
|
|
657
|
+
throw new Error('Upload sem identificador de arquivo.');
|
|
658
|
+
}
|
|
659
|
+
updateField(field.key, value);
|
|
660
|
+
return uploaded;
|
|
661
|
+
}}
|
|
662
|
+
onAttachFile={async fileObj => {
|
|
663
|
+
const id = extractFileId(fileObj);
|
|
664
|
+
const iri = toFileIri(fileObj);
|
|
665
|
+
const value = id ? String(id) : iri || '';
|
|
666
|
+
if (!value) {
|
|
667
|
+
throw new Error('Arquivo sem identificador.');
|
|
668
|
+
}
|
|
669
|
+
updateField(field.key, value);
|
|
670
|
+
return fileObj;
|
|
671
|
+
}}
|
|
672
|
+
onRemoveAttachment={async () => {
|
|
673
|
+
updateField(field.key, '');
|
|
674
|
+
return true;
|
|
675
|
+
}}
|
|
676
|
+
renderTrigger={({ openManager, uploading }) => (
|
|
677
|
+
<TouchableOpacity
|
|
678
|
+
style={[
|
|
679
|
+
styles.filePickerButton,
|
|
680
|
+
!editable && styles.inputDisabled,
|
|
681
|
+
]}
|
|
682
|
+
disabled={!editable || uploading}
|
|
683
|
+
activeOpacity={0.85}
|
|
684
|
+
onPress={openManager}>
|
|
685
|
+
{uploading ? (
|
|
686
|
+
<ActivityIndicator color="#166534" />
|
|
687
|
+
) : (
|
|
688
|
+
<Icon name="folder" size={16} color="#166534" />
|
|
689
|
+
)}
|
|
690
|
+
<Text style={styles.filePickerButtonText}>
|
|
691
|
+
{configValues[field.key]
|
|
692
|
+
? 'Trocar certificado (gerenciador)'
|
|
693
|
+
: 'Selecionar / enviar certificado'}
|
|
694
|
+
</Text>
|
|
695
|
+
</TouchableOpacity>
|
|
696
|
+
)}
|
|
697
|
+
/>
|
|
698
|
+
</View>
|
|
699
|
+
) : (
|
|
700
|
+
<TextInput
|
|
701
|
+
style={[
|
|
702
|
+
styles.input,
|
|
703
|
+
!editable && styles.inputDisabled,
|
|
704
|
+
]}
|
|
705
|
+
value={configValues[field.key] || ''}
|
|
706
|
+
onChangeText={value => updateField(field.key, value)}
|
|
707
|
+
editable={editable}
|
|
708
|
+
autoCapitalize="none"
|
|
709
|
+
autoCorrect={false}
|
|
710
|
+
secureTextEntry={Boolean(field.secureTextEntry)}
|
|
711
|
+
placeholder={field.placeholder}
|
|
712
|
+
/>
|
|
713
|
+
)}
|
|
714
|
+
</View>
|
|
715
|
+
))}
|
|
716
|
+
</View>
|
|
717
|
+
)}
|
|
718
|
+
|
|
719
|
+
<TouchableOpacity
|
|
720
|
+
style={[
|
|
721
|
+
styles.saveButton,
|
|
722
|
+
{ backgroundColor: providerConfig.accent },
|
|
723
|
+
actionDisabled && styles.saveButtonDisabled,
|
|
724
|
+
]}
|
|
725
|
+
disabled={actionDisabled}
|
|
726
|
+
activeOpacity={0.9}
|
|
727
|
+
onPress={providerConfig.oauthConnect ? handleOAuthConnect : saveIntegration}>
|
|
728
|
+
{actionLoading ? (
|
|
729
|
+
<ActivityIndicator color="#FFFFFF" />
|
|
730
|
+
) : (
|
|
731
|
+
<Icon name={providerConfig.oauthConnect ? 'log-in' : 'save'} size={16} color="#FFFFFF" />
|
|
732
|
+
)}
|
|
733
|
+
<Text style={styles.saveButtonText}>
|
|
734
|
+
{providerConfig.oauthConnect
|
|
735
|
+
? providerConfig.connectLabel || 'Conectar'
|
|
736
|
+
: providerConfig.saveLabel}
|
|
737
|
+
</Text>
|
|
738
|
+
</TouchableOpacity>
|
|
739
|
+
</View>
|
|
740
|
+
</ScrollView>
|
|
741
|
+
</SafeAreaView>
|
|
742
|
+
);
|
|
743
|
+
}
|
|
744
|
+
// TODO(store-first): quando este arquivo for mexido, mover a leitura para stores, remover api.fetch e evitar repassar dados em objetos quando o store ja resolver isso.
|