Add SEO fields to Tasks model, improve content generation response handling, and enhance progress bar animation
- Added primary_keyword, secondary_keywords, tags, and categories fields to Tasks model - Updated generate_content function to handle full JSON response with all SEO fields - Improved progress bar animation: smooth 1% increments every 300ms - Enhanced step detection for content generation vs clustering vs ideas - Fixed progress modal to show correct messages for each function type - Added comprehensive logging to Keywords and Tasks pages for AI functions - Fixed error handling to show meaningful error messages instead of generic failures
This commit is contained in:
76
frontend/src/components/common/PageTransition.tsx
Normal file
76
frontend/src/components/common/PageTransition.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
import { ReactNode, useEffect, useState } from 'react';
|
||||
import { useLocation } from 'react-router';
|
||||
|
||||
interface PageTransitionProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Smooth page transition wrapper with modern loading indicator
|
||||
* Provides seamless transitions between pages without feeling like a page load
|
||||
* Uses subtle fade effects and minimal loading indicator
|
||||
*/
|
||||
export default function PageTransition({ children }: PageTransitionProps) {
|
||||
const location = useLocation();
|
||||
const [isTransitioning, setIsTransitioning] = useState(false);
|
||||
const [displayChildren, setDisplayChildren] = useState(children);
|
||||
const [currentPath, setCurrentPath] = useState(location.pathname);
|
||||
|
||||
useEffect(() => {
|
||||
// Only show transition if pathname actually changed
|
||||
if (location.pathname === currentPath) {
|
||||
setDisplayChildren(children);
|
||||
return;
|
||||
}
|
||||
|
||||
// Start transition with minimal delay
|
||||
setIsTransitioning(true);
|
||||
setCurrentPath(location.pathname);
|
||||
|
||||
// Quick fade-out, then swap content
|
||||
const fadeOutTimer = setTimeout(() => {
|
||||
setDisplayChildren(children);
|
||||
}, 100);
|
||||
|
||||
// Complete transition quickly for smooth feel
|
||||
const fadeInTimer = setTimeout(() => {
|
||||
setIsTransitioning(false);
|
||||
}, 200);
|
||||
|
||||
return () => {
|
||||
clearTimeout(fadeOutTimer);
|
||||
clearTimeout(fadeInTimer);
|
||||
};
|
||||
}, [location.pathname, children, currentPath]);
|
||||
|
||||
return (
|
||||
<div className="relative min-h-screen">
|
||||
{/* Subtle fade overlay - very light */}
|
||||
<div
|
||||
className={`absolute inset-0 bg-white/50 dark:bg-gray-900/50 backdrop-blur-sm transition-opacity duration-200 z-40 pointer-events-none ${
|
||||
isTransitioning ? 'opacity-100' : 'opacity-0'
|
||||
}`}
|
||||
/>
|
||||
|
||||
{/* Minimal loading indicator - only shows briefly */}
|
||||
{isTransitioning && (
|
||||
<div className="fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 z-50 pointer-events-none">
|
||||
<div className="relative w-10 h-10">
|
||||
<div className="absolute inset-0 border-2 border-gray-200/50 dark:border-gray-700/50 rounded-full"></div>
|
||||
<div className="absolute inset-0 border-2 border-transparent border-t-brand-500 rounded-full animate-spin"></div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Page content with smooth fade */}
|
||||
<div
|
||||
className={`transition-opacity duration-200 ease-in-out ${
|
||||
isTransitioning ? 'opacity-0' : 'opacity-100'
|
||||
}`}
|
||||
>
|
||||
{displayChildren}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { Modal } from '../ui/modal';
|
||||
import { ProgressBar } from '../ui/progress';
|
||||
import Button from '../ui/button/Button';
|
||||
@@ -34,13 +34,19 @@ export default function ProgressModal({
|
||||
}: ProgressModalProps) {
|
||||
// Auto-close on completion after 2 seconds
|
||||
// Don't auto-close on error - let user manually close to see error details
|
||||
const hasAutoClosedRef = React.useRef(false);
|
||||
useEffect(() => {
|
||||
if (status === 'completed' && onClose) {
|
||||
if (status === 'completed' && onClose && !hasAutoClosedRef.current) {
|
||||
hasAutoClosedRef.current = true;
|
||||
const timer = setTimeout(() => {
|
||||
onClose();
|
||||
}, 2000);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
// Reset when status changes away from completed
|
||||
if (status !== 'completed') {
|
||||
hasAutoClosedRef.current = false;
|
||||
}
|
||||
// Don't auto-close on error - user should manually dismiss
|
||||
}, [status, onClose]);
|
||||
|
||||
|
||||
@@ -93,8 +93,9 @@ export default function ResourceDebugOverlay({ enabled }: ResourceDebugOverlayPr
|
||||
const requestId = response.headers.get('X-Resource-Tracking-ID');
|
||||
if (requestId) {
|
||||
requestIdRef.current = requestId;
|
||||
// Fetch metrics after a short delay to ensure backend has stored them
|
||||
setTimeout(() => fetchRequestMetrics(requestId), 200);
|
||||
// Fetch metrics after a delay to ensure backend has stored them
|
||||
// Use a slightly longer delay to avoid race conditions
|
||||
setTimeout(() => fetchRequestMetrics(requestId), 300);
|
||||
}
|
||||
|
||||
return response;
|
||||
@@ -111,7 +112,7 @@ export default function ResourceDebugOverlay({ enabled }: ResourceDebugOverlayPr
|
||||
}, [enabled, isAdminOrDeveloper]);
|
||||
|
||||
// Fetch metrics for a request - use fetchAPI to get proper authentication handling
|
||||
const fetchRequestMetrics = async (requestId: string) => {
|
||||
const fetchRequestMetrics = async (requestId: string, retryCount = 0) => {
|
||||
try {
|
||||
// Use fetchAPI which handles token refresh and authentication properly
|
||||
// But we need to use native fetch to avoid interception loop
|
||||
@@ -135,7 +136,10 @@ export default function ResourceDebugOverlay({ enabled }: ResourceDebugOverlayPr
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
console.log('Fetched metrics for request:', requestId, data); // Debug log
|
||||
// Only log in debug mode to reduce console noise
|
||||
if (import.meta.env.DEV) {
|
||||
console.debug('Fetched metrics for request:', requestId, data);
|
||||
}
|
||||
metricsRef.current = [...metricsRef.current, data];
|
||||
setMetrics([...metricsRef.current]);
|
||||
} else if (response.status === 401) {
|
||||
@@ -166,11 +170,19 @@ export default function ResourceDebugOverlay({ enabled }: ResourceDebugOverlayPr
|
||||
}
|
||||
// Silently ignore 401 errors - user might not be authenticated
|
||||
} else if (response.status === 404) {
|
||||
// Metrics not found or expired - this is expected, silently ignore
|
||||
// Metrics expire after 5 minutes, so 404 is normal for older requests
|
||||
// Metrics not found - could be race condition, retry once after short delay
|
||||
if (retryCount === 0) {
|
||||
// First attempt failed, retry once after 200ms (middleware might still be storing)
|
||||
setTimeout(() => fetchRequestMetrics(requestId, 1), 200);
|
||||
return;
|
||||
}
|
||||
// Second attempt also failed - metrics truly not available
|
||||
// This is expected: metrics expired (5min TTL), request wasn't tracked, or middleware error
|
||||
// Silently ignore - no need to log or show error
|
||||
return;
|
||||
} else {
|
||||
console.warn('Failed to fetch metrics:', response.status, response.statusText);
|
||||
// Only log non-404/401 errors (500, 403, etc.)
|
||||
console.warn('Failed to fetch metrics:', response.status, response.statusText, 'for request:', requestId);
|
||||
}
|
||||
} catch (error) {
|
||||
// Only log non-network errors
|
||||
|
||||
Reference in New Issue
Block a user