Added onboarding progress tracking & landing page

This commit is contained in:
ajaysi
2025-10-02 13:20:15 +05:30
parent e57d2577f8
commit 510b79bbf8
135 changed files with 25917 additions and 5768 deletions

View File

@@ -1,8 +1,15 @@
import axios from 'axios';
// Create a shared axios instance for all API calls
// Optional token getter installed from within the app after Clerk is available
let authTokenGetter: (() => Promise<string | null>) | null = null;
export const setAuthTokenGetter = (getter: () => Promise<string | null>) => {
authTokenGetter = getter;
};
// Create a shared axios instance for all API calls (same-origin; CRA proxy forwards to backend)
export const apiClient = axios.create({
baseURL: 'http://localhost:8000',
baseURL: '',
timeout: 60000, // Increased to 60 seconds for regular API calls
headers: {
'Content-Type': 'application/json',
@@ -11,7 +18,7 @@ export const apiClient = axios.create({
// Create a specialized client for AI operations with extended timeout
export const aiApiClient = axios.create({
baseURL: 'http://localhost:8000',
baseURL: '',
timeout: 180000, // 3 minutes timeout for AI operations (matching 20-25 second responses)
headers: {
'Content-Type': 'application/json',
@@ -20,7 +27,7 @@ export const aiApiClient = axios.create({
// Create a specialized client for long-running operations like SEO analysis
export const longRunningApiClient = axios.create({
baseURL: 'http://localhost:8000',
baseURL: '',
timeout: 300000, // 5 minutes timeout for SEO analysis
headers: {
'Content-Type': 'application/json',
@@ -29,7 +36,7 @@ export const longRunningApiClient = axios.create({
// Create a specialized client for polling operations with reasonable timeout
export const pollingApiClient = axios.create({
baseURL: 'http://localhost:8000',
baseURL: '',
timeout: 60000, // 60 seconds timeout for polling status checks
headers: {
'Content-Type': 'application/json',
@@ -38,8 +45,17 @@ export const pollingApiClient = axios.create({
// Add request interceptor for logging (optional)
apiClient.interceptors.request.use(
(config) => {
async (config) => {
console.log(`Making ${config.method?.toUpperCase()} request to ${config.url}`);
try {
const token = authTokenGetter ? await authTokenGetter() : null;
if (token) {
config.headers = config.headers || {};
(config.headers as any)['Authorization'] = `Bearer ${token}`;
}
} catch (e) {
// non-fatal
}
return config;
},
(error) => {
@@ -47,12 +63,41 @@ apiClient.interceptors.request.use(
}
);
// Add response interceptor for error handling (optional)
// Add response interceptor with automatic token refresh on 401
apiClient.interceptors.response.use(
(response) => {
return response;
},
(error) => {
async (error) => {
const originalRequest = error.config;
// If 401 and we haven't retried yet, try to refresh token and retry
if (error?.response?.status === 401 && !originalRequest._retry && authTokenGetter) {
originalRequest._retry = true;
try {
// Get fresh token
const newToken = await authTokenGetter();
if (newToken) {
// Update the request with new token
originalRequest.headers['Authorization'] = `Bearer ${newToken}`;
// Retry the request
return apiClient(originalRequest);
}
} catch (retryError) {
console.error('Token refresh failed:', retryError);
}
// If retry failed and not in onboarding, redirect
const isOnboardingRoute = window.location.pathname.includes('/onboarding') ||
window.location.pathname === '/';
if (!isOnboardingRoute) {
try { window.location.assign('/'); } catch {}
} else {
console.warn('401 Unauthorized - token refresh failed');
}
}
console.error('API Error:', error.response?.status, error.response?.data);
return Promise.reject(error);
}
@@ -60,8 +105,15 @@ apiClient.interceptors.response.use(
// Add interceptors for AI client
aiApiClient.interceptors.request.use(
(config) => {
async (config) => {
console.log(`Making AI ${config.method?.toUpperCase()} request to ${config.url}`);
try {
const token = authTokenGetter ? await authTokenGetter() : null;
if (token) {
config.headers = config.headers || {};
(config.headers as any)['Authorization'] = `Bearer ${token}`;
}
} catch (e) {}
return config;
},
(error) => {
@@ -73,7 +125,32 @@ aiApiClient.interceptors.response.use(
(response) => {
return response;
},
(error) => {
async (error) => {
const originalRequest = error.config;
// If 401 and we haven't retried yet, try to refresh token and retry
if (error?.response?.status === 401 && !originalRequest._retry && authTokenGetter) {
originalRequest._retry = true;
try {
const newToken = await authTokenGetter();
if (newToken) {
originalRequest.headers['Authorization'] = `Bearer ${newToken}`;
return aiApiClient(originalRequest);
}
} catch (retryError) {
console.error('Token refresh failed:', retryError);
}
const isOnboardingRoute = window.location.pathname.includes('/onboarding') ||
window.location.pathname === '/';
if (!isOnboardingRoute) {
try { window.location.assign('/'); } catch {}
} else {
console.warn('401 Unauthorized - token refresh failed');
}
}
console.error('AI API Error:', error.response?.status, error.response?.data);
return Promise.reject(error);
}
@@ -81,8 +158,15 @@ aiApiClient.interceptors.response.use(
// Add interceptors for long-running client
longRunningApiClient.interceptors.request.use(
(config) => {
async (config) => {
console.log(`Making long-running ${config.method?.toUpperCase()} request to ${config.url}`);
try {
const token = authTokenGetter ? await authTokenGetter() : null;
if (token) {
config.headers = config.headers || {};
(config.headers as any)['Authorization'] = `Bearer ${token}`;
}
} catch (e) {}
return config;
},
(error) => {
@@ -95,6 +179,16 @@ longRunningApiClient.interceptors.response.use(
return response;
},
(error) => {
if (error?.response?.status === 401) {
// Only redirect on 401 if we're not in onboarding flow
const isOnboardingRoute = window.location.pathname.includes('/onboarding') ||
window.location.pathname === '/';
if (!isOnboardingRoute) {
try { window.location.assign('/'); } catch {}
} else {
console.warn('401 Unauthorized during onboarding - token may need refresh');
}
}
console.error('Long-running API Error:', error.response?.status, error.response?.data);
return Promise.reject(error);
}
@@ -102,8 +196,15 @@ longRunningApiClient.interceptors.response.use(
// Add interceptors for polling client
pollingApiClient.interceptors.request.use(
(config) => {
async (config) => {
console.log(`Making polling ${config.method?.toUpperCase()} request to ${config.url}`);
try {
const token = authTokenGetter ? await authTokenGetter() : null;
if (token) {
config.headers = config.headers || {};
(config.headers as any)['Authorization'] = `Bearer ${token}`;
}
} catch (e) {}
return config;
},
(error) => {
@@ -116,6 +217,16 @@ pollingApiClient.interceptors.response.use(
return response;
},
(error) => {
if (error?.response?.status === 401) {
// Only redirect on 401 if we're not in onboarding flow
const isOnboardingRoute = window.location.pathname.includes('/onboarding') ||
window.location.pathname === '/';
if (!isOnboardingRoute) {
try { window.location.assign('/'); } catch {}
} else {
console.warn('401 Unauthorized during onboarding - token may need refresh');
}
}
console.error('Polling API Error:', error.response?.status, error.response?.data);
return Promise.reject(error);
}