ALwrity + Wix + Wordpress + GSC integration + Production API calls fixes
This commit is contained in:
@@ -10,7 +10,8 @@ export const setAuthTokenGetter = (getter: () => Promise<string | null>) => {
|
||||
// Get API URL from environment variables
|
||||
const getApiUrl = () => {
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
return process.env.REACT_APP_API_URL || 'https://your-backend-url.railway.app';
|
||||
// In production, use the environment variable or fallback
|
||||
return process.env.REACT_APP_API_URL || process.env.REACT_APP_BACKEND_URL;
|
||||
}
|
||||
return ''; // Use proxy in development
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useCopilotAction } from '@copilotkit/react-core';
|
||||
import DiffPreview from './DiffPreview';
|
||||
import { apiClient } from '../../api/client';
|
||||
|
||||
interface HallucinationCheckerProps {
|
||||
buildFullMarkdown: () => string;
|
||||
@@ -27,12 +28,8 @@ export const HallucinationChecker: React.FC<HallucinationCheckerProps> = ({
|
||||
parameters: [],
|
||||
handler: async () => {
|
||||
const content = buildFullMarkdown();
|
||||
const res = await fetch('/api/blog/quality/hallucination-check', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ text: content })
|
||||
});
|
||||
const data = await res.json();
|
||||
const res = await apiClient.post('/api/blog/quality/hallucination-check', { text: content });
|
||||
const data = res.data;
|
||||
setHallucinationResult(data);
|
||||
return { success: true, total_claims: data?.total_claims };
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import { useCopilotAction } from '@copilotkit/react-core';
|
||||
import { blogWriterApi, BlogSEOMetadataResponse } from '../../../services/blogWriterApi';
|
||||
import { apiClient } from '../../../api/client';
|
||||
|
||||
interface SEOProcessorProps {
|
||||
buildFullMarkdown: () => string;
|
||||
@@ -50,22 +51,12 @@ export const SEOProcessor: React.FC<SEOProcessorProps> = ({
|
||||
if (!current) return { success: false, message: 'No content yet for this section' };
|
||||
|
||||
// Use comprehensive SEO analysis endpoint
|
||||
const response = await fetch('/api/blog-writer/seo/analyze', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
content: current,
|
||||
keywords: []
|
||||
})
|
||||
const response = await apiClient.post('/api/blog-writer/seo/analyze', {
|
||||
content: current,
|
||||
keywords: []
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to analyze blog content');
|
||||
}
|
||||
|
||||
const res = await response.json();
|
||||
const res = response.data;
|
||||
onSEOAnalysis(res);
|
||||
return { success: true, message: 'Analysis ready' };
|
||||
},
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
IconButton,
|
||||
Tooltip
|
||||
} from '@mui/material';
|
||||
import { apiClient } from '../../api/client';
|
||||
import {
|
||||
CheckCircle,
|
||||
Cancel,
|
||||
@@ -187,23 +188,13 @@ export const SEOAnalysisModal: React.FC<SEOAnalysisModalProps> = ({
|
||||
}
|
||||
|
||||
// Make API call to analyze blog content
|
||||
const response = await fetch('/api/blog-writer/seo/analyze', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
blog_content: blogContent,
|
||||
blog_title: blogTitle,
|
||||
research_data: researchData
|
||||
})
|
||||
const response = await apiClient.post('/api/blog-writer/seo/analyze', {
|
||||
blog_content: blogContent,
|
||||
blog_title: blogTitle,
|
||||
research_data: researchData
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to analyze blog content');
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
const result = response.data;
|
||||
console.log('🔍 Backend SEO Analysis Response:', result);
|
||||
|
||||
// Convert API response to frontend format - fail fast if data is missing
|
||||
|
||||
@@ -44,6 +44,7 @@ import {
|
||||
Tag as TagIcon,
|
||||
Refresh as RefreshIcon
|
||||
} from '@mui/icons-material';
|
||||
import { apiClient } from '../../api/client';
|
||||
|
||||
// Import metadata display components
|
||||
import { CoreMetadataTab } from './SEO/MetadataDisplay/CoreMetadataTab';
|
||||
@@ -113,23 +114,13 @@ export const SEOMetadataModal: React.FC<SEOMetadataModalProps> = ({
|
||||
console.log('🚀 Starting SEO metadata generation...');
|
||||
|
||||
// Make API call to generate metadata
|
||||
const response = await fetch('/api/blog/seo/metadata', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
content: blogContent,
|
||||
title: blogTitle,
|
||||
research_data: researchData
|
||||
})
|
||||
const response = await apiClient.post('/api/blog/seo/metadata', {
|
||||
content: blogContent,
|
||||
title: blogTitle,
|
||||
research_data: researchData
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
const result = response.data;
|
||||
console.log('✅ SEO metadata generation response:', result);
|
||||
|
||||
if (!result.success) {
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
CardHeader,
|
||||
Avatar
|
||||
} from '@mui/material';
|
||||
import { apiClient } from '../../../api/client';
|
||||
import {
|
||||
CheckCircle as HealthyIcon,
|
||||
Warning as WarningIcon,
|
||||
@@ -104,12 +105,8 @@ const SystemStatusIndicator: React.FC<SystemStatusIndicatorProps> = ({ className
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/content-planning/monitoring/lightweight-stats');
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch system status');
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
const response = await apiClient.get('/api/content-planning/monitoring/lightweight-stats');
|
||||
const result = response.data;
|
||||
if (result.status === 'success') {
|
||||
setStatusData(result.data);
|
||||
} else {
|
||||
@@ -132,25 +129,23 @@ const SystemStatusIndicator: React.FC<SystemStatusIndicatorProps> = ({ className
|
||||
|
||||
const fetchDetailedStats = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/content-planning/monitoring/api-stats');
|
||||
if (response.ok) {
|
||||
const result = await response.json();
|
||||
if (result.status === 'success') {
|
||||
setDetailedStats(result.data);
|
||||
if (result.data?.cache_performance) {
|
||||
setCachePerf(result.data.cache_performance);
|
||||
}
|
||||
|
||||
// Generate chart data
|
||||
const chartData = result.data.top_endpoints.slice(0, 5).map((endpoint: any, index: number) => ({
|
||||
name: endpoint.endpoint.split(' ')[1].split('/').pop() || 'API',
|
||||
requests: endpoint.count,
|
||||
avgTime: endpoint.avg_time,
|
||||
errors: endpoint.errors,
|
||||
hitRate: endpoint.cache_hit_rate
|
||||
}));
|
||||
setChartData(chartData);
|
||||
const response = await apiClient.get('/api/content-planning/monitoring/api-stats');
|
||||
const result = response.data;
|
||||
if (result.status === 'success') {
|
||||
setDetailedStats(result.data);
|
||||
if (result.data?.cache_performance) {
|
||||
setCachePerf(result.data.cache_performance);
|
||||
}
|
||||
|
||||
// Generate chart data
|
||||
const chartData = result.data.top_endpoints.slice(0, 5).map((endpoint: any, index: number) => ({
|
||||
name: endpoint.endpoint.split(' ')[1].split('/').pop() || 'API',
|
||||
requests: endpoint.count,
|
||||
avgTime: endpoint.avg_time,
|
||||
errors: endpoint.errors,
|
||||
hitRate: endpoint.cache_hit_rate
|
||||
}));
|
||||
setChartData(chartData);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error fetching detailed stats:', err);
|
||||
|
||||
@@ -16,6 +16,7 @@ import { useLocation } from 'react-router-dom';
|
||||
import ContentStrategyBuilder from '../components/ContentStrategyBuilder';
|
||||
import CalendarGenerationWizard from '../components/CalendarGenerationWizard';
|
||||
import { CalendarGenerationModal } from '../components/CalendarGenerationModal';
|
||||
import { apiClient } from '../../../api/client';
|
||||
|
||||
// Import hooks and services
|
||||
import { useStrategyCalendarContext } from '../../../contexts/StrategyCalendarContext';
|
||||
@@ -130,39 +131,26 @@ const CreateTab: React.FC = () => {
|
||||
|
||||
while (retryCount < maxRetries) {
|
||||
try {
|
||||
startResponse = await fetch('/api/content-planning/calendar-generation/start', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(requestData),
|
||||
});
|
||||
|
||||
if (startResponse.ok) {
|
||||
break; // Success, exit retry loop
|
||||
} else {
|
||||
console.warn(`⚠️ Attempt ${retryCount + 1} failed with status: ${startResponse.status}`);
|
||||
retryCount++;
|
||||
if (retryCount < maxRetries) {
|
||||
// Wait before retry (exponential backoff)
|
||||
await new Promise(resolve => setTimeout(resolve, 1000 * retryCount));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
const response = await apiClient.post('/api/content-planning/calendar-generation/start', requestData);
|
||||
startResponse = { ok: true, data: response.data };
|
||||
break; // Success, exit retry loop
|
||||
} catch (error: any) {
|
||||
console.warn(`⚠️ Attempt ${retryCount + 1} failed with error:`, error);
|
||||
retryCount++;
|
||||
if (retryCount < maxRetries) {
|
||||
// Wait before retry (exponential backoff)
|
||||
await new Promise(resolve => setTimeout(resolve, 1000 * retryCount));
|
||||
} else {
|
||||
startResponse = { ok: false, data: null };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!startResponse || !startResponse.ok) {
|
||||
throw new Error(`Failed to start calendar generation after ${maxRetries} attempts: ${startResponse?.statusText || 'Network error'}`);
|
||||
throw new Error(`Failed to start calendar generation after ${maxRetries} attempts`);
|
||||
}
|
||||
|
||||
const startData = await startResponse.json();
|
||||
const startData = startResponse.data;
|
||||
const sessionId = startData.session_id;
|
||||
|
||||
console.log('🎯 Backend response received, session ID:', sessionId);
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
readPrefs
|
||||
} from './utils/linkedInWriterUtils';
|
||||
import { PostHITL, ArticleHITL, CarouselHITL, VideoScriptHITL, CommentResponseHITL } from './components';
|
||||
import { apiClient } from '../../api/client';
|
||||
|
||||
const useCopilotActionTyped = useCopilotAction as any;
|
||||
|
||||
@@ -25,22 +26,14 @@ const RegisterLinkedInActions: React.FC = () => {
|
||||
],
|
||||
handler: async (args: any) => {
|
||||
try {
|
||||
const response = await fetch('/api/linkedin/generate-image-prompts', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
content_type: args.content_type,
|
||||
topic: args.topic,
|
||||
industry: args.industry,
|
||||
content: args.content
|
||||
})
|
||||
const response = await apiClient.post('/api/linkedin/generate-image-prompts', {
|
||||
content_type: args.content_type,
|
||||
topic: args.topic,
|
||||
industry: args.industry,
|
||||
content: args.content
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to generate image prompts: ${response.status}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
const result = response.data;
|
||||
return {
|
||||
success: true,
|
||||
prompts: result,
|
||||
@@ -66,21 +59,13 @@ const RegisterLinkedInActions: React.FC = () => {
|
||||
],
|
||||
handler: async (args: any) => {
|
||||
try {
|
||||
const response = await fetch('/api/linkedin/generate-image', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
prompt: args.prompt,
|
||||
content_context: args.content_context,
|
||||
aspect_ratio: args.aspect_ratio || '1:1'
|
||||
})
|
||||
const response = await apiClient.post('/api/linkedin/generate-image', {
|
||||
prompt: args.prompt,
|
||||
content_context: args.content_context,
|
||||
aspect_ratio: args.aspect_ratio || '1:1'
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to generate image: ${response.status}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
const result = response.data;
|
||||
if (result.success) {
|
||||
return {
|
||||
success: true,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { usePlatformPersonaContext } from '../../shared/PersonaContext/PlatformPersonaProvider';
|
||||
import { apiClient } from '../../../api/client';
|
||||
|
||||
// Define the cache data type
|
||||
interface BrainstormCacheData {
|
||||
@@ -201,33 +202,22 @@ const BrainstormFlow: React.FC<BrainstormFlowProps> = ({
|
||||
// First: run grounded search for the seed prompt
|
||||
let results: any[] = [];
|
||||
try {
|
||||
const sr = await fetch('/api/brainstorm/search', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ prompt: finalSeed })
|
||||
});
|
||||
if (sr.ok) {
|
||||
const data = await sr.json();
|
||||
results = data?.results || [];
|
||||
}
|
||||
const sr = await apiClient.post('/api/brainstorm/search', { prompt: finalSeed });
|
||||
results = sr.data?.results || [];
|
||||
} catch {}
|
||||
setSearchResults(results);
|
||||
|
||||
// Then: request persona-aware brainstorm ideas using the search results
|
||||
try {
|
||||
const ir = await fetch('/api/brainstorm/ideas', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
seed: finalSeed,
|
||||
persona: corePersona || null,
|
||||
platformPersona: platformPersona || null,
|
||||
results,
|
||||
count: 5
|
||||
})
|
||||
const ir = await apiClient.post('/api/brainstorm/ideas', {
|
||||
seed: finalSeed,
|
||||
persona: corePersona || null,
|
||||
platformPersona: platformPersona || null,
|
||||
results,
|
||||
count: 5
|
||||
});
|
||||
if (ir.ok) {
|
||||
const data = await ir.json();
|
||||
if (ir.data) {
|
||||
const data = ir.data;
|
||||
const list = Array.isArray(data?.ideas) ? data.ideas : [];
|
||||
setIdeas(list);
|
||||
setAiSearchPrompts(list.map((x: any) => x.prompt));
|
||||
@@ -477,19 +467,9 @@ const BrainstormFlow: React.FC<BrainstormFlowProps> = ({
|
||||
onClick={async () => {
|
||||
// Use existing Google grounding flow via backend LinkedInService
|
||||
try {
|
||||
const resp = await fetch('/api/brainstorm/search', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ prompt: selectedPrompt })
|
||||
});
|
||||
if (resp.ok) {
|
||||
const data = await resp.json();
|
||||
setSearchResults(data?.results || []);
|
||||
setBrainstormStage('results');
|
||||
} else {
|
||||
setSearchResults([]);
|
||||
setBrainstormStage('results');
|
||||
}
|
||||
const resp = await apiClient.post('/api/brainstorm/search', { prompt: selectedPrompt });
|
||||
setSearchResults(resp.data?.results || []);
|
||||
setBrainstormStage('results');
|
||||
} catch {
|
||||
setSearchResults([]);
|
||||
setBrainstormStage('results');
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
Fade,
|
||||
Container,
|
||||
Grid,
|
||||
Snackbar,
|
||||
} from '@mui/material';
|
||||
import { Lock } from '@mui/icons-material';
|
||||
import OnboardingButton from './common/OnboardingButton';
|
||||
@@ -23,7 +24,6 @@ interface ApiKeyStepProps {
|
||||
}
|
||||
|
||||
const ApiKeyStep: React.FC<ApiKeyStepProps> = ({ onContinue, updateHeaderContent }) => {
|
||||
const [currentProvider, setCurrentProvider] = useState(0);
|
||||
const [focusedProvider, setFocusedProvider] = useState<any>(null);
|
||||
|
||||
const {
|
||||
@@ -36,6 +36,10 @@ const ApiKeyStep: React.FC<ApiKeyStepProps> = ({ onContinue, updateHeaderContent
|
||||
selectedProvider,
|
||||
providers,
|
||||
isValid,
|
||||
currentProviderIndex,
|
||||
setCurrentProviderIndex,
|
||||
showCompletionToast,
|
||||
setShowCompletionToast,
|
||||
setShowHelp,
|
||||
handleContinue,
|
||||
handleBenefitsClick,
|
||||
@@ -55,13 +59,14 @@ const ApiKeyStep: React.FC<ApiKeyStepProps> = ({ onContinue, updateHeaderContent
|
||||
|
||||
// Set initial focused provider
|
||||
if (providers.length > 0) {
|
||||
setFocusedProvider(providers[currentProvider] ?? providers[0]);
|
||||
setFocusedProvider(providers[currentProviderIndex] ?? providers[0]);
|
||||
}
|
||||
}, [updateHeaderContent, providers, currentProvider]);
|
||||
}, [updateHeaderContent, providers, currentProviderIndex]);
|
||||
|
||||
return (
|
||||
<Fade in={true} timeout={500}>
|
||||
<Container maxWidth="lg" sx={{ py: 2 }}>
|
||||
<>
|
||||
<Fade in={true} timeout={500}>
|
||||
<Container maxWidth="lg" sx={{ py: 2 }}>
|
||||
<form onSubmit={(e) => { e.preventDefault(); handleContinue(); }}>
|
||||
{/* Main Content Layout */}
|
||||
<Grid container spacing={4} sx={{ mb: 4 }}>
|
||||
@@ -69,8 +74,8 @@ const ApiKeyStep: React.FC<ApiKeyStepProps> = ({ onContinue, updateHeaderContent
|
||||
<Grid item xs={12} lg={8}>
|
||||
<ApiKeyCarousel
|
||||
providers={providers}
|
||||
currentProvider={currentProvider}
|
||||
setCurrentProvider={setCurrentProvider}
|
||||
currentProvider={currentProviderIndex}
|
||||
setCurrentProvider={setCurrentProviderIndex}
|
||||
onProviderFocus={handleProviderFocus}
|
||||
/>
|
||||
</Grid>
|
||||
@@ -80,7 +85,7 @@ const ApiKeyStep: React.FC<ApiKeyStepProps> = ({ onContinue, updateHeaderContent
|
||||
<ApiKeySidebar
|
||||
currentProvider={focusedProvider}
|
||||
allProviders={providers}
|
||||
currentStep={currentProvider + 1}
|
||||
currentStep={currentProviderIndex + 1}
|
||||
totalSteps={providers.length}
|
||||
/>
|
||||
</Grid>
|
||||
@@ -169,7 +174,7 @@ const ApiKeyStep: React.FC<ApiKeyStepProps> = ({ onContinue, updateHeaderContent
|
||||
}
|
||||
}}
|
||||
>
|
||||
{isValid ? 'Continue to Website Analysis' : 'Complete All Required API Keys'}
|
||||
{isValid ? 'Continue' : 'Complete All Required API Keys'}
|
||||
</OnboardingButton>
|
||||
</Box>
|
||||
|
||||
@@ -197,8 +202,42 @@ const ApiKeyStep: React.FC<ApiKeyStepProps> = ({ onContinue, updateHeaderContent
|
||||
</Typography>
|
||||
</Box>
|
||||
</form>
|
||||
</Container>
|
||||
</Fade>
|
||||
</Container>
|
||||
</Fade>
|
||||
|
||||
{/* Completion Toast */}
|
||||
<Snackbar
|
||||
open={showCompletionToast}
|
||||
autoHideDuration={5000}
|
||||
onClose={() => setShowCompletionToast(false)}
|
||||
anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
|
||||
sx={{
|
||||
'& .MuiSnackbarContent-root': {
|
||||
background: 'linear-gradient(135deg, #10B981 0%, #059669 100%)',
|
||||
color: 'white',
|
||||
fontWeight: 600,
|
||||
fontSize: '1rem',
|
||||
borderRadius: '12px',
|
||||
boxShadow: '0 8px 32px rgba(16, 185, 129, 0.3)',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Alert
|
||||
onClose={() => setShowCompletionToast(false)}
|
||||
severity="success"
|
||||
sx={{
|
||||
width: '100%',
|
||||
background: 'transparent',
|
||||
color: 'white',
|
||||
'& .MuiAlert-icon': {
|
||||
color: 'white',
|
||||
},
|
||||
}}
|
||||
>
|
||||
🎉 All API keys configured! Click Continue to proceed to Website Analysis.
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useAuth } from '@clerk/clerk-react';
|
||||
import { getApiKeysForOnboarding, getStep1ApiKeysFromProgress, saveApiKey } from '../../../../api/onboarding';
|
||||
import { getKeyStatus, formatErrorMessage } from '../../common/onboardingUtils';
|
||||
import { Provider } from './ProviderCard';
|
||||
import { apiClient } from '../../../../api/client';
|
||||
|
||||
export const useApiKeyStep = (onContinue: (stepData?: any) => void) => {
|
||||
const { getToken } = useAuth();
|
||||
@@ -20,6 +21,8 @@ export const useApiKeyStep = (onContinue: (stepData?: any) => void) => {
|
||||
const [benefitsModalOpen, setBenefitsModalOpen] = useState(false);
|
||||
const [selectedProvider, setSelectedProvider] = useState<Provider | null>(null);
|
||||
const [keysLoaded, setKeysLoaded] = useState(false);
|
||||
const [currentProviderIndex, setCurrentProviderIndex] = useState(0);
|
||||
const [showCompletionToast, setShowCompletionToast] = useState(false);
|
||||
|
||||
const loadExistingKeys = useCallback(async () => {
|
||||
try {
|
||||
@@ -131,34 +134,22 @@ export const useApiKeyStep = (onContinue: (stepData?: any) => void) => {
|
||||
|
||||
// Complete step 1 with the API keys data
|
||||
console.log('ApiKeyStep: Attempting to complete step 1 with data:', stepData);
|
||||
let response;
|
||||
try {
|
||||
response = await fetch('/api/onboarding/step/1/complete', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${await getToken()}`
|
||||
},
|
||||
body: JSON.stringify({ data: stepData })
|
||||
});
|
||||
console.log('ApiKeyStep: Step completion response status:', response.status);
|
||||
const response = await apiClient.post('/api/onboarding/step/1/complete', { data: stepData });
|
||||
console.log('ApiKeyStep: Step completion response:', response.data);
|
||||
} catch (fetchError: any) {
|
||||
console.error('Network error completing step:', fetchError);
|
||||
setError('Network error. Please check your connection and try again.');
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
let errorMessage = 'Failed to complete step';
|
||||
try {
|
||||
const errorData = await response.json();
|
||||
console.log('ApiKeyStep: Error response data:', errorData);
|
||||
errorMessage = errorData.detail || errorMessage;
|
||||
} catch (parseError) {
|
||||
console.error('Error parsing error response:', parseError);
|
||||
errorMessage = `Server error (${response.status}). Please try again.`;
|
||||
console.error('Error completing step:', fetchError);
|
||||
let errorMessage = 'Failed to complete step. Please try again.';
|
||||
|
||||
if (fetchError.response) {
|
||||
// Server responded with an error
|
||||
console.log('ApiKeyStep: Error response data:', fetchError.response.data);
|
||||
errorMessage = fetchError.response.data?.detail || errorMessage;
|
||||
} else if (fetchError.request) {
|
||||
// Request made but no response
|
||||
errorMessage = 'Network error. Please check your connection and try again.';
|
||||
}
|
||||
|
||||
console.log('ApiKeyStep: Setting error message:', errorMessage);
|
||||
setError(errorMessage);
|
||||
setLoading(false);
|
||||
@@ -228,6 +219,31 @@ export const useApiKeyStep = (onContinue: (stepData?: any) => void) => {
|
||||
// All three keys are required
|
||||
const isValid = geminiKey.trim() && exaKey.trim() && copilotkitKey.trim();
|
||||
|
||||
// Auto-scroll to next provider when current one is valid
|
||||
useEffect(() => {
|
||||
if (currentProviderIndex < 2) {
|
||||
const currentKey = currentProviderIndex === 0 ? geminiKey :
|
||||
currentProviderIndex === 1 ? exaKey : copilotkitKey;
|
||||
|
||||
if (currentKey.trim() && getKeyStatus(currentKey, currentProviderIndex === 0 ? 'gemini' :
|
||||
currentProviderIndex === 1 ? 'exa' : 'copilotkit') === 'valid') {
|
||||
// Auto-scroll to next provider after a short delay
|
||||
setTimeout(() => {
|
||||
setCurrentProviderIndex(prev => prev + 1);
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
}, [geminiKey, exaKey, copilotkitKey, currentProviderIndex]);
|
||||
|
||||
// Show completion toast when all keys are valid
|
||||
useEffect(() => {
|
||||
if (isValid && keysLoaded) {
|
||||
setShowCompletionToast(true);
|
||||
// Auto-hide toast after 5 seconds
|
||||
setTimeout(() => setShowCompletionToast(false), 5000);
|
||||
}
|
||||
}, [isValid, keysLoaded]);
|
||||
|
||||
const handleBenefitsClick = (provider: Provider) => {
|
||||
setSelectedProvider(provider);
|
||||
setBenefitsModalOpen(true);
|
||||
@@ -260,6 +276,10 @@ export const useApiKeyStep = (onContinue: (stepData?: any) => void) => {
|
||||
keysLoaded,
|
||||
providers,
|
||||
isValid,
|
||||
currentProviderIndex,
|
||||
setCurrentProviderIndex,
|
||||
showCompletionToast,
|
||||
setShowCompletionToast,
|
||||
|
||||
// Actions
|
||||
setShowHelp,
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
IconButton,
|
||||
Tooltip
|
||||
} from '@mui/material';
|
||||
import { apiClient } from '../../api/client';
|
||||
import {
|
||||
ExpandMore as ExpandMoreIcon,
|
||||
ContentCopy as CopyIcon,
|
||||
@@ -93,21 +94,13 @@ const StyleDetectionStep: React.FC<StyleDetectionStepProps> = ({ onContinue }) =
|
||||
include_guidelines: true
|
||||
};
|
||||
|
||||
const response = await fetch('/api/onboarding/style-detection/complete', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(requestData),
|
||||
});
|
||||
const response = await apiClient.post('/api/onboarding/style-detection/complete', requestData);
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
setAnalysis(result.style_analysis);
|
||||
if (response.data.success) {
|
||||
setAnalysis(response.data.style_analysis);
|
||||
setSuccess('Style analysis completed successfully!');
|
||||
} else {
|
||||
setError(result.error || 'Analysis failed');
|
||||
setError(response.data.error || 'Analysis failed');
|
||||
}
|
||||
} catch (err) {
|
||||
setError('Failed to analyze content. Please try again.');
|
||||
|
||||
@@ -63,8 +63,13 @@ export const usePlatformConnections = () => {
|
||||
const redirectUri = `${redirectOrigin}/wix/callback`;
|
||||
const oauthData = await wixClient.auth.generateOAuthData(redirectUri);
|
||||
|
||||
// Use sessionStorage to ensure data is scoped to this tab/session (like WixTestPage)
|
||||
sessionStorage.setItem('wix_oauth_data', JSON.stringify(oauthData));
|
||||
// Persist OAuth data robustly so callback can always recover it
|
||||
// 1) SessionStorage for same-origin same-tab flows
|
||||
try { sessionStorage.setItem('wix_oauth_data', JSON.stringify(oauthData)); } catch {}
|
||||
// 2) Key by state so callback can look up by state value
|
||||
try { sessionStorage.setItem(`wix_oauth_data_${oauthData.state}`, JSON.stringify(oauthData)); } catch {}
|
||||
// 3) window.name persists across top-level redirects even when origin changes
|
||||
try { (window as any).name = `WIX_OAUTH::${btoa(JSON.stringify(oauthData))}`; } catch {}
|
||||
const { authUrl } = await wixClient.auth.getAuthUrl(oauthData);
|
||||
window.location.href = authUrl;
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Box, CircularProgress, Typography, Alert } from '@mui/material';
|
||||
import { createClient, OAuthStrategy } from '@wix/sdk';
|
||||
import { apiClient } from '../../api/client';
|
||||
|
||||
const WixCallbackPage: React.FC = () => {
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@@ -14,22 +15,37 @@ const WixCallbackPage: React.FC = () => {
|
||||
setError(`${error}: ${errorDescription || ''}`);
|
||||
return;
|
||||
}
|
||||
// Recover oauthData via multiple fallbacks
|
||||
let oauthData: any | null = null;
|
||||
const saved = sessionStorage.getItem('wix_oauth_data') || localStorage.getItem('wix_oauth_data');
|
||||
if (!saved) {
|
||||
if (saved) {
|
||||
try { oauthData = JSON.parse(saved); } catch {}
|
||||
}
|
||||
if (!oauthData && state) {
|
||||
const byState = sessionStorage.getItem(`wix_oauth_data_${state}`);
|
||||
if (byState) {
|
||||
try { oauthData = JSON.parse(byState); } catch {}
|
||||
}
|
||||
}
|
||||
if (!oauthData && typeof window.name === 'string' && window.name.startsWith('WIX_OAUTH::')) {
|
||||
try { oauthData = JSON.parse(atob(window.name.replace('WIX_OAUTH::',''))); } catch {}
|
||||
}
|
||||
if (!oauthData) {
|
||||
setError('Missing OAuth state. Please start the connection again.');
|
||||
return;
|
||||
}
|
||||
const oauthData = JSON.parse(saved);
|
||||
// Use the originally generated state to avoid SDK "Invalid _state" errors
|
||||
const tokens = await wixClient.auth.getMemberTokens(code, state, oauthData);
|
||||
wixClient.auth.setTokens(tokens);
|
||||
// Persist tokens for subsequent API calls on this tab
|
||||
try { sessionStorage.setItem('wix_tokens', JSON.stringify(tokens)); } catch {}
|
||||
// optional: ping backend to mark connected
|
||||
try { await fetch('/api/wix/test/connection/status'); } catch {}
|
||||
try { await apiClient.get('/api/wix/test/connection/status'); } catch {}
|
||||
// Cleanup saved oauth data
|
||||
sessionStorage.removeItem('wix_oauth_data');
|
||||
sessionStorage.removeItem(`wix_oauth_data_${state}`);
|
||||
localStorage.removeItem('wix_oauth_data');
|
||||
try { (window as any).name = ''; } catch {}
|
||||
// Mark frontend session as connected for onboarding UI
|
||||
sessionStorage.setItem('wix_connected', 'true');
|
||||
// Notify opener (if opened as popup) and close; otherwise fallback to redirect
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
* Integrates with external services like Sentry, LogRocket, etc.
|
||||
*/
|
||||
|
||||
import { apiClient } from '../api/client';
|
||||
|
||||
export interface ErrorReport {
|
||||
error: Error | string;
|
||||
context?: string;
|
||||
@@ -67,22 +69,16 @@ const sendToBackend = async (report: ErrorReport): Promise<void> => {
|
||||
try {
|
||||
// Only send in production or if explicitly enabled
|
||||
if (process.env.NODE_ENV === 'production' || process.env.REACT_APP_ENABLE_ERROR_REPORTING === 'true') {
|
||||
await fetch('/api/log-error', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
error_message: report.error instanceof Error ? report.error.message : report.error,
|
||||
error_stack: report.error instanceof Error ? report.error.stack : undefined,
|
||||
context: report.context,
|
||||
user_id: report.userId,
|
||||
metadata: report.metadata,
|
||||
severity: report.severity,
|
||||
timestamp: report.timestamp,
|
||||
user_agent: navigator.userAgent,
|
||||
url: window.location.href,
|
||||
}),
|
||||
await apiClient.post('/api/log-error', {
|
||||
error_message: report.error instanceof Error ? report.error.message : report.error,
|
||||
error_stack: report.error instanceof Error ? report.error.stack : undefined,
|
||||
context: report.context,
|
||||
user_id: report.userId,
|
||||
metadata: report.metadata,
|
||||
severity: report.severity,
|
||||
timestamp: report.timestamp,
|
||||
user_agent: navigator.userAgent,
|
||||
url: window.location.href,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
Reference in New Issue
Block a user