Bing Analytics and Insights added, background jobs added, database setup updated, environment setup updated, frontend updated, backend updated.

Onboarding Manager and Router Manager refactored, analytics and background jobs added, database setup updated, environment setup updated, frontend updated, backend updated.
Critical onboarding database migration implemented.
This commit is contained in:
ajaysi
2025-10-18 10:28:15 +05:30
parent 40fb6ac95b
commit 1f087aad4c
69 changed files with 11995 additions and 189 deletions

View File

@@ -0,0 +1,330 @@
import React, { useEffect, useState, useCallback } from 'react';
import {
Box,
Typography,
Alert,
Container,
CircularProgress,
Stack,
Card,
CardContent,
Chip,
Fade,
Divider,
} from '@mui/material';
import {
CheckCircleOutline as CheckCircleIcon,
ErrorOutline as ErrorIcon,
InfoOutlined as InfoIcon,
WarningAmberOutlined as WarningIcon,
Key as KeyIcon,
Star as StarIcon,
} from '@mui/icons-material';
import OnboardingButton from './common/OnboardingButton';
import { apiClient } from '../../api/client';
import { useSubscription } from '../../contexts/SubscriptionContext';
interface ApiKeyValidationStepProps {
onContinue: (stepData?: any) => void;
updateHeaderContent: (content: { title: string; description: string }) => void;
onValidationChange?: (isValid: boolean) => void;
}
interface ApiKeyStatus {
valid: boolean;
status: 'configured' | 'missing' | 'invalid' | 'checking';
error?: string;
}
interface ValidationResponse {
api_keys: Record<string, string>;
validation_results: Record<string, ApiKeyStatus>;
all_valid: boolean;
total_providers: number;
configured_providers: string[];
missing_keys: string[];
}
const ApiKeyValidationStep: React.FC<ApiKeyValidationStepProps> = ({
onContinue,
updateHeaderContent,
onValidationChange,
}) => {
const [loading, setLoading] = useState(true);
const [validationData, setValidationData] = useState<ValidationResponse | null>(null);
const [error, setError] = useState<string | null>(null);
const [isValid, setIsValid] = useState(false);
const { subscription } = useSubscription();
const validateApiKeys = useCallback(async () => {
setLoading(true);
setError(null);
try {
const response = await apiClient.get<ValidationResponse>('/api/onboarding/api-keys/validate');
setValidationData(response.data);
setIsValid(response.data.all_valid);
if (onValidationChange) {
onValidationChange(response.data.all_valid);
}
} catch (err: any) {
console.error('Error validating API keys:', err);
setError(err.response?.data?.detail || 'Failed to validate API keys. Please check backend logs.');
setIsValid(false);
if (onValidationChange) {
onValidationChange(false);
}
} finally {
setLoading(false);
}
}, [onValidationChange]);
useEffect(() => {
updateHeaderContent({
title: 'API Keys Configured',
description: 'Your AI service API keys have been successfully configured in the backend environment.',
});
validateApiKeys();
}, [updateHeaderContent, validateApiKeys]);
const handleContinue = () => {
if (isValid) {
onContinue();
}
};
const getStatusIcon = (status: 'configured' | 'missing' | 'invalid' | 'checking') => {
switch (status) {
case 'configured':
return <CheckCircleIcon color="success" />;
case 'missing':
return <WarningIcon color="warning" />;
case 'invalid':
return <ErrorIcon color="error" />;
case 'checking':
return <CircularProgress size={20} />;
default:
return <InfoIcon color="info" />;
}
};
const getStatusColor = (status: 'configured' | 'missing' | 'invalid' | 'checking') => {
switch (status) {
case 'configured':
return 'success';
case 'missing':
return 'warning';
case 'invalid':
return 'error';
case 'checking':
return 'info';
default:
return 'info';
}
};
const formatProviderName = (provider: string) => {
return provider
.replace(/_API_KEY/g, '')
.replace(/_/g, ' ')
.replace(/\b\w/g, l => l.toUpperCase());
};
return (
<Fade in={true} timeout={500}>
<Container maxWidth="md" sx={{ py: 4 }}>
<Typography variant="h5" component="h2" gutterBottom align="center" sx={{ mb: 3, fontWeight: 600 }}>
API Key Validation
</Typography>
{loading && (
<Box display="flex" flexDirection="column" alignItems="center" justifyContent="center" minHeight="200px">
<CircularProgress size={50} sx={{ mb: 2 }} />
<Typography variant="h6" color="text.secondary">
Validating API key configurations...
</Typography>
</Box>
)}
{!loading && error && (
<Alert severity="error" sx={{ mb: 3 }}>
{error}
</Alert>
)}
{!loading && validationData && (
<Box sx={{ mb: 4 }}>
{isValid ? (
<Alert severity="success" sx={{ mb: 3 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 2 }}>
<StarIcon sx={{ color: 'success.main' }} />
<Typography variant="h6" sx={{ fontWeight: 600 }}>
All API Keys Configured Successfully!
</Typography>
</Box>
<Typography variant="body2" sx={{ mb: 2 }}>
Your AI services are ready to use. You can now proceed to the next step.
</Typography>
{/* Subscription Plan Details */}
{subscription && (
<Box sx={{
mt: 2,
p: 2,
bgcolor: 'rgba(76, 175, 80, 0.1)',
borderRadius: 2,
border: '1px solid rgba(76, 175, 80, 0.2)'
}}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>
Your Subscription Plan
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 1 }}>
<Chip
label={subscription.plan.charAt(0).toUpperCase() + subscription.plan.slice(1)}
color="success"
size="small"
variant="filled"
/>
<Typography variant="body2" color="text.secondary">
{subscription.active ? 'Active' : 'Inactive'}
</Typography>
</Box>
<Typography variant="caption" color="text.secondary">
Monthly API calls: {subscription.limits.gemini_calls.toLocaleString()} Gemini, {subscription.limits.openai_calls.toLocaleString()} OpenAI
</Typography>
</Box>
)}
</Alert>
) : (
<Alert severity="warning" sx={{ mb: 3 }}>
Some required API keys are missing or invalid. Please configure them in your backend .env file.
</Alert>
)}
{/* Compact API Key Status Grid */}
<Box sx={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))',
gap: 2,
mb: 3
}}>
{Object.entries(validationData.validation_results).map(([provider, status]) => (
<Card
key={provider}
variant="outlined"
sx={{
border: `1px solid`,
borderColor: status.status === 'configured' ? '#e8f5e8' : '#fff3cd',
bgcolor: 'background.paper',
'&:hover': {
boxShadow: 2,
},
transition: 'all 0.2s ease-in-out'
}}
>
<CardContent sx={{ p: 2.5 }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
<KeyIcon
sx={{
color: status.status === 'configured' ? '#2e7d32' : '#ed6c02',
fontSize: 20
}}
/>
<Typography variant="h6" fontWeight={600} sx={{ color: 'text.primary' }}>
{formatProviderName(provider)}
</Typography>
</Box>
{getStatusIcon(status.status)}
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Typography variant="body2" sx={{ color: 'text.secondary', fontWeight: 500 }}>
Status:
</Typography>
<Chip
label={status.status}
color={getStatusColor(status.status) as any}
size="small"
variant="filled"
sx={{
fontWeight: 600,
fontSize: '0.75rem',
height: 24
}}
/>
</Box>
{status.status === 'configured' && (
<Typography variant="caption" sx={{
color: '#2e7d32',
fontWeight: 500,
display: 'block',
mt: 0.5
}}>
Ready to use
</Typography>
)}
{status.error && (
<Typography variant="caption" sx={{
color: 'error.main',
fontWeight: 500,
display: 'block',
mt: 0.5
}}>
{status.error}
</Typography>
)}
</CardContent>
</Card>
))}
</Box>
{/* Compact Summary Section */}
<Box sx={{
display: 'flex',
gap: 2,
flexWrap: 'wrap',
justifyContent: 'center',
mt: 3
}}>
{validationData.configured_providers.length > 0 && (
<Chip
icon={<CheckCircleIcon />}
label={`${validationData.configured_providers.length} Configured`}
color="success"
variant="outlined"
sx={{
fontWeight: 600,
'& .MuiChip-icon': {
color: 'success.main'
}
}}
/>
)}
{validationData.missing_keys.length > 0 && (
<Chip
icon={<WarningIcon />}
label={`${validationData.missing_keys.length} Missing`}
color="warning"
variant="outlined"
sx={{
fontWeight: 600,
'& .MuiChip-icon': {
color: 'warning.main'
}
}}
/>
)}
</Box>
</Box>
)}
{/* Continue button is handled by the main wizard, not here */}
</Container>
</Fade>
);
};
export default ApiKeyValidationStep;

