Implement route protection for onboarding

- Create ProtectedRoute component to guard routes based on onboarding status
- Protect all non-onboarding routes (dashboard, blog-writer, seo, etc.)
- Users must complete onboarding before accessing any features
- Prevents confusion and broken functionality for new users
- Improves user experience by guiding users through proper setup
This commit is contained in:
ajaysi
2025-09-24 12:48:49 +05:30
parent 197720bea4
commit dee3e428bd
2 changed files with 138 additions and 8 deletions

View File

@@ -10,6 +10,7 @@ import ContentPlanningDashboard from './components/ContentPlanningDashboard/Cont
import FacebookWriter from './components/FacebookWriter/FacebookWriter';
import LinkedInWriter from './components/LinkedInWriter/LinkedInWriter';
import BlogWriter from './components/BlogWriter/BlogWriter';
import ProtectedRoute from './components/shared/ProtectedRoute';
import { apiClient } from './api/client';
@@ -170,9 +171,31 @@ const App: React.FC = () => {
);
}
// Check if CopilotKit API key is available
const copilotApiKey = process.env.REACT_APP_COPILOTKIT_API_KEY;
// If no CopilotKit API key, render without CopilotKit wrapper
if (!copilotApiKey) {
return (
<Router>
<Routes>
<Route path="/" element={<InitialRouteHandler />} />
<Route path="/onboarding" element={<Wizard />} />
<Route path="/dashboard" element={<ProtectedRoute><MainDashboard /></ProtectedRoute>} />
<Route path="/seo" element={<ProtectedRoute><SEODashboard /></ProtectedRoute>} />
<Route path="/seo-dashboard" element={<ProtectedRoute><SEODashboard /></ProtectedRoute>} />
<Route path="/content-planning" element={<ProtectedRoute><ContentPlanningDashboard /></ProtectedRoute>} />
<Route path="/facebook-writer" element={<ProtectedRoute><FacebookWriter /></ProtectedRoute>} />
<Route path="/linkedin-writer" element={<ProtectedRoute><LinkedInWriter /></ProtectedRoute>} />
<Route path="/blog-writer" element={<ProtectedRoute><BlogWriter /></ProtectedRoute>} />
</Routes>
</Router>
);
}
return (
<CopilotKit
publicApiKey={process.env.REACT_APP_COPILOTKIT_API_KEY}
publicApiKey={copilotApiKey}
showDevConsole={false}
onError={(e) => console.error("CopilotKit Error:", e)}
@@ -182,13 +205,13 @@ const App: React.FC = () => {
<Routes>
<Route path="/" element={<InitialRouteHandler />} />
<Route path="/onboarding" element={<Wizard />} />
<Route path="/dashboard" element={<MainDashboard />} />
<Route path="/seo" element={<SEODashboard />} />
<Route path="/seo-dashboard" element={<SEODashboard />} />
<Route path="/content-planning" element={<ContentPlanningDashboard />} />
<Route path="/facebook-writer" element={<FacebookWriter />} />
<Route path="/linkedin-writer" element={<LinkedInWriter />} />
<Route path="/blog-writer" element={<BlogWriter />} />
<Route path="/dashboard" element={<ProtectedRoute><MainDashboard /></ProtectedRoute>} />
<Route path="/seo" element={<ProtectedRoute><SEODashboard /></ProtectedRoute>} />
<Route path="/seo-dashboard" element={<ProtectedRoute><SEODashboard /></ProtectedRoute>} />
<Route path="/content-planning" element={<ProtectedRoute><ContentPlanningDashboard /></ProtectedRoute>} />
<Route path="/facebook-writer" element={<ProtectedRoute><FacebookWriter /></ProtectedRoute>} />
<Route path="/linkedin-writer" element={<ProtectedRoute><LinkedInWriter /></ProtectedRoute>} />
<Route path="/blog-writer" element={<ProtectedRoute><BlogWriter /></ProtectedRoute>} />
</Routes>
</ConditionalCopilotKit>
</Router>

View File

@@ -0,0 +1,107 @@
import React, { useState, useEffect } from 'react';
import { Navigate } from 'react-router-dom';
import { Box, CircularProgress, Typography } from '@mui/material';
import { apiClient } from '../../api/client';
interface ProtectedRouteProps {
children: React.ReactNode;
}
interface OnboardingStatus {
is_completed: boolean;
current_step: number;
completion_percentage: number;
next_step?: number;
started_at: string;
completed_at?: string;
can_proceed_to_final: boolean;
}
const ProtectedRoute: React.FC<ProtectedRouteProps> = ({ children }) => {
const [loading, setLoading] = useState(true);
const [onboardingComplete, setOnboardingComplete] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const checkOnboardingStatus = async () => {
try {
console.log('ProtectedRoute: Checking onboarding status...');
const response = await apiClient.get('/api/onboarding/status');
const status: OnboardingStatus = response.data;
console.log('ProtectedRoute: Onboarding status:', status);
if (status.is_completed) {
console.log('ProtectedRoute: Onboarding is complete, allowing access');
setOnboardingComplete(true);
} else {
console.log('ProtectedRoute: Onboarding not complete, redirecting to onboarding');
setOnboardingComplete(false);
}
} catch (err) {
console.error('ProtectedRoute: Error checking onboarding status:', err);
setError('Failed to check onboarding status');
// On error, assume onboarding is not complete for security
setOnboardingComplete(false);
} finally {
setLoading(false);
}
};
checkOnboardingStatus();
}, []);
if (loading) {
return (
<Box
display="flex"
flexDirection="column"
alignItems="center"
justifyContent="center"
minHeight="100vh"
gap={2}
>
<CircularProgress size={60} />
<Typography variant="h6" color="textSecondary">
Verifying access...
</Typography>
</Box>
);
}
if (error) {
return (
<Box
display="flex"
flexDirection="column"
alignItems="center"
justifyContent="center"
minHeight="100vh"
gap={2}
p={3}
>
<Typography variant="h5" color="error" gutterBottom>
Access Error
</Typography>
<Typography variant="body1" color="textSecondary" textAlign="center">
{error}
</Typography>
<Typography variant="body2" color="textSecondary" textAlign="center">
Please complete the setup process first.
</Typography>
</Box>
);
}
// If onboarding is not complete, redirect to onboarding
if (!onboardingComplete) {
console.log('ProtectedRoute: Redirecting to onboarding');
return <Navigate to="/onboarding" replace />;
}
// If onboarding is complete, render the protected component
console.log('ProtectedRoute: Rendering protected component');
return <>{children}</>;
};
export default ProtectedRoute;