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, { useState, useEffect } from 'react';
import { Avatar, Box, Menu, MenuItem, Typography, Tooltip, Chip, Divider } from '@mui/material';
import { Avatar, Box, Menu, MenuItem, Typography, Tooltip, Chip, Divider, IconButton, CircularProgress } from '@mui/material';
import { useUser, useClerk } from '@clerk/clerk-react';
import { useSubscription } from '../../contexts/SubscriptionContext';
import SystemStatusIndicator from '../ContentPlanningDashboard/components/SystemStatusIndicator';
@@ -11,6 +11,7 @@ import {
logBackendCooldownSkipOnce,
} from '../../api/client';
import { saveNavigationState } from '../../utils/navigationState';
import { Refresh as RefreshIcon } from '@mui/icons-material';
interface UserBadgeProps {
colorMode?: 'light' | 'dark';
@@ -19,9 +20,10 @@ interface UserBadgeProps {
const UserBadge: React.FC<UserBadgeProps> = ({ colorMode = 'light' }) => {
const { user, isSignedIn } = useUser();
const { signOut } = useClerk();
const { subscription } = useSubscription();
const { subscription, refreshSubscription } = useSubscription();
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const [systemStatus, setSystemStatus] = useState<'healthy' | 'warning' | 'critical' | 'unknown'>('unknown');
const [isRefreshing, setIsRefreshing] = useState(false);
const open = Boolean(anchorEl);
const initials = React.useMemo(() => {
@@ -80,7 +82,8 @@ const UserBadge: React.FC<UserBadgeProps> = ({ colorMode = 'light' }) => {
// Get plan display info
const getPlanColor = () => {
switch (subscription?.plan) {
const plan = subscription?.plan?.toLowerCase() || 'free';
switch (plan) {
case 'free': return '#4caf50';
case 'basic': return '#2196f3';
case 'pro': return '#9c27b0';
@@ -90,13 +93,29 @@ const UserBadge: React.FC<UserBadgeProps> = ({ colorMode = 'light' }) => {
};
const getPlanLabel = () => {
if (!subscription?.active) return 'No Plan';
if (!subscription?.plan) return 'Free';
const plan = subscription.plan.toLowerCase();
if (plan === 'free') return 'Free';
if (plan === 'basic') return 'Basic';
if (plan === 'pro') return 'Pro';
if (plan === 'enterprise') return 'Enterprise';
return subscription.plan.charAt(0).toUpperCase() + subscription.plan.slice(1);
};
const handleOpen = (e: React.MouseEvent<HTMLElement>) => setAnchorEl(e.currentTarget);
const handleClose = () => setAnchorEl(null);
const handleRefreshPlan = async () => {
setIsRefreshing(true);
try {
await refreshSubscription();
} catch (err) {
console.error('Failed to refresh subscription:', err);
} finally {
setIsRefreshing(false);
}
};
const handleSignOut = async () => {
try {
await signOut();
@@ -121,7 +140,7 @@ const UserBadge: React.FC<UserBadgeProps> = ({ colorMode = 'light' }) => {
}}
/>
<Tooltip title={`${user?.fullName || user?.username || user?.primaryEmailAddress?.emailAddress || 'User'} - System: ${systemStatus.toUpperCase()}`}>
<Tooltip title="User Navigation Menu">
<Box sx={{ position: 'relative', display: 'inline-flex' }}>
<Avatar
onClick={handleOpen}
@@ -195,22 +214,37 @@ const UserBadge: React.FC<UserBadgeProps> = ({ colorMode = 'light' }) => {
</Box>
{/* Subscription Info */}
<Box sx={{ px: 2.5, py: 1.5, bgcolor: '#f8f9fb' }}>
<Typography variant="caption" sx={{ display: 'block', mb: 0.5, fontWeight: 600, color: '#6b7280', fontSize: '0.65rem', textTransform: 'uppercase', letterSpacing: '0.5px' }}>
Current Plan
</Typography>
<Chip
label={getPlanLabel()}
size="small"
sx={{
bgcolor: `${getPlanColor()}15`,
border: `1.5px solid ${getPlanColor()}40`,
color: getPlanColor(),
fontWeight: 700,
fontSize: '0.75rem',
height: 26,
}}
/>
<Box sx={{ px: 2.5, py: 1.5, bgcolor: '#f8f9fb', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<Box>
<Typography variant="caption" sx={{ display: 'block', mb: 0.5, fontWeight: 600, color: '#6b7280', fontSize: '0.65rem', textTransform: 'uppercase', letterSpacing: '0.5px' }}>
Current Plan
</Typography>
<Chip
label={getPlanLabel()}
size="small"
sx={{
bgcolor: `${getPlanColor()}15`,
border: `1.5px solid ${getPlanColor()}40`,
color: getPlanColor(),
fontWeight: 700,
fontSize: '0.75rem',
height: 26,
}}
/>
</Box>
<Tooltip title="Refresh subscription status">
<IconButton
onClick={handleRefreshPlan}
size="small"
disabled={isRefreshing}
sx={{
color: '#6b7280',
'&:hover': { bgcolor: '#e5e7eb' },
}}
>
{isRefreshing ? <CircularProgress size={16} /> : <RefreshIcon fontSize="small" />}
</IconButton>
</Tooltip>
</Box>
<Divider sx={{ mx: 2 }} />
@@ -258,6 +292,9 @@ const UserBadge: React.FC<UserBadgeProps> = ({ colorMode = 'light' }) => {
<MenuItem onClick={() => { handleClose(); saveNavigationState(window.location.pathname); window.location.href = '/pricing'; }} sx={{ mx: 1, borderRadius: 1, color: '#374151', '&:hover': { bgcolor: '#f3f4f6' } }}>
Manage Subscription
</MenuItem>
<MenuItem onClick={() => { handleClose(); window.location.href = '/billing'; }} sx={{ mx: 1, borderRadius: 1, color: '#374151', '&:hover': { bgcolor: '#f3f4f6' } }}>
View Costing Details
</MenuItem>
<MenuItem onClick={handleSignOut} sx={{ mx: 1, borderRadius: 1, color: '#6b7280', '&:hover': { bgcolor: '#fef2f2', color: '#ef4444' } }}>
Sign out
</MenuItem>