zoho crm data fetched from bulkread data
This commit is contained in:
parent
5428ac9f3e
commit
536a72ff4a
File diff suppressed because it is too large
Load Diff
@ -34,7 +34,8 @@ import {
|
||||
selectDealsPagination,
|
||||
selectSalesOrdersPagination,
|
||||
selectPurchaseOrdersPagination,
|
||||
selectInvoicesPagination
|
||||
selectInvoicesPagination,
|
||||
selectCrmCounts
|
||||
} from '../store/selectors';
|
||||
import {
|
||||
fetchAllCrmData,
|
||||
@ -45,6 +46,7 @@ import {
|
||||
fetchSalesOrders,
|
||||
fetchPurchaseOrders,
|
||||
fetchInvoices,
|
||||
fetchCrmCounts,
|
||||
resetLeadsPagination,
|
||||
resetTasksPagination,
|
||||
resetContactsPagination,
|
||||
@ -80,6 +82,7 @@ const ZohoCrmDataScreen: React.FC = () => {
|
||||
const salesOrdersPagination = useSelector(selectSalesOrdersPagination);
|
||||
const purchaseOrdersPagination = useSelector(selectPurchaseOrdersPagination);
|
||||
const invoicesPagination = useSelector(selectInvoicesPagination);
|
||||
const counts = useSelector(selectCrmCounts);
|
||||
|
||||
// Create CRM data object from Redux state
|
||||
const crmData: CrmData = useMemo(() => ({
|
||||
@ -184,6 +187,8 @@ const ZohoCrmDataScreen: React.FC = () => {
|
||||
|
||||
useEffect(() => {
|
||||
fetchCrmData();
|
||||
// Fetch counts in parallel
|
||||
dispatch(fetchCrmCounts());
|
||||
}, []);
|
||||
|
||||
const handleRefresh = useCallback(() => {
|
||||
@ -196,8 +201,9 @@ const ZohoCrmDataScreen: React.FC = () => {
|
||||
dispatch(resetPurchaseOrdersPagination());
|
||||
dispatch(resetInvoicesPagination());
|
||||
|
||||
// Then fetch fresh data
|
||||
// Then fetch fresh data and counts
|
||||
fetchCrmData(true);
|
||||
dispatch(fetchCrmCounts());
|
||||
}, [fetchCrmData, dispatch]);
|
||||
|
||||
const handleRetry = useCallback(() => {
|
||||
@ -230,26 +236,52 @@ const ZohoCrmDataScreen: React.FC = () => {
|
||||
errors.salesOrders || errors.purchaseOrders || errors.invoices;
|
||||
|
||||
|
||||
// Tab configuration
|
||||
// Tab configuration with counts from API
|
||||
const tabs = [
|
||||
{ key: 'leads', label: 'Leads', icon: 'account-heart', count: crmData.leads.length },
|
||||
{ key: 'tasks', label: 'Tasks', icon: 'check-circle', count: crmData.tasks.length },
|
||||
{ key: 'contacts', label: 'Contacts', icon: 'account-group', count: crmData.contacts.length },
|
||||
{ key: 'deals', label: 'Deals', icon: 'handshake', count: crmData.deals.length },
|
||||
{ key: 'salesOrders', label: 'Sales Orders', icon: 'shopping', count: crmData.salesOrders.length },
|
||||
{ key: 'purchaseOrders', label: 'Purchase Orders', icon: 'cart', count: crmData.purchaseOrders.length },
|
||||
{ key: 'invoices', label: 'Invoices', icon: 'receipt', count: crmData.invoices.length },
|
||||
{
|
||||
key: 'leads',
|
||||
label: 'Leads',
|
||||
icon: 'account-heart',
|
||||
count: counts?.leads || crmData.leads.length
|
||||
},
|
||||
{
|
||||
key: 'tasks',
|
||||
label: 'Tasks',
|
||||
icon: 'check-circle',
|
||||
count: counts?.tasks || crmData.tasks.length
|
||||
},
|
||||
{
|
||||
key: 'contacts',
|
||||
label: 'Contacts',
|
||||
icon: 'account-group',
|
||||
count: counts?.contacts || crmData.contacts.length
|
||||
},
|
||||
{
|
||||
key: 'deals',
|
||||
label: 'Deals',
|
||||
icon: 'handshake',
|
||||
count: counts?.deals || crmData.deals.length
|
||||
},
|
||||
{
|
||||
key: 'salesOrders',
|
||||
label: 'Sales Orders',
|
||||
icon: 'shopping',
|
||||
count: counts?.salesOrders || crmData.salesOrders.length
|
||||
},
|
||||
{
|
||||
key: 'purchaseOrders',
|
||||
label: 'Purchase Orders',
|
||||
icon: 'cart',
|
||||
count: counts?.purchaseOrders || crmData.purchaseOrders.length
|
||||
},
|
||||
{
|
||||
key: 'invoices',
|
||||
label: 'Invoices',
|
||||
icon: 'receipt',
|
||||
count: counts?.invoices || crmData.invoices.length
|
||||
},
|
||||
] as const;
|
||||
|
||||
if (isLoading && !crmData.leads.length) {
|
||||
return <LoadingSpinner />;
|
||||
}
|
||||
|
||||
if (hasError && !crmData.leads.length) {
|
||||
return <ErrorState onRetry={handleRetry} />;
|
||||
}
|
||||
|
||||
|
||||
const renderTabContent = useCallback(() => {
|
||||
const commonFlatListProps = {
|
||||
numColumns: 1,
|
||||
@ -385,6 +417,15 @@ const ZohoCrmDataScreen: React.FC = () => {
|
||||
}
|
||||
}, [selectedTab, crmData, handleCardPress, loadMoreData, renderFooter, refreshing, handleRefresh]);
|
||||
|
||||
// Conditional returns after all hooks
|
||||
if (isLoading && !crmData.leads.length) {
|
||||
return <LoadingSpinner />;
|
||||
}
|
||||
|
||||
if (hasError && !crmData.leads.length) {
|
||||
return <ErrorState onRetry={handleRetry} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={[styles.container, { backgroundColor: colors.background }]}>
|
||||
{/* Fixed Header */}
|
||||
|
||||
@ -76,5 +76,181 @@ export const crmAPI = {
|
||||
};
|
||||
return http.get<CrmApiResponse<CrmPaginatedResponse<any>>>(`/api/v1/integrations/zoho/crm/invoices`, queryParams);
|
||||
},
|
||||
|
||||
// Get counts for all CRM modules
|
||||
getCrmCounts: () => {
|
||||
return http.get<{
|
||||
status: string;
|
||||
message: string;
|
||||
data: {
|
||||
data: {
|
||||
accounts: { count: number; success: boolean };
|
||||
leads: { count: number; success: boolean };
|
||||
tasks: { count: number; success: boolean };
|
||||
invoices: { count: number; success: boolean };
|
||||
sales_orders: { count: number; success: boolean };
|
||||
purchase_orders: { count: number; success: boolean };
|
||||
deals: { count: number; success: boolean };
|
||||
contacts: { count: number; success: boolean };
|
||||
vendors: { count: number; success: boolean };
|
||||
};
|
||||
info: {
|
||||
totalModules: number;
|
||||
successfulModules: number;
|
||||
failedModules: number;
|
||||
};
|
||||
};
|
||||
timestamp: string;
|
||||
}>('/api/v1/integrations/zoho/crm/counts', { provider: 'zoho' });
|
||||
},
|
||||
|
||||
// Get comprehensive CRM KPIs report
|
||||
getCrmKPIs: () => {
|
||||
return http.get<{
|
||||
status: string;
|
||||
message: string;
|
||||
data: {
|
||||
businessOverview: {
|
||||
totalRecords: number;
|
||||
moduleCounts: {
|
||||
leads: number;
|
||||
contacts: number;
|
||||
accounts: number;
|
||||
deals: number;
|
||||
tasks: number;
|
||||
vendors: number;
|
||||
invoices: number;
|
||||
salesOrders: number;
|
||||
purchaseOrders: number;
|
||||
};
|
||||
revenueMetrics: {
|
||||
totalRevenue: number;
|
||||
totalDealValue: number;
|
||||
totalInvoices: number;
|
||||
averageInvoiceValue: number;
|
||||
overdueAmount: number;
|
||||
overdueCount: number;
|
||||
revenueByMonth: Array<{ month: string; revenue: number }>;
|
||||
topRevenueAccounts: Array<{ account: string; revenue: number }>;
|
||||
};
|
||||
growthMetrics: {
|
||||
recentLeads: number;
|
||||
previousLeads: number;
|
||||
recentDeals: number;
|
||||
previousDeals: number;
|
||||
leadGrowthRate: string;
|
||||
dealGrowthRate: string;
|
||||
};
|
||||
};
|
||||
salesPipeline: {
|
||||
totalLeads: number;
|
||||
totalDeals: number;
|
||||
qualifiedLeads: number;
|
||||
convertedLeads: number;
|
||||
openDeals: number;
|
||||
closedWonDeals: number;
|
||||
totalPipelineValue: number;
|
||||
conversionRate: string;
|
||||
qualificationRate: string;
|
||||
winRate: string;
|
||||
dealStages: Record<string, number>;
|
||||
leadSources: Record<string, number>;
|
||||
conversionFunnel: {
|
||||
leads: { count: number; percentage: number };
|
||||
qualified: { count: number; percentage: string };
|
||||
converted: { count: number; percentage: string };
|
||||
deals: { count: number; percentage: number };
|
||||
closedWon: { count: number; percentage: string };
|
||||
};
|
||||
};
|
||||
operationalEfficiency: {
|
||||
totalTasks: number;
|
||||
completedTasks: number;
|
||||
overdueTasks: number;
|
||||
highPriorityTasks: number;
|
||||
taskCompletionRate: string;
|
||||
overdueRate: string;
|
||||
taskStatus: Record<string, number>;
|
||||
invoiceStatus: Record<string, number>;
|
||||
orderStatus: {
|
||||
salesOrders: Record<string, number>;
|
||||
purchaseOrders: Record<string, number>;
|
||||
};
|
||||
};
|
||||
customerRelationships: {
|
||||
totalContacts: number;
|
||||
totalAccounts: number;
|
||||
totalVendors: number;
|
||||
recentContacts: number;
|
||||
accountsWithRevenue: number;
|
||||
contactToAccountRatio: string;
|
||||
industryDistribution: Record<string, number>;
|
||||
contactSources: Record<string, number>;
|
||||
accountOwnership: Record<string, number>;
|
||||
};
|
||||
financialHealth: {
|
||||
totalRevenue: number;
|
||||
totalInvoices: number;
|
||||
averageInvoiceValue: number;
|
||||
overdueAmount: number;
|
||||
revenueByMonth: Array<{ month: string; revenue: number }>;
|
||||
topRevenueAccounts: Array<{ account: string; revenue: number }>;
|
||||
};
|
||||
businessInsights: {
|
||||
topPerformingSources: Array<{
|
||||
source: string;
|
||||
totalLeads: number;
|
||||
qualifiedLeads: number;
|
||||
conversionRate: string;
|
||||
avgQuality: string;
|
||||
}>;
|
||||
topRevenueOwners: Array<{
|
||||
owner: string;
|
||||
count: number;
|
||||
total: number;
|
||||
}>;
|
||||
mostActiveOwners: Array<{
|
||||
owner: string;
|
||||
count: number;
|
||||
value: number;
|
||||
}>;
|
||||
businessHealth: {
|
||||
overallScore: number;
|
||||
leadGeneration: number;
|
||||
processEfficiency: number;
|
||||
qualityMetrics: number;
|
||||
};
|
||||
recommendations: Array<{
|
||||
category: string;
|
||||
priority: string;
|
||||
title: string;
|
||||
description: string;
|
||||
action: string;
|
||||
}>;
|
||||
};
|
||||
generatedAt: string;
|
||||
advancedFinancialKPIs: {
|
||||
revenueGrowthRate: { value: number; percentage: string; trend: string };
|
||||
grossMargin: { value: number; percentage: string; totalRevenue: number; totalCOGS: number; grossProfit: number };
|
||||
netProfitMargin: { value: number; percentage: string; totalRevenue: number; totalCosts: number; netProfit: number };
|
||||
operatingCashFlow: { cashIn: number; cashOut: number; netCashFlow: number; cashFlowRatio: string };
|
||||
cashRunway: { months: string; status: string; monthlyCashIn: number; monthlyCashOut: number };
|
||||
customerAcquisitionCost: { value: number; marketingSpend: number; newCustomers: number; status: string };
|
||||
customerLifetimeValue: { value: number; totalRevenue: number; totalCustomers: number; status: string };
|
||||
ltvToCacRatio: { ratio: string; status: string };
|
||||
netRevenueRetention: { percentage: string; recurringRevenue: number; currentCustomers: number; status: string };
|
||||
churnRate: { percentage: string; lostCustomers: number; totalCustomers: number; status: string };
|
||||
averageRevenuePerAccount: { value: number; totalRevenue: number; totalAccounts: number; status: string };
|
||||
burnMultiple: { multiple: string; totalBurn: number; newARR: number; status: string };
|
||||
salesCycleLength: { averageDays: number; status: string };
|
||||
netPromoterScore: { score: number; promoters: number; totalCustomers: number; status: string; note: string };
|
||||
daysSalesOutstanding: { days: number; totalOutstanding: number; averageDailySales: number; status: string };
|
||||
growthEfficiencyRatio: { ratio: string; status: string };
|
||||
ebitda: { value: number; totalRevenue: number; totalCosts: number; margin: string; status: string };
|
||||
};
|
||||
};
|
||||
timestamp: string;
|
||||
}>('/api/v1/reports/crm/kpis');
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@ -63,6 +63,22 @@ export interface CrmState {
|
||||
// Statistics
|
||||
stats: CrmStats | null;
|
||||
|
||||
// Module counts
|
||||
counts: {
|
||||
leads: number;
|
||||
tasks: number;
|
||||
contacts: number;
|
||||
deals: number;
|
||||
salesOrders: number;
|
||||
purchaseOrders: number;
|
||||
invoices: number;
|
||||
accounts: number;
|
||||
vendors: number;
|
||||
} | null;
|
||||
|
||||
// Comprehensive KPIs report
|
||||
kpis: any | null;
|
||||
|
||||
// Last updated timestamps
|
||||
lastUpdated: {
|
||||
leads: string | null;
|
||||
@ -115,6 +131,8 @@ const initialState: CrmState = {
|
||||
invoices: { page: 1, count: 0, moreRecords: false },
|
||||
},
|
||||
stats: null,
|
||||
counts: null,
|
||||
kpis: null,
|
||||
lastUpdated: {
|
||||
leads: null,
|
||||
tasks: null,
|
||||
@ -205,6 +223,41 @@ export const fetchInvoices = createAsyncThunk(
|
||||
}
|
||||
);
|
||||
|
||||
// Fetch CRM counts
|
||||
export const fetchCrmCounts = createAsyncThunk(
|
||||
'crm/fetchCounts',
|
||||
async () => {
|
||||
const response = await crmAPI.getCrmCounts();
|
||||
return response.data?.data || {
|
||||
data: {
|
||||
accounts: { count: 0, success: false },
|
||||
leads: { count: 0, success: false },
|
||||
tasks: { count: 0, success: false },
|
||||
invoices: { count: 0, success: false },
|
||||
sales_orders: { count: 0, success: false },
|
||||
purchase_orders: { count: 0, success: false },
|
||||
deals: { count: 0, success: false },
|
||||
contacts: { count: 0, success: false },
|
||||
vendors: { count: 0, success: false },
|
||||
},
|
||||
info: {
|
||||
totalModules: 0,
|
||||
successfulModules: 0,
|
||||
failedModules: 0,
|
||||
}
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// Fetch comprehensive CRM KPIs
|
||||
export const fetchCrmKPIs = createAsyncThunk(
|
||||
'crm/fetchKPIs',
|
||||
async () => {
|
||||
const response = await crmAPI.getCrmKPIs();
|
||||
return response.data?.data || null;
|
||||
}
|
||||
);
|
||||
|
||||
// Fetch all CRM data
|
||||
export const fetchAllCrmData = createAsyncThunk(
|
||||
'crm/fetchAllData',
|
||||
@ -564,6 +617,41 @@ const crmSlice = createSlice({
|
||||
state.errors.salesOrders = errorMessage;
|
||||
state.errors.purchaseOrders = errorMessage;
|
||||
state.errors.invoices = errorMessage;
|
||||
})
|
||||
|
||||
// Fetch CRM counts
|
||||
.addCase(fetchCrmCounts.pending, (state) => {
|
||||
// No loading state needed for counts as it's fetched in background
|
||||
})
|
||||
.addCase(fetchCrmCounts.fulfilled, (state, action) => {
|
||||
const { data } = action.payload;
|
||||
state.counts = {
|
||||
leads: data.leads?.count || 0,
|
||||
tasks: data.tasks?.count || 0,
|
||||
contacts: data.contacts?.count || 0,
|
||||
deals: data.deals?.count || 0,
|
||||
salesOrders: data.sales_orders?.count || 0,
|
||||
purchaseOrders: data.purchase_orders?.count || 0,
|
||||
invoices: data.invoices?.count || 0,
|
||||
accounts: data.accounts?.count || 0,
|
||||
vendors: data.vendors?.count || 0,
|
||||
};
|
||||
})
|
||||
.addCase(fetchCrmCounts.rejected, (state) => {
|
||||
// Keep existing counts on error
|
||||
console.warn('Failed to fetch CRM counts');
|
||||
})
|
||||
|
||||
// Fetch CRM KPIs
|
||||
.addCase(fetchCrmKPIs.pending, (state) => {
|
||||
// No loading state needed for KPIs as it's fetched in background
|
||||
})
|
||||
.addCase(fetchCrmKPIs.fulfilled, (state, action) => {
|
||||
state.kpis = action.payload;
|
||||
})
|
||||
.addCase(fetchCrmKPIs.rejected, (state) => {
|
||||
// Keep existing KPIs on error
|
||||
console.warn('Failed to fetch CRM KPIs');
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@ -45,6 +45,12 @@ export const selectPurchaseOrdersError = (state: RootState) => state.crm.errors.
|
||||
export const selectInvoicesError = (state: RootState) => state.crm.errors.invoices;
|
||||
|
||||
// Computed selectors for dashboard
|
||||
// Counts selector
|
||||
export const selectCrmCounts = (state: RootState) => state.crm.counts;
|
||||
|
||||
// KPIs selector
|
||||
export const selectCrmKPIs = (state: RootState) => state.crm.kpis;
|
||||
|
||||
export const selectCrmStats = createSelector(
|
||||
[selectLeads, selectTasks, selectContacts, selectDeals, selectSalesOrders, selectPurchaseOrders, selectInvoices],
|
||||
(leads, tasks, contacts, deals, salesOrders, purchaseOrders, invoices): CrmStats => {
|
||||
|
||||
@ -15,6 +15,7 @@ import { useNavigation } from '@react-navigation/native';
|
||||
import { manageToken } from '../services/integrationAPI';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { RootState } from '@/store/store';
|
||||
import http from '@/services/http';
|
||||
|
||||
// Types
|
||||
type ServiceKey = 'zohoProjects' | 'zohoCRM' | 'zohoBooks' | 'zohoPeople';
|
||||
@ -38,6 +39,8 @@ interface ZohoAuthState {
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
currentUrl: string;
|
||||
processing: boolean;
|
||||
processingStep: string;
|
||||
}
|
||||
|
||||
// Zoho OAuth Configuration
|
||||
@ -174,23 +177,98 @@ const ZohoAuth: React.FC<ZohoAuthProps> = ({
|
||||
loading: true,
|
||||
error: null,
|
||||
currentUrl: buildZohoAuthUrl(currentScope),
|
||||
processing: false,
|
||||
processingStep: '',
|
||||
});
|
||||
|
||||
// Backend exchange mode: only log and return the authorization code to the caller
|
||||
const handleAuthorizationCode = useCallback(async (authCode: string) => {
|
||||
console.log('[ZohoAuth] Authorization code received:', authCode);
|
||||
console.log('[ZohoAuth] Send this code to your backend to exchange for tokens.',user);
|
||||
const response = await manageToken.manageToken({ authorization_code: authCode, id: user?.uuid, service_name: 'zoho', access_token: accessToken });
|
||||
console.log('[ZohoAuth] Response from manageToken:', response);
|
||||
// Return the code via onAuthSuccess using the existing shape
|
||||
onAuthSuccess?.({
|
||||
accessToken: authCode, // This is the AUTHORIZATION CODE, not an access token
|
||||
tokenType: 'authorization_code',
|
||||
scope: currentScope,
|
||||
expiresIn: undefined,
|
||||
refreshToken: undefined,
|
||||
});
|
||||
}, [onAuthSuccess, currentScope]);
|
||||
|
||||
// Show processing modal
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
processing: true,
|
||||
processingStep: 'Storing credentials and preparing data setup...'
|
||||
}));
|
||||
|
||||
try {
|
||||
const response = await manageToken.manageToken({
|
||||
authorization_code: authCode,
|
||||
id: user?.uuid || '',
|
||||
service_name: 'zoho',
|
||||
access_token: accessToken || ''
|
||||
});
|
||||
|
||||
console.log('[ZohoAuth] Response from manageToken:', response);
|
||||
|
||||
// Check if response status is success
|
||||
if (response?.data && typeof response.data === 'object' && 'status' in response.data && response.data.status === 'success') {
|
||||
console.log('[ZohoAuth] Token exchange successful, scheduling bulk read job...');
|
||||
|
||||
// Update processing step
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
processingStep: 'Initializing data import and bulk read job...'
|
||||
}));
|
||||
|
||||
try {
|
||||
// Schedule bulk read job
|
||||
const bulkReadResponse = await http.post('/api/v1/integrations/zoho/bulk-read/schedule?provider=zoho', {}, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${accessToken}`,
|
||||
},
|
||||
});
|
||||
|
||||
console.log('[ZohoAuth] Bulk read job scheduled:', bulkReadResponse.data);
|
||||
|
||||
// Update processing step
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
processingStep: 'Data import process initiated successfully!'
|
||||
}));
|
||||
|
||||
// Wait a moment to show success message
|
||||
await new Promise<void>(resolve => setTimeout(resolve, 1500));
|
||||
} catch (bulkReadError) {
|
||||
console.warn('[ZohoAuth] Failed to schedule bulk read job:', bulkReadError);
|
||||
// Don't fail the auth process if bulk read scheduling fails
|
||||
}
|
||||
} else {
|
||||
const errorMessage = response?.data && typeof response.data === 'object' && 'message' in response.data
|
||||
? response.data.message
|
||||
: 'Unknown error';
|
||||
console.warn('[ZohoAuth] Token exchange failed:', errorMessage);
|
||||
}
|
||||
|
||||
// Hide processing modal and call success callback
|
||||
setState(prev => ({ ...prev, processing: false, processingStep: '' }));
|
||||
|
||||
// Return the code via onAuthSuccess using the existing shape
|
||||
onAuthSuccess?.({
|
||||
accessToken: authCode, // This is the AUTHORIZATION CODE, not an access token
|
||||
tokenType: 'authorization_code',
|
||||
scope: currentScope,
|
||||
expiresIn: undefined,
|
||||
refreshToken: undefined,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[ZohoAuth] Error during token exchange:', error);
|
||||
|
||||
// Hide processing modal and call success callback
|
||||
setState(prev => ({ ...prev, processing: false, processingStep: '' }));
|
||||
|
||||
// Still call onAuthSuccess with the auth code even if bulk read fails
|
||||
onAuthSuccess?.({
|
||||
accessToken: authCode,
|
||||
tokenType: 'authorization_code',
|
||||
scope: currentScope,
|
||||
expiresIn: undefined,
|
||||
refreshToken: undefined,
|
||||
});
|
||||
}
|
||||
}, [onAuthSuccess, currentScope, user?.uuid, accessToken]);
|
||||
|
||||
// Handle WebView navigation state changes
|
||||
const handleNavigationStateChange = useCallback((navState: any) => {
|
||||
@ -372,6 +450,28 @@ const ZohoAuth: React.FC<ZohoAuthProps> = ({
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Processing Modal */}
|
||||
{state.processing && (
|
||||
<View style={[styles.processingOverlay, { backgroundColor: 'rgba(0, 0, 0, 0.7)' }]}>
|
||||
<View style={[styles.processingModal, { backgroundColor: colors.surface }]}>
|
||||
<View style={styles.processingIconContainer}>
|
||||
<ActivityIndicator size="large" color={colors.primary} />
|
||||
</View>
|
||||
<Text style={[styles.processingTitle, { color: colors.text, fontFamily: fonts.bold }]}>
|
||||
Setting Up Integration
|
||||
</Text>
|
||||
<Text style={[styles.processingStep, { color: colors.textLight, fontFamily: fonts.regular }]}>
|
||||
{state.processingStep}
|
||||
</Text>
|
||||
<View style={styles.processingDots}>
|
||||
<View style={[styles.dot, { backgroundColor: colors.primary }]} />
|
||||
<View style={[styles.dot, { backgroundColor: colors.primary }]} />
|
||||
<View style={[styles.dot, { backgroundColor: colors.primary }]} />
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Loading Overlay */}
|
||||
{state.loading && !state.error && (
|
||||
<View style={[styles.loadingOverlay, { backgroundColor: colors.background }]}>
|
||||
@ -383,7 +483,7 @@ const ZohoAuth: React.FC<ZohoAuthProps> = ({
|
||||
)}
|
||||
|
||||
{/* WebView */}
|
||||
{!state.error && (
|
||||
{!state.error && !state.processing && (
|
||||
<WebView
|
||||
ref={webViewRef}
|
||||
source={{ uri: state.currentUrl }}
|
||||
@ -502,6 +602,53 @@ const styles = StyleSheet.create({
|
||||
fontSize: 14,
|
||||
marginTop: 8,
|
||||
},
|
||||
processingOverlay: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
zIndex: 2000,
|
||||
},
|
||||
processingModal: {
|
||||
borderRadius: 16,
|
||||
padding: 32,
|
||||
alignItems: 'center',
|
||||
minWidth: 280,
|
||||
maxWidth: 320,
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 4 },
|
||||
shadowOpacity: 0.2,
|
||||
shadowRadius: 8,
|
||||
elevation: 8,
|
||||
},
|
||||
processingIconContainer: {
|
||||
marginBottom: 20,
|
||||
},
|
||||
processingTitle: {
|
||||
fontSize: 20,
|
||||
marginBottom: 12,
|
||||
textAlign: 'center',
|
||||
},
|
||||
processingStep: {
|
||||
fontSize: 14,
|
||||
textAlign: 'center',
|
||||
lineHeight: 20,
|
||||
marginBottom: 20,
|
||||
},
|
||||
processingDots: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
dot: {
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: 4,
|
||||
marginHorizontal: 4,
|
||||
},
|
||||
});
|
||||
|
||||
export default ZohoAuth;
|
||||
|
||||
@ -8,9 +8,9 @@ import { clearSelectedService } from '@/modules/integrations/store/integrationsS
|
||||
let pendingRequest: any = null;
|
||||
|
||||
const http = create({
|
||||
// baseURL: 'http://10.175.59.235:4000',
|
||||
// baseURL: 'http://160.187.167.216',
|
||||
baseURL: 'https://gold-tires-sniff.loca.lt',
|
||||
// baseURL: 'http://192.168.1.20:4000',
|
||||
baseURL: 'http://160.187.167.216',
|
||||
// baseURL: 'https://angry-gifts-shave.loca.lt',
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user