feat: Brainstorm Topics with GSC + Issue #518 fixes + Blog Editor enhancements

Issue #518 - Subscription not updating after checkout:
- Fix stale closure in SubscriptionContext checkout polling (use subscriptionRef)
- Move checkout success polling from InitialRouteHandler into SubscriptionContext
- Remove redundant polling code from InitialRouteHandler
- Fix plan label: 'Free' instead of 'No Plan', proper capitalization
- Add plan refresh button in UserBadge
- Add 'View Costing Details' to UserBadge dropdown
- Rename 'ALwrity Podcast Maker' to 'Podcast Creator' across UI
- Clean subscription=success URL param after verification

Blog Writer WYSIWYG Editor enhancements:
- Per-section preview toggle (view/edit icons)
- Enhanced hover-based toolbar
- Circular SVG progress stats bar with detailed tooltip
- Research tool chips in stats bar footer
- Per-section TTS with useTextToSpeech hook (browser native)
- Full blog preview modal with print/PDF support
- PlayAllTTSButton: sequential playback with progress bar
- OnThisPageNav: floating sidebar with scroll tracking
- Section data attributes for scroll anchoring

GSC Brainstorm Topics feature:
- Backend: gsc_brainstorm_service.py (rule-based + LLM recommendations)
- Backend: POST /gsc/brainstorm endpoint with 3-word minimum validation
- Frontend: gscBrainstorm.ts API client
- Frontend: useGSCBrainstormConnection hook (popup OAuth, no /onboarding redirect)
- Frontend: useGSCBrainstorm hook (connect check + brainstorm call)
- Frontend: GSCBrainstormModal (3-tab results: Opportunities, Gaps, AI Recs)
- Frontend: BrainstormButton (visible at 3+ words, GSC connect overlay)
- Wire BrainstormButton into ManualResearchForm and ResearchAction
- Add blog_writer to gsc_auth router features for ALWRITY_ENABLED_FEATURES
This commit is contained in:
ajaysi
2026-05-20 22:34:37 +05:30
parent 68190dedb3
commit 644e72d289
98 changed files with 16137 additions and 2501 deletions

View File

@@ -1,5 +1,5 @@
import React, { useRef, useCallback, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useNavigate, useSearchParams } from 'react-router-dom';
import Dialog from '@mui/material/Dialog';
import DialogTitle from '@mui/material/DialogTitle';
import DialogContent from '@mui/material/DialogContent';
@@ -36,6 +36,8 @@ import { BlogWriterLandingSection } from './BlogWriterUtils/BlogWriterLandingSec
import { CopilotKitComponents } from './BlogWriterUtils/CopilotKitComponents';
const BlogWriter: React.FC = () => {
const [searchParams, setSearchParams] = useSearchParams();
// Add light theme class to body/html on mount, remove on unmount
React.useEffect(() => {
document.body.classList.add('blog-writer-page');
@@ -76,6 +78,7 @@ const BlogWriter: React.FC = () => {
flowAnalysisCompleted,
flowAnalysisResults,
sectionImages,
restoreAttempted,
setResearch,
setOutline,
setTitleOptions,
@@ -203,6 +206,21 @@ const BlogWriter: React.FC = () => {
// Store navigateToPhase in a ref for use in polling callbacks
const navigateToPhaseRef = React.useRef<((phase: string) => void) | null>(null);
// Normalize section keys to match outline IDs when updating from API responses
const handleSectionsUpdate = useCallback((newSections: Record<string, string>) => {
if (outline && outline.length > 0 && Object.keys(newSections).length > 0) {
const normalized: Record<string, string> = {};
const values = Object.values(newSections);
outline.forEach((s, idx) => {
const id = String(s.id);
normalized[id] = newSections[id] ?? values[idx] ?? '';
});
setSections(normalized);
} else {
setSections(newSections);
}
}, [outline, setSections]);
// Polling hooks - extracted to useBlogWriterPolling
const {
researchPolling,
@@ -216,7 +234,7 @@ const BlogWriter: React.FC = () => {
onResearchComplete: handleResearchComplete,
onOutlineComplete: handleOutlineComplete,
onOutlineError: handleOutlineError,
onSectionsUpdate: setSections,
onSectionsUpdate: handleSectionsUpdate,
onContentConfirmed: () => {
debug.log('[BlogWriter] Content generation completed - auto-confirming content');
setContentConfirmed(true);
@@ -328,6 +346,14 @@ const BlogWriter: React.FC = () => {
setContentConfirmed, setOutlineConfirmed, setSelectedTitle, setTitleOptions,
setCurrentPhase]);
// Handle ?new=true query param from "New Blog" button in Asset Library
React.useEffect(() => {
if (searchParams.get('new') === 'true') {
handleNewBlog();
setSearchParams({}, { replace: true });
}
}, [searchParams, handleNewBlog, setSearchParams]);
const handleMyBlogs = useCallback(() => {
navigate('/asset-library?source_module=blog_writer&asset_type=text');
}, [navigate]);
@@ -532,6 +558,7 @@ const BlogWriter: React.FC = () => {
currentPhase={currentPhase}
navigateToPhase={navigateToPhase}
onResearchComplete={handleResearchComplete}
restoreAttempted={restoreAttempted}
/>
{research && (
@@ -572,6 +599,8 @@ const BlogWriter: React.FC = () => {
setShowOutlineModal(true);
}}
onContentGenerationStart={handleMediumGenerationStarted}
buildFullMarkdown={buildFullMarkdown}
convertMarkdownToHTML={convertMarkdownToHTML}
/>
</>
)}