Added image generation to blog writer
This commit is contained in:
210
frontend/src/hooks/usePhaseNavigation.ts
Normal file
210
frontend/src/hooks/usePhaseNavigation.ts
Normal file
@@ -0,0 +1,210 @@
|
||||
import { useState, useEffect, useMemo, useRef, useCallback } from 'react';
|
||||
import { BlogResearchResponse, BlogOutlineSection } from '../services/blogWriterApi';
|
||||
|
||||
export interface Phase {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
description: string;
|
||||
completed: boolean;
|
||||
current: boolean;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
export const usePhaseNavigation = (
|
||||
research: BlogResearchResponse | null,
|
||||
outline: BlogOutlineSection[],
|
||||
outlineConfirmed: boolean,
|
||||
hasContent: boolean,
|
||||
contentConfirmed: boolean,
|
||||
seoAnalysis: any,
|
||||
seoMetadata: any,
|
||||
seoRecommendationsApplied?: boolean
|
||||
) => {
|
||||
// Initialize from localStorage if available
|
||||
const getInitialPhase = (): string => {
|
||||
try {
|
||||
if (typeof window !== 'undefined') {
|
||||
const stored = window.localStorage.getItem('blogwriter_current_phase');
|
||||
if (stored) return stored;
|
||||
}
|
||||
} catch {}
|
||||
return 'research';
|
||||
};
|
||||
|
||||
const [currentPhase, setCurrentPhase] = useState<string>(getInitialPhase());
|
||||
const [userSelectedPhase, setUserSelectedPhase] = useState<boolean>(() => {
|
||||
try {
|
||||
if (typeof window !== 'undefined') {
|
||||
const stored = window.localStorage.getItem('blogwriter_user_selected_phase');
|
||||
return stored === 'true';
|
||||
}
|
||||
} catch {}
|
||||
return false;
|
||||
});
|
||||
const lastClickAtRef = useRef<number>(0);
|
||||
|
||||
// Determine phase states based on current data
|
||||
const phases = useMemo((): Phase[] => {
|
||||
const researchCompleted = !!research;
|
||||
const outlineCompleted = outline.length > 0;
|
||||
const contentCompleted = hasContent && contentConfirmed;
|
||||
// SEO is complete when analysis exists AND recommendations are applied
|
||||
const seoCompleted = !!seoAnalysis && (seoRecommendationsApplied === true || !!seoMetadata);
|
||||
|
||||
return [
|
||||
{
|
||||
id: 'research',
|
||||
name: 'Research',
|
||||
icon: '🔍',
|
||||
description: 'Research your topic and gather data',
|
||||
completed: researchCompleted,
|
||||
current: currentPhase === 'research',
|
||||
disabled: false // Research is always accessible
|
||||
},
|
||||
{
|
||||
id: 'outline',
|
||||
name: 'Outline',
|
||||
icon: '📝',
|
||||
description: 'Create and refine your blog outline',
|
||||
completed: outlineCompleted,
|
||||
current: currentPhase === 'outline',
|
||||
disabled: !researchCompleted // Disabled only if research not completed (can always go back if completed)
|
||||
},
|
||||
{
|
||||
id: 'content',
|
||||
name: 'Content',
|
||||
icon: '✍️',
|
||||
description: 'Generate and edit your blog content',
|
||||
completed: contentCompleted,
|
||||
current: currentPhase === 'content',
|
||||
disabled: !outlineCompleted // Disabled only if outline not completed (can always go back if completed)
|
||||
},
|
||||
{
|
||||
id: 'seo',
|
||||
name: 'SEO',
|
||||
icon: '📈',
|
||||
description: 'Optimize for search engines',
|
||||
completed: seoCompleted,
|
||||
current: currentPhase === 'seo',
|
||||
disabled: !contentCompleted // Disabled only if content not completed (can always go back if completed)
|
||||
},
|
||||
{
|
||||
id: 'publish',
|
||||
name: 'Publish',
|
||||
icon: '🚀',
|
||||
description: 'Publish your blog post',
|
||||
completed: false, // This would be set when actually published
|
||||
current: currentPhase === 'publish',
|
||||
disabled: !seoCompleted // Can access if SEO done
|
||||
}
|
||||
];
|
||||
}, [research, outline, outlineConfirmed, hasContent, contentConfirmed, seoAnalysis, seoMetadata, seoRecommendationsApplied, currentPhase]);
|
||||
|
||||
// Persist current phase and user selection
|
||||
useEffect(() => {
|
||||
try {
|
||||
if (typeof window !== 'undefined') {
|
||||
window.localStorage.setItem('blogwriter_current_phase', currentPhase);
|
||||
window.localStorage.setItem('blogwriter_user_selected_phase', String(userSelectedPhase));
|
||||
}
|
||||
} catch {}
|
||||
}, [currentPhase, userSelectedPhase]);
|
||||
|
||||
// Validate stored phase against current availability (quiet)
|
||||
useEffect(() => {
|
||||
const current = phases.find(p => p.id === currentPhase);
|
||||
if (!current) {
|
||||
setCurrentPhase('research');
|
||||
return;
|
||||
}
|
||||
if (current.disabled) {
|
||||
// Find the first non-disabled phase in order of progression the user qualifies for
|
||||
const fallback = phases.find(p => !p.disabled) || ({ id: 'research' } as Phase);
|
||||
if (fallback.id !== currentPhase) {
|
||||
setCurrentPhase(fallback.id);
|
||||
}
|
||||
}
|
||||
}, [phases, currentPhase]);
|
||||
|
||||
// Auto-update current phase based on completion status (only if user hasn't manually selected a phase)
|
||||
useEffect(() => {
|
||||
if (userSelectedPhase) {
|
||||
return; // Don't auto-update if user has manually selected a phase
|
||||
}
|
||||
|
||||
// Auto-progress to the next available phase when conditions are met
|
||||
if (research && outline.length === 0) {
|
||||
// Research completed, but no outline yet - stay on research
|
||||
if (currentPhase !== 'research') {
|
||||
setCurrentPhase('research');
|
||||
}
|
||||
} else if (research && outline.length > 0 && !outlineConfirmed) {
|
||||
// Outline created but not confirmed - move to outline phase
|
||||
if (currentPhase !== 'outline') {
|
||||
setCurrentPhase('outline');
|
||||
}
|
||||
} else if (outlineConfirmed && hasContent && !contentConfirmed) {
|
||||
// Content generated but not confirmed - move to content phase
|
||||
if (currentPhase !== 'content') {
|
||||
setCurrentPhase('content');
|
||||
}
|
||||
} else if (contentConfirmed && !seoAnalysis) {
|
||||
// Content confirmed but no SEO analysis yet - move to SEO phase
|
||||
if (currentPhase !== 'seo') {
|
||||
setCurrentPhase('seo');
|
||||
}
|
||||
} else if (seoAnalysis && !seoRecommendationsApplied && !seoMetadata) {
|
||||
// SEO analysis done but recommendations not applied - stay on SEO phase
|
||||
if (currentPhase !== 'seo') {
|
||||
setCurrentPhase('seo');
|
||||
}
|
||||
} else if (seoAnalysis && (seoRecommendationsApplied || seoMetadata)) {
|
||||
// SEO recommendations applied or metadata generated
|
||||
if (currentPhase === 'seo') {
|
||||
// CRITICAL: Stay in SEO phase so user can review updated content - don't auto-progress
|
||||
// User will manually navigate to publish when ready
|
||||
// This prevents blank screen by keeping user in SEO phase where BlogEditor is visible
|
||||
// No action needed - already in SEO phase, stay here
|
||||
} else {
|
||||
// User is NOT in SEO phase - can progress to publish
|
||||
// This handles cases where user navigates away and comes back
|
||||
// Only auto-progress if user is already in a different phase (not actively in SEO)
|
||||
if (currentPhase !== 'publish') {
|
||||
setCurrentPhase('publish');
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [research, outline, outlineConfirmed, hasContent, contentConfirmed, seoAnalysis, seoMetadata, seoRecommendationsApplied, currentPhase, userSelectedPhase]);
|
||||
|
||||
const navigateToPhase = useCallback((phaseId: string) => {
|
||||
// Minimal debounce (200ms) to avoid race conditions on rapid clicks
|
||||
const now = Date.now();
|
||||
if (now - lastClickAtRef.current < 200) { return; }
|
||||
lastClickAtRef.current = now;
|
||||
|
||||
const phase = phases.find(p => p.id === phaseId);
|
||||
|
||||
if (phase && !phase.disabled) {
|
||||
setCurrentPhase(phaseId);
|
||||
setUserSelectedPhase(true); // Mark that user has manually selected a phase
|
||||
} else {
|
||||
// Quietly ignore blocked navigation
|
||||
}
|
||||
}, [phases, currentPhase]);
|
||||
|
||||
// Reset user selection when a new phase is completed (to allow auto-progression)
|
||||
const resetUserSelection = () => {
|
||||
setUserSelectedPhase(false);
|
||||
};
|
||||
|
||||
return {
|
||||
phases,
|
||||
currentPhase,
|
||||
navigateToPhase,
|
||||
setCurrentPhase,
|
||||
resetUserSelection
|
||||
};
|
||||
};
|
||||
|
||||
export default usePhaseNavigation;
|
||||
Reference in New Issue
Block a user