View File

@@ -1,8 +1,10 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useCallback } from 'react';
import {
Box,
Fade,
Snackbar
Snackbar,
Typography,
Paper
} from '@mui/material';
import {
// Social Media Icons
@@ -16,7 +18,8 @@ import {
// Platform Icons
Web as WordPressIcon,
Web as WixIcon,
Google as GoogleIcon
Google as GoogleIcon,
Analytics as AnalyticsIcon
} from '@mui/icons-material';
// Import refactored components
@@ -24,8 +27,12 @@ import EmailSection from './common/EmailSection';
import PlatformSection from './common/PlatformSection';
import BenefitsSummary from './common/BenefitsSummary';
import ComingSoonSection from './common/ComingSoonSection';
import { useWordPressOAuth } from '../../hooks/useWordPressOAuth';
import { useBingOAuth } from '../../hooks/useBingOAuth';
import { useGSCConnection } from './common/useGSCConnection';
import { usePlatformConnections } from './common/usePlatformConnections';
import PlatformAnalytics from '../shared/PlatformAnalytics';
import { cachedAnalyticsAPI } from '../../api/cachedAnalytics';
interface IntegrationsStepProps {
onContinue: () => void;
@@ -50,7 +57,39 @@ const IntegrationsStep: React.FC<IntegrationsStepProps> = ({ onContinue, updateH
// Use custom hooks
const { gscSites, connectedPlatforms, setConnectedPlatforms, handleGSCConnect } = useGSCConnection();
// Invalidate analytics cache when platform connections change
const invalidateAnalyticsCache = useCallback(() => {
console.log('🔄 IntegrationsStep: Invalidating analytics cache due to connection change');
cachedAnalyticsAPI.invalidateAll();
}, []);
// Force refresh analytics data (bypass cache)
const forceRefreshAnalytics = useCallback(async () => {
console.log('🔄 IntegrationsStep: Force refreshing analytics data (bypassing cache)');
try {
// Clear all cache first
cachedAnalyticsAPI.clearCache();
// Force refresh platform status
await cachedAnalyticsAPI.forceRefreshPlatformStatus();
// Force refresh analytics data
await cachedAnalyticsAPI.forceRefreshAnalyticsData(['bing', 'gsc']);
console.log('✅ IntegrationsStep: Analytics data force refreshed successfully');
} catch (error) {
console.error('❌ IntegrationsStep: Error force refreshing analytics:', error);
}
}, []);
const { isLoading, showToast, setShowToast, toastMessage, handleConnect } = usePlatformConnections();
// WordPress OAuth hook
const { connected: wordpressConnected, sites: wordpressSites } = useWordPressOAuth();
// Bing OAuth hook
const { connected: bingConnected, sites: bingSites, connect: connectBing } = useBingOAuth();
console.log('Bing OAuth hook initialized:', { bingConnected, connectBing: typeof connectBing });
// Initialize integrations data
const [integrations] = useState<IntegrationPlatform[]>([
@@ -91,6 +130,18 @@ const IntegrationsStep: React.FC<IntegrationsStepProps> = ({ onContinue, updateH
oauthUrl: '/gsc/auth/url',
isEnabled: true
},
{
id: 'bing',
name: 'Bing Webmaster Tools',
description: 'Connect Bing Webmaster for comprehensive SEO insights and search performance data',
icon: <AnalyticsIcon />,
category: 'analytics',
status: 'available',
features: ['Bing search performance', 'SEO insights', 'Index status monitoring'],
benefits: ['Bing search analytics', 'SEO optimization insights', 'Search engine visibility tracking'],
oauthUrl: '/bing/auth/url',
isEnabled: true
},
// Social Media Platforms
{
id: 'facebook',
@@ -178,7 +229,65 @@ const IntegrationsStep: React.FC<IntegrationsStepProps> = ({ onContinue, updateH
});
}, [updateHeaderContent]);
// Handle OAuth callback parameters
// Handle WordPress connection status changes
useEffect(() => {
console.log('IntegrationsStep: WordPress status changed:', {
wordpressConnected,
wordpressSitesCount: wordpressSites.length,
connectedPlatforms,
currentPlatforms: connectedPlatforms
});
if (wordpressConnected && wordpressSites.length > 0) {
// WordPress is connected, add to connected platforms
if (!connectedPlatforms.includes('wordpress')) {
console.log('IntegrationsStep: Adding WordPress to connected platforms');
setConnectedPlatforms([...connectedPlatforms, 'wordpress']);
console.log('WordPress connection detected:', wordpressSites);
invalidateAnalyticsCache();
} else {
console.log('IntegrationsStep: WordPress already in connected platforms');
}
} else if (!wordpressConnected && connectedPlatforms.includes('wordpress')) {
// WordPress is disconnected, remove from connected platforms
console.log('IntegrationsStep: Removing WordPress from connected platforms');
setConnectedPlatforms(connectedPlatforms.filter(platform => platform !== 'wordpress'));
console.log('WordPress disconnection detected');
invalidateAnalyticsCache();
} else {
console.log('IntegrationsStep: No WordPress status change needed');
}
}, [wordpressConnected, wordpressSites, connectedPlatforms, setConnectedPlatforms, invalidateAnalyticsCache]);
// Handle Bing connection status changes
useEffect(() => {
console.log('IntegrationsStep: Bing status changed:', {
bingConnected,
bingSitesCount: bingSites.length,
connectedPlatforms,
currentPlatforms: connectedPlatforms
});
if (bingConnected && bingSites.length > 0) {
if (!connectedPlatforms.includes('bing')) {
console.log('IntegrationsStep: Adding Bing to connected platforms');
setConnectedPlatforms([...connectedPlatforms, 'bing']);
console.log('Bing connection detected:', bingSites);
invalidateAnalyticsCache();
} else {
console.log('IntegrationsStep: Bing already in connected platforms');
}
} else if (!bingConnected && connectedPlatforms.includes('bing')) {
console.log('IntegrationsStep: Removing Bing from connected platforms');
setConnectedPlatforms(connectedPlatforms.filter(platform => platform !== 'bing'));
console.log('Bing disconnection detected');
invalidateAnalyticsCache();
} else {
console.log('IntegrationsStep: No Bing status change needed');
}
}, [bingConnected, bingSites, connectedPlatforms, setConnectedPlatforms, invalidateAnalyticsCache]);
// Handle OAuth callback parameters (legacy support)
useEffect(() => {
const urlParams = new URLSearchParams(window.location.search);
const wordpressConnected = urlParams.get('wordpress_connected');
@@ -246,9 +355,31 @@ const IntegrationsStep: React.FC<IntegrationsStepProps> = ({ onContinue, updateH
}, []);
const handlePlatformConnect = async (platformId: string) => {
console.log('🚀 INTEGRATIONS_STEP: handlePlatformConnect called with platformId:', platformId);
console.log('🚀 INTEGRATIONS_STEP: platformId type:', typeof platformId);
console.log('🚀 INTEGRATIONS_STEP: platformId length:', platformId.length);
console.log('🚀 INTEGRATIONS_STEP: platformId === "bing":', platformId === 'bing');
console.log('🚀 INTEGRATIONS_STEP: platformId === "gsc":', platformId === 'gsc');
console.log('🚀 INTEGRATIONS_STEP: connectBing function type:', typeof connectBing);
console.log('🚀 INTEGRATIONS_STEP: connectBing function:', connectBing);
console.log('🚀 INTEGRATIONS_STEP: Stack trace:', new Error().stack);
if (platformId === 'gsc') {
console.log('🚀 INTEGRATIONS_STEP: Handling GSC connection');
await handleGSCConnect();
} else if (platformId === 'bing') {
console.log('🚀 INTEGRATIONS_STEP: Handling Bing connection - about to call connectBing');
// Use the Bing OAuth hook for connection
try {
console.log('🚀 INTEGRATIONS_STEP: Calling connectBing()...');
await connectBing();
console.log('🚀 INTEGRATIONS_STEP: Bing connection initiated successfully');
} catch (error) {
console.error('🚀 INTEGRATIONS_STEP: Bing connection failed:', error);
}
} else {
console.log('🚀 INTEGRATIONS_STEP: Handling other platform connection:', platformId);
console.log('🚀 INTEGRATIONS_STEP: This should NOT happen for Bing!');
await handleConnect(platformId);
}
};
@@ -298,6 +429,47 @@ const IntegrationsStep: React.FC<IntegrationsStepProps> = ({ onContinue, updateH
</div>
</Fade>
{/* Analytics Data Display */}
{connectedPlatforms.length > 0 && (
<Fade in timeout={1200}>
<div>
<Paper
elevation={2}
sx={{
mt: 3,
p: 3,
borderRadius: 2,
background: 'linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%)'
}}
>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
<AnalyticsIcon sx={{ mr: 1, color: 'primary.main' }} />
<Typography variant="h6" sx={{ fontWeight: 600, color: 'text.primary' }}>
Platform Analytics
</Typography>
</Box>
<Typography variant="body2" sx={{ color: 'text.secondary', mb: 3 }}>
Here's what data is available from your connected platforms:
</Typography>
<PlatformAnalytics
platforms={connectedPlatforms}
showSummary={true}
refreshInterval={0}
onDataLoaded={(data: any) => {
console.log('Analytics data loaded:', data);
}}
onRefreshReady={(refreshFn) => {
console.log('🔄 PlatformAnalytics refresh function ready');
// Store the refresh function for potential use
(window as any).refreshAnalytics = refreshFn;
}}
/>
</Paper>
</div>
</Fade>
)}
{/* Social Media Platforms */}
<Fade in timeout={1200}>
<div>

View File

@@ -329,12 +329,23 @@ const PersonaStep: React.FC<PersonaStepProps> = ({
// Validation effect - notify wizard when persona data is ready
useEffect(() => {
const isValid = !!(corePersona && platformPersonas && Object.keys(platformPersonas).length > 0 && qualityMetrics);
// Only validate as complete if:
// 1. Not currently generating
// 2. Generation completed successfully (has success data)
// 3. Has all required persona data
const hasValidData = !!(corePersona && platformPersonas && Object.keys(platformPersonas).length > 0 && qualityMetrics);
const isComplete = !isGenerating && hasValidData && generationStep === 'preview';
const isValid = isComplete;
console.log('PersonaStep: Validation check:', {
corePersona: !!corePersona,
platformPersonas: !!platformPersonas,
platformPersonasCount: platformPersonas ? Object.keys(platformPersonas).length : 0,
qualityMetrics: !!qualityMetrics,
isGenerating,
generationStep,
hasValidData,
isComplete,
isValid
});
@@ -342,23 +353,32 @@ const PersonaStep: React.FC<PersonaStepProps> = ({
console.log('PersonaStep: Calling onValidationChange with:', isValid);
onValidationChange(isValid);
}
}, [corePersona, platformPersonas, qualityMetrics, onValidationChange]);
}, [corePersona, platformPersonas, qualityMetrics, isGenerating, generationStep, onValidationChange]);
// Auto-call onContinue when persona data is ready
// Auto-call onContinue when persona data is ready and generation is complete
useEffect(() => {
console.log('PersonaStep: Checking persona data readiness:', {
corePersona: !!corePersona,
platformPersonas: !!platformPersonas,
qualityMetrics: !!qualityMetrics,
success,
isGenerating
isGenerating,
generationStep
});
if (corePersona && platformPersonas && qualityMetrics && success) {
console.log('PersonaStep: Persona data is ready, auto-calling onContinue');
// Only auto-continue if:
// 1. Generation is complete (not generating and at preview step)
// 2. Has valid persona data and success flag
const hasValidData = corePersona && platformPersonas && qualityMetrics && success;
const isGenerationComplete = !isGenerating && generationStep === 'preview';
if (hasValidData && isGenerationComplete) {
console.log('PersonaStep: Persona data is ready and generation complete, auto-calling onContinue');
handleContinue();
} else {
console.log('PersonaStep: Not ready to continue yet - hasValidData:', hasValidData, 'isGenerationComplete:', isGenerationComplete);
}
}, [corePersona, platformPersonas, qualityMetrics, success, handleContinue]);
}, [corePersona, platformPersonas, qualityMetrics, success, isGenerating, generationStep, handleContinue]);
// (auto-generation handled in initial effect via server/local cache fallback)

View File

@@ -9,7 +9,7 @@ import {
} from '@mui/material';
import { getCurrentStep, setCurrentStep } from '../../api/onboarding';
import { apiClient } from '../../api/client';
import ApiKeyStep from './ApiKeyStep';
import ApiKeyValidationStep from './ApiKeyValidationStep';
import WebsiteStep from './WebsiteStep';
import CompetitorAnalysisStep from './CompetitorAnalysisStep';
import PersonaStep from './PersonaStep';
@@ -181,7 +181,7 @@ const Wizard: React.FC<WizardProps> = ({ onComplete }) => {
});
}, []);
// Memoized callback specifically for ApiKeyStep to prevent infinite loops
// Memoized callback specifically for ApiKeyValidationStep to prevent infinite loops
const handleApiKeyValidationChange = useCallback((isValid: boolean) => {
handleStepValidationChange(0, isValid);
}, [handleStepValidationChange]);
@@ -219,9 +219,22 @@ const Wizard: React.FC<WizardProps> = ({ onComplete }) => {
if (cachedInit) {
console.log('Wizard: Using cached init data from batch endpoint');
const data = JSON.parse(cachedInit);
// Extract data from batch response
const { onboarding, session } = data;
// Check if user should start from step 1 due to new API key flow
// If backend says current_step is 1 but cache shows higher step, reset
if (onboarding.current_step === 1 && onboarding.completion_percentage === 0) {
console.log('Wizard: Detected new API key flow - user should start from step 1');
// Clear cache and start fresh
sessionStorage.removeItem('onboarding_init');
localStorage.removeItem('onboarding_active_step');
localStorage.removeItem('onboarding_data');
setActiveStep(0); // Start from step 1 (index 0)
setLoading(false);
return;
}
// Load step data, especially research data from step 3 and persona data from step 4
if (onboarding.steps && Array.isArray(onboarding.steps)) {
@@ -586,7 +599,7 @@ const Wizard: React.FC<WizardProps> = ({ onComplete }) => {
const renderStepContent = (step: number) => {
const stepComponents = [
<ApiKeyStep key="api-keys" onContinue={handleNext} updateHeaderContent={updateHeaderContent} onValidationChange={handleApiKeyValidationChange} />,
<ApiKeyValidationStep key="api-keys" onContinue={handleNext} updateHeaderContent={updateHeaderContent} onValidationChange={handleApiKeyValidationChange} />,
<WebsiteStep key="website" onContinue={handleNext} updateHeaderContent={updateHeaderContent} onValidationChange={(isValid) => handleStepValidationChange(1, isValid)} />,
<CompetitorAnalysisStep
key="research"

View File

@@ -15,6 +15,7 @@ import {
Close
} from '@mui/icons-material';
import UserBadge from '../../shared/UserBadge';
import UsageDashboard from '../../shared/UsageDashboard';
interface WizardHeaderProps {
activeStep: number;
@@ -95,8 +96,10 @@ export const WizardHeader: React.FC<WizardHeaderProps> = ({
{/* Top Row - Title and Actions */}
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3, position: 'relative', zIndex: 1 }}>
<Box sx={{ flex: 1 }}>
<Box sx={{ flex: 1, display: 'flex', alignItems: 'center', gap: 2 }}>
<UserBadge colorMode="dark" />
{/* Usage Dashboard - Show API usage statistics during onboarding */}
<UsageDashboard compact={true} />
</Box>
<Box sx={{ flex: 2, textAlign: 'center' }}>
<Typography variant="h4" sx={{ fontWeight: 700, letterSpacing: '-0.025em' }}>

View File

@@ -87,7 +87,8 @@ export const usePlatformConnections = () => {
}
// For other platforms, you can add their connection logic here
console.log(`Connecting to ${platformId}...`);
console.log(`🔧 USE_PLATFORM_CONNECTIONS: Connecting to ${platformId}...`);
console.log(`🔧 USE_PLATFORM_CONNECTIONS: Stack trace:`, new Error().stack);
} catch (error) {
console.error('Connection error:', error);