Files
ALwrity/frontend/src/hooks/useGSCBrainstorm.ts
2026-05-23 13:09:41 +05:30

159 lines
5.0 KiB
TypeScript

import { useState, useCallback, useRef, useEffect } from 'react';
import { useAuth } from '@clerk/clerk-react';
import {
gscBrainstormAPI,
BrainstormResult,
ContentOpportunity,
KeywordGap,
QuickWin,
PageOpportunity,
AIRecommendations,
AIRecommendation,
BrainstormSummary,
} from '../api/gscBrainstorm';
import { useGSCBrainstormConnection } from './useGSCBrainstormConnection';
interface UseGSCBrainstormReturn {
gscConnected: boolean;
gscSites: { siteUrl: string; permissionLevel: string }[] | null;
isConnecting: boolean;
connectError: string | null;
isBrainstorming: boolean;
brainstormError: string | null;
brainstormResult: BrainstormResult | null;
contentOpportunities: ContentOpportunity[];
keywordGaps: KeywordGap[];
quickWins: QuickWin[];
pageOpportunities: PageOpportunity[];
aiRecommendations: AIRecommendations | null;
summary: BrainstormSummary | null;
connectGSC: () => Promise<void>;
brainstorm: (keywords: string, siteUrl?: string) => Promise<BrainstormResult | null>;
reset: () => void;
progressMessage: string;
}
const PROGRESS_MESSAGES = [
'Fetching your Google Search Console data for the last 30 days...',
'Analyzing which keywords bring traffic to your site and which ones need work...',
'Scanning for quick wins — keywords already on page 1 that just need a boost...',
'Identifying keyword gaps where better content could move you to page 1...',
'Reviewing your pages for optimization opportunities...',
'Computing your SEO health score and benchmark metrics...',
'Generating AI-powered blog post recommendations tailored to your GSC data...',
'Formatting insights into actionable topic suggestions you can use today...',
];
export const useGSCBrainstorm = (): UseGSCBrainstormReturn => {
const { getToken } = useAuth();
const {
gscConnected,
gscSites,
isConnecting,
connectError,
checkConnection,
connectGSC,
} = useGSCBrainstormConnection();
const [isBrainstorming, setIsBrainstorming] = useState(false);
const [brainstormError, setBrainstormError] = useState<string | null>(null);
const [brainstormResult, setBrainstormResult] = useState<BrainstormResult | null>(null);
const [progressMessage, setProgressMessage] = useState('');
const progressIndexRef = useRef(0);
const progressTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);
useEffect(() => {
return () => {
if (progressTimerRef.current) {
clearInterval(progressTimerRef.current);
}
};
}, []);
const startProgressMessages = () => {
progressIndexRef.current = 0;
setProgressMessage(PROGRESS_MESSAGES[0]);
progressTimerRef.current = setInterval(() => {
progressIndexRef.current += 1;
if (progressIndexRef.current < PROGRESS_MESSAGES.length) {
setProgressMessage(PROGRESS_MESSAGES[progressIndexRef.current]);
} else if (progressTimerRef.current) {
clearInterval(progressTimerRef.current);
progressTimerRef.current = null;
}
}, 3000);
};
const stopProgressMessages = () => {
if (progressTimerRef.current) {
clearInterval(progressTimerRef.current);
progressTimerRef.current = null;
}
setProgressMessage('');
};
const brainstorm = useCallback(
async (keywords: string, siteUrl?: string): Promise<BrainstormResult | null> => {
setIsBrainstorming(true);
setBrainstormError(null);
startProgressMessages();
try {
gscBrainstormAPI.setAuthTokenGetter(async () => {
try {
return await getToken();
} catch {
return null;
}
});
const result = await gscBrainstormAPI.brainstorm(keywords, siteUrl);
setBrainstormResult(result);
return result;
} catch (error) {
const message =
error instanceof Error ? error.message : 'Failed to brainstorm topics. Please try again.';
setBrainstormError(message);
return null;
} finally {
setIsBrainstorming(false);
stopProgressMessages();
}
},
[getToken],
);
const reset = useCallback(() => {
setBrainstormResult(null);
setBrainstormError(null);
setIsBrainstorming(false);
stopProgressMessages();
}, []);
return {
gscConnected,
gscSites,
isConnecting,
connectError,
isBrainstorming,
brainstormError,
brainstormResult,
contentOpportunities: brainstormResult?.content_opportunities ?? [],
keywordGaps: brainstormResult?.keyword_gaps ?? [],
quickWins: brainstormResult?.quick_wins ?? [],
pageOpportunities: brainstormResult?.page_opportunities ?? [],
aiRecommendations: brainstormResult?.ai_recommendations
&& Array.isArray(brainstormResult.ai_recommendations?.immediate_opportunities)
? (brainstormResult.ai_recommendations as AIRecommendations)
: null,
summary: brainstormResult?.summary
&& brainstormResult.summary.site_url
? (brainstormResult.summary as BrainstormSummary)
: null,
connectGSC,
brainstorm,
reset,
progressMessage,
};
};