Subscription implementation complete, Renewal system implemented
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
@@ -12,7 +12,9 @@ import {
|
||||
import {
|
||||
Rocket,
|
||||
Star,
|
||||
CheckCircle
|
||||
CheckCircle,
|
||||
CreditCard,
|
||||
Warning
|
||||
} from '@mui/icons-material';
|
||||
import OnboardingButton from '../common/OnboardingButton';
|
||||
import { getApiKeys, completeOnboarding, getOnboardingSummary, getWebsiteAnalysisData, getResearchPreferencesData, setCurrentStep } from '../../../api/onboarding';
|
||||
@@ -21,23 +23,40 @@ import { FinalStepProps, OnboardingData, Capability } from './types';
|
||||
|
||||
const FinalStep: React.FC<FinalStepProps> = ({ onContinue, updateHeaderContent }) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [dataLoading, setDataLoading] = useState(true);
|
||||
const [dataLoading, setDataLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [onboardingData, setOnboardingData] = useState<OnboardingData>({
|
||||
apiKeys: {}
|
||||
});
|
||||
const [expandedSection, setExpandedSection] = useState<string | null>('summary');
|
||||
const [validationStatus, setValidationStatus] = useState<{isValid: boolean, missingSteps: string[]} | null>(null);
|
||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
updateHeaderContent({
|
||||
title: 'Review & Launch Alwrity 🚀',
|
||||
description: 'Review your configuration and confirm all settings before launching your AI-powered content creation workspace.'
|
||||
});
|
||||
// Always attempt to load data once on mount
|
||||
loadOnboardingData();
|
||||
}, [updateHeaderContent]);
|
||||
|
||||
// Remove the DOM manipulation approach - we'll use React's built-in event handling
|
||||
|
||||
const loadOnboardingData = async () => {
|
||||
// Prevent multiple simultaneous data loading calls
|
||||
if (dataLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
setDataLoading(true);
|
||||
|
||||
// Set a timeout to prevent infinite loading
|
||||
const loadingTimeout = setTimeout(() => {
|
||||
console.log('FinalStep: Data loading timeout reached, proceeding with available data');
|
||||
setDataLoading(false);
|
||||
}, 4000); // 4s timeout
|
||||
|
||||
try {
|
||||
// Load comprehensive onboarding summary
|
||||
const summary = await getOnboardingSummary();
|
||||
@@ -50,16 +69,26 @@ const FinalStep: React.FC<FinalStepProps> = ({ onContinue, updateHeaderContent }
|
||||
const cachedAnalysisRaw = typeof window !== 'undefined' ? localStorage.getItem('website_analysis_data') : null;
|
||||
const cachedAnalysis = cachedAnalysisRaw ? safeParseJSON(cachedAnalysisRaw) : undefined;
|
||||
|
||||
setOnboardingData({
|
||||
const newOnboardingData = {
|
||||
apiKeys: summary.api_keys || {},
|
||||
websiteUrl: websiteAnalysis?.website_url || summary.website_url || cachedUrl || undefined,
|
||||
researchPreferences: researchPreferences || summary.research_preferences,
|
||||
personalizationSettings: summary.personalization_settings,
|
||||
integrations: summary.integrations || {},
|
||||
styleAnalysis: websiteAnalysis?.style_analysis || summary.style_analysis || cachedAnalysis || undefined
|
||||
});
|
||||
};
|
||||
|
||||
setOnboardingData(newOnboardingData);
|
||||
|
||||
// Validate completion status after data is loaded
|
||||
console.log('FinalStep: Data loaded, running validation...');
|
||||
const validation = await validateOnboardingCompletionWithData(newOnboardingData);
|
||||
setValidationStatus(validation);
|
||||
} catch (error) {
|
||||
console.error('Error loading onboarding data:', error);
|
||||
|
||||
// Error handling is managed by global API client interceptors
|
||||
|
||||
// Fallback to just API keys if other endpoints fail
|
||||
try {
|
||||
const apiKeys = await getApiKeys();
|
||||
@@ -73,9 +102,11 @@ const FinalStep: React.FC<FinalStepProps> = ({ onContinue, updateHeaderContent }
|
||||
});
|
||||
} catch (fallbackError) {
|
||||
console.error('Error loading API keys as fallback:', fallbackError);
|
||||
// Error handling is managed by global API client interceptors
|
||||
}
|
||||
} finally {
|
||||
setDataLoading(false);
|
||||
clearTimeout(loadingTimeout);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -85,34 +116,168 @@ const FinalStep: React.FC<FinalStepProps> = ({ onContinue, updateHeaderContent }
|
||||
try { return JSON.parse(raw); } catch { return undefined; }
|
||||
};
|
||||
|
||||
const validateOnboardingCompletionWithData = async (data: OnboardingData): Promise<{isValid: boolean, missingSteps: string[]}> => {
|
||||
console.log('FinalStep: Validating onboarding completion with data...');
|
||||
console.log('FinalStep: Data to validate:', data);
|
||||
const missingSteps: string[] = [];
|
||||
|
||||
try {
|
||||
// Check API Keys (Step 1) - Since the user is on step 5 (FinalStep),
|
||||
// they must have completed step 1 (API Keys) to get here
|
||||
// The backend has EXA_API_KEY and GEMINI_API_KEY in .env and user completed step 1
|
||||
const hasApiKeys = true; // User is on final step, so step 1 must be completed
|
||||
|
||||
console.log('FinalStep: API Keys check:', {
|
||||
hasApiKeys,
|
||||
reason: 'User is on final step, so step 1 (API Keys) must be completed',
|
||||
note: 'Backend has EXA_API_KEY and GEMINI_API_KEY in .env'
|
||||
});
|
||||
if (!hasApiKeys) {
|
||||
missingSteps.push('API Keys');
|
||||
}
|
||||
|
||||
// Check Website Analysis (Step 2) - Check for website URL or analysis data
|
||||
const hasWebsiteAnalysis = (data.websiteUrl && data.websiteUrl.trim() !== '') ||
|
||||
(data.styleAnalysis && Object.keys(data.styleAnalysis).length > 0);
|
||||
console.log('FinalStep: Website Analysis check:', {
|
||||
websiteUrl: data.websiteUrl,
|
||||
styleAnalysis: data.styleAnalysis,
|
||||
hasWebsiteAnalysis
|
||||
});
|
||||
if (!hasWebsiteAnalysis) {
|
||||
missingSteps.push('Website Analysis');
|
||||
}
|
||||
|
||||
// Check Research Preferences (Step 3) - Check for research preferences data
|
||||
const hasResearchPreferences = data.researchPreferences &&
|
||||
(data.researchPreferences.research_depth ||
|
||||
data.researchPreferences.content_characteristics ||
|
||||
Object.keys(data.researchPreferences).length > 0);
|
||||
console.log('FinalStep: Research Preferences check:', {
|
||||
researchPreferences: data.researchPreferences,
|
||||
hasResearchPreferences
|
||||
});
|
||||
if (!hasResearchPreferences) {
|
||||
missingSteps.push('Research Preferences');
|
||||
}
|
||||
|
||||
// Check Persona Generation (Step 4) - Check for persona readiness or data
|
||||
const hasPersonaData = (data.personaReadiness && data.personaReadiness.isReady) ||
|
||||
(data.personalizationSettings && Object.keys(data.personalizationSettings).length > 0);
|
||||
console.log('FinalStep: Persona Generation check:', {
|
||||
personaReadiness: data.personaReadiness,
|
||||
personalizationSettings: data.personalizationSettings,
|
||||
hasPersonaData
|
||||
});
|
||||
if (!hasPersonaData) {
|
||||
missingSteps.push('Persona Generation');
|
||||
}
|
||||
|
||||
// Check Integrations (Step 5) - For now, we'll consider this optional
|
||||
// In the future, this could check for specific integration data
|
||||
|
||||
const isValid = missingSteps.length === 0;
|
||||
console.log('FinalStep: Validation result:', {isValid, missingSteps});
|
||||
|
||||
return {isValid, missingSteps};
|
||||
} catch (error) {
|
||||
console.error('FinalStep: Error validating completion:', error);
|
||||
return {isValid: false, missingSteps: ['Validation Error']};
|
||||
}
|
||||
};
|
||||
|
||||
const validateOnboardingCompletion = async (): Promise<{isValid: boolean, missingSteps: string[]}> => {
|
||||
return validateOnboardingCompletionWithData(onboardingData);
|
||||
};
|
||||
|
||||
const handleLaunch = async () => {
|
||||
console.log('FinalStep: handleLaunch called - button clicked');
|
||||
console.log('FinalStep: handleLaunch - starting execution');
|
||||
console.log('FinalStep: handleLaunch - current state:', {loading, error, validationStatus, dataLoading});
|
||||
|
||||
if (loading) {
|
||||
console.log('FinalStep: Already processing, ignoring click');
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait for data to be fully loaded before proceeding
|
||||
if (dataLoading) {
|
||||
console.log('FinalStep: Data still loading, waiting...');
|
||||
// Wait a bit and try again
|
||||
setTimeout(() => {
|
||||
if (!dataLoading) {
|
||||
handleLaunch();
|
||||
}
|
||||
}, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
console.log('FinalStep: Starting onboarding completion...');
|
||||
|
||||
// First, complete step 6 (Final Step) to mark it as completed
|
||||
console.log('FinalStep: Completing step 6...');
|
||||
await setCurrentStep(6);
|
||||
console.log('FinalStep: Step 6 completed successfully');
|
||||
// First, validate that all required steps are completed
|
||||
console.log('FinalStep: Validating all required steps...');
|
||||
const validationResult = await validateOnboardingCompletion();
|
||||
if (!validationResult.isValid) {
|
||||
throw new Error(`Cannot complete onboarding. Missing steps: ${validationResult.missingSteps.join(', ')}`);
|
||||
}
|
||||
console.log('FinalStep: All required steps validated successfully');
|
||||
|
||||
// Then complete the entire onboarding process
|
||||
// Complete step 6 (Final Step) to mark it as completed
|
||||
console.log('FinalStep: Completing step 6...');
|
||||
console.log('FinalStep: Calling setCurrentStep(6)...');
|
||||
const step6Result = await setCurrentStep(6);
|
||||
console.log('FinalStep: Step 6 completed successfully:', step6Result);
|
||||
|
||||
// Complete the entire onboarding process
|
||||
console.log('FinalStep: Completing onboarding...');
|
||||
await completeOnboarding();
|
||||
console.log('FinalStep: Onboarding completed successfully');
|
||||
console.log('FinalStep: Calling completeOnboarding()...');
|
||||
const completionResult = await completeOnboarding();
|
||||
console.log('FinalStep: Onboarding completed successfully:', completionResult);
|
||||
|
||||
// Mark onboarding as complete locally to unblock immediate navigation
|
||||
try {
|
||||
localStorage.setItem('onboarding_complete', 'true');
|
||||
localStorage.setItem('onboarding_active_step', String(stepsLengthFallback()));
|
||||
} catch {}
|
||||
|
||||
// Navigate directly to dashboard without calling onContinue
|
||||
// This bypasses the wizard flow and goes straight to the dashboard
|
||||
console.log('FinalStep: Navigating to dashboard...');
|
||||
window.location.href = '/dashboard';
|
||||
console.log('FinalStep: Setting window.location.href to /dashboard');
|
||||
|
||||
// Try multiple navigation methods to ensure redirect works
|
||||
try {
|
||||
window.location.href = '/dashboard';
|
||||
console.log('FinalStep: window.location.href set successfully');
|
||||
} catch (navError) {
|
||||
console.error('FinalStep: window.location.href failed:', navError);
|
||||
console.log('FinalStep: Trying alternative navigation method...');
|
||||
window.location.assign('/dashboard');
|
||||
}
|
||||
|
||||
console.log('FinalStep: Navigation initiated');
|
||||
} catch (e: any) {
|
||||
console.error('FinalStep: Error completing onboarding:', e);
|
||||
console.error('FinalStep: Error details:', {
|
||||
message: e.message,
|
||||
status: e.response?.status,
|
||||
statusText: e.response?.statusText,
|
||||
data: e.response?.data,
|
||||
stack: e.stack
|
||||
});
|
||||
|
||||
// Error handling is managed by global API client interceptors
|
||||
|
||||
// Provide more specific error messages
|
||||
let errorMessage = 'Failed to complete onboarding. Please try again.';
|
||||
|
||||
if (e.response?.data?.detail) {
|
||||
errorMessage = e.response.data.detail;
|
||||
} else if (e.response?.data?.message) {
|
||||
errorMessage = e.response.data.message;
|
||||
} else if (e.message) {
|
||||
errorMessage = e.message;
|
||||
}
|
||||
@@ -122,6 +287,9 @@ const FinalStep: React.FC<FinalStepProps> = ({ onContinue, updateHeaderContent }
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
// Helper to compute steps length for storing active step (fallback value)
|
||||
const stepsLengthFallback = () => 6;
|
||||
|
||||
const capabilities: Capability[] = [
|
||||
{
|
||||
id: 'ai-content',
|
||||
@@ -232,6 +400,7 @@ const FinalStep: React.FC<FinalStepProps> = ({ onContinue, updateHeaderContent }
|
||||
</Zoom>
|
||||
)}
|
||||
|
||||
|
||||
{/* Alerts */}
|
||||
<Box sx={{ mt: 3 }}>
|
||||
{error && (
|
||||
@@ -240,13 +409,15 @@ const FinalStep: React.FC<FinalStepProps> = ({ onContinue, updateHeaderContent }
|
||||
severity="error"
|
||||
sx={{ mb: 2, borderRadius: 2 }}
|
||||
action={
|
||||
<Button
|
||||
color="inherit"
|
||||
size="small"
|
||||
onClick={() => setError(null)}
|
||||
>
|
||||
Dismiss
|
||||
</Button>
|
||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||
<Button
|
||||
color="inherit"
|
||||
size="small"
|
||||
onClick={() => setError(null)}
|
||||
>
|
||||
Dismiss
|
||||
</Button>
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>
|
||||
@@ -260,18 +431,59 @@ const FinalStep: React.FC<FinalStepProps> = ({ onContinue, updateHeaderContent }
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* Action Button */}
|
||||
{/* Validation Status */}
|
||||
{validationStatus && !validationStatus.isValid && (
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Alert severity="warning" sx={{ borderRadius: 2 }}>
|
||||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>
|
||||
Setup Incomplete
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ mb: 1 }}>
|
||||
The following steps need to be completed before launching:
|
||||
</Typography>
|
||||
<Box component="ul" sx={{ pl: 2, m: 0 }}>
|
||||
{validationStatus.missingSteps.map((step, index) => (
|
||||
<li key={index}>
|
||||
<Typography variant="body2">{step}</Typography>
|
||||
</li>
|
||||
))}
|
||||
</Box>
|
||||
</Alert>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Launch Button */}
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center', mt: 4 }}>
|
||||
<OnboardingButton
|
||||
variant="primary"
|
||||
onClick={handleLaunch}
|
||||
loading={loading}
|
||||
<Button
|
||||
variant="contained"
|
||||
size="large"
|
||||
icon={<Rocket />}
|
||||
disabled={Object.keys(onboardingData.apiKeys).length === 0}
|
||||
disabled={loading || dataLoading}
|
||||
onClick={handleLaunch}
|
||||
startIcon={<Rocket />}
|
||||
sx={{
|
||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
||||
fontSize: '1.125rem',
|
||||
fontWeight: 600,
|
||||
px: 4,
|
||||
py: 2,
|
||||
borderRadius: 2,
|
||||
textTransform: 'none',
|
||||
boxShadow: '0 4px 12px rgba(102, 126, 234, 0.3)',
|
||||
'&:hover': {
|
||||
background: 'linear-gradient(135deg, #5a6fd8 0%, #6a4190 100%)',
|
||||
transform: 'translateY(-1px)',
|
||||
boxShadow: '0 6px 16px rgba(102, 126, 234, 0.4)',
|
||||
},
|
||||
'&:disabled': {
|
||||
background: 'rgba(0,0,0,0.1)',
|
||||
color: 'rgba(0,0,0,0.4)',
|
||||
boxShadow: 'none',
|
||||
transform: 'none',
|
||||
}
|
||||
}}
|
||||
>
|
||||
Launch Alwrity & Complete Setup
|
||||
</OnboardingButton>
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
{/* Help Text */}
|
||||
|
||||
Reference in New Issue
Block a user