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:
79
frontend/src/api/gscBrainstorm.ts
Normal file
79
frontend/src/api/gscBrainstorm.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { apiClient } from './client';
|
||||
|
||||
export interface ContentOpportunity {
|
||||
type: 'Content Optimization' | 'Content Enhancement';
|
||||
keyword: string;
|
||||
opportunity: string;
|
||||
potential_impact: 'High' | 'Medium';
|
||||
current_position: number;
|
||||
impressions: number;
|
||||
priority: 'High' | 'Medium';
|
||||
}
|
||||
|
||||
export interface KeywordGap {
|
||||
keyword: string;
|
||||
position: number;
|
||||
impressions: number;
|
||||
}
|
||||
|
||||
export interface AIRecommendations {
|
||||
immediate_opportunities: string[];
|
||||
content_strategy: string[];
|
||||
long_term_strategy: string[];
|
||||
}
|
||||
|
||||
export interface BrainstormSummary {
|
||||
site_url: string;
|
||||
date_range: { start: string; end: string };
|
||||
total_keywords_analyzed: number;
|
||||
total_impressions: number;
|
||||
total_clicks: number;
|
||||
avg_ctr: number;
|
||||
avg_position: number;
|
||||
keyword_distribution: {
|
||||
positions_1_3: number;
|
||||
positions_4_10: number;
|
||||
positions_11_20: number;
|
||||
positions_21_plus: number;
|
||||
};
|
||||
top_keywords: Array<{ keyword: string; impressions: number; position: number }>;
|
||||
top_pages: Array<{ page: string; clicks: number; impressions: number }>;
|
||||
}
|
||||
|
||||
export interface BrainstormResult {
|
||||
error?: string;
|
||||
content_opportunities: ContentOpportunity[];
|
||||
keyword_gaps: KeywordGap[];
|
||||
ai_recommendations: AIRecommendations | Record<string, never>;
|
||||
summary: BrainstormSummary | Record<string, never>;
|
||||
}
|
||||
|
||||
class GSCBrainstormAPI {
|
||||
private baseUrl = '/gsc';
|
||||
private getAuthToken: (() => Promise<string | null>) | null = null;
|
||||
|
||||
setAuthTokenGetter(getToken: () => Promise<string | null>) {
|
||||
this.getAuthToken = getToken;
|
||||
}
|
||||
|
||||
private async getAuthenticatedClient() {
|
||||
const token = this.getAuthToken ? await this.getAuthToken() : null;
|
||||
if (!token) {
|
||||
throw new Error('No authentication token available');
|
||||
}
|
||||
return apiClient.create({
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
}
|
||||
|
||||
async brainstorm(keywords: string, siteUrl?: string): Promise<BrainstormResult> {
|
||||
const client = await this.getAuthenticatedClient();
|
||||
const response = await client.post(`${this.baseUrl}/brainstorm`, {
|
||||
keywords,
|
||||
site_url: siteUrl || undefined,
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
|
||||
export const gscBrainstormAPI = new GSCBrainstormAPI();
|
||||
@@ -1,83 +0,0 @@
|
||||
/**
|
||||
* Wix API Client
|
||||
* Handles Wix connection status and operations
|
||||
*/
|
||||
|
||||
import { apiClient } from './client';
|
||||
|
||||
export interface WixStatus {
|
||||
connected: boolean;
|
||||
sites: Array<{
|
||||
id: string;
|
||||
blog_url: string;
|
||||
blog_id: string;
|
||||
created_at: string;
|
||||
scope: string;
|
||||
}>;
|
||||
total_sites: number;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
class WixAPI {
|
||||
private baseUrl = '/api/wix';
|
||||
private getAuthToken: (() => Promise<string | null>) | null = null;
|
||||
|
||||
/**
|
||||
* Set the auth token getter function
|
||||
*/
|
||||
setAuthTokenGetter(getToken: () => Promise<string | null>) {
|
||||
this.getAuthToken = getToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get authenticated API client with auth token
|
||||
*/
|
||||
private async getAuthenticatedClient() {
|
||||
const token = this.getAuthToken ? await this.getAuthToken() : null;
|
||||
|
||||
if (!token) {
|
||||
throw new Error('No authentication token available');
|
||||
}
|
||||
|
||||
return apiClient.create({
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Wix connection status
|
||||
*/
|
||||
async getStatus(): Promise<WixStatus> {
|
||||
try {
|
||||
const client = await this.getAuthenticatedClient();
|
||||
const response = await client.get(`${this.baseUrl}/status`);
|
||||
return response.data;
|
||||
} catch (error: any) {
|
||||
console.error('Wix API: Error getting status:', error);
|
||||
return {
|
||||
connected: false,
|
||||
sites: [],
|
||||
total_sites: 0,
|
||||
error: error.response?.data?.detail || error.message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Health check for Wix service
|
||||
*/
|
||||
async healthCheck(): Promise<boolean> {
|
||||
try {
|
||||
const client = await this.getAuthenticatedClient();
|
||||
await client.get(`${this.baseUrl}/connection/status`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Wix API: Health check failed:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const wixAPI = new WixAPI();
|
||||
Reference in New Issue
Block a user