ALwrity Version 0.5.0 (Fastapi + React )

This commit is contained in:
ajaysi
2025-08-06 12:48:02 +05:30
parent f28a919caa
commit 32f97fa6b3
476 changed files with 115544 additions and 28747 deletions

View File

@@ -0,0 +1,167 @@
# Zustand Store Implementation
This directory contains Zustand stores for managing state across the Alwrity dashboard components.
## Overview
Zustand has been implemented to replace the previous state management approach using custom hooks and local state. This provides:
- **Centralized state management** across components
- **Automatic persistence** with Zustand's persist middleware
- **Better performance** with selective re-renders
- **Simpler state updates** with immer-like syntax
- **Better debugging** with Redux DevTools support
## Stores
### 1. `dashboardStore.ts` - Main Dashboard Store
Manages state for the main dashboard including:
- Search and filter state
- Favorites management
- Snackbar notifications
- Loading and error states
**Key Features:**
- Automatic persistence of favorites and filter preferences
- Snackbar management with automatic hiding
- Optimized re-renders with selective state subscriptions
**Usage:**
```typescript
import { useDashboardStore } from '../stores/dashboardStore';
const {
loading,
error,
searchQuery,
favorites,
toggleFavorite,
setSearchQuery,
showSnackbar,
} = useDashboardStore();
```
### 2. `seoDashboardStore.ts` - SEO Dashboard Store
Manages state for the SEO dashboard including:
- Dashboard data fetching and caching
- Loading and error states
- Data refresh functionality
**Key Features:**
- Automatic data fetching on component mount
- Error handling with retry functionality
- Data caching with last updated timestamp
- DevTools integration for debugging
**Usage:**
```typescript
import { useSEODashboardStore } from '../stores/seoDashboardStore';
const {
loading,
error,
data,
fetchDashboardData,
refreshData,
} = useSEODashboardStore();
```
### 3. `sharedDashboardStore.ts` - Shared Dashboard Store
Manages common functionality across all dashboards:
- Sidebar state
- Theme management
- Global notifications
**Key Features:**
- Theme switching with system preference detection
- Notification management with auto-cleanup
- Sidebar state management
**Usage:**
```typescript
import { useSharedDashboardStore } from '../stores/sharedDashboardStore';
const {
isSidebarOpen,
currentTheme,
notifications,
toggleSidebar,
setTheme,
addNotification,
} = useSharedDashboardStore();
```
## Benefits Over Previous Implementation
### Before (Custom Hooks + Local State)
```typescript
// MainDashboard - Custom hook with manual localStorage
const useDashboardState = () => {
const [state, setState] = useState<DashboardState>({...});
// Manual localStorage handling
// Complex state updates
// No cross-component communication
};
// SEODashboard - Local state
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [data, setData] = useState<SEODashboardData | null>(null);
```
### After (Zustand Stores)
```typescript
// Centralized, persistent, and optimized
const {
loading,
error,
data,
fetchDashboardData,
} = useSEODashboardStore();
// Automatic persistence
const {
favorites,
searchQuery,
toggleFavorite,
} = useDashboardStore();
```
## Performance Improvements
1. **Selective Re-renders**: Components only re-render when their specific state changes
2. **Automatic Persistence**: No manual localStorage management needed
3. **Optimized Updates**: Zustand's internal optimizations reduce unnecessary renders
4. **DevTools Integration**: Better debugging and state inspection
## Migration Notes
- The old `useDashboardState` hook can be removed after confirming the new implementation works correctly
- All localStorage operations are now handled automatically by Zustand's persist middleware
- Error handling is more robust with centralized error states
- Snackbar management is simplified with automatic cleanup
## Future Enhancements
1. **Real-time Updates**: Can easily add WebSocket integration for live data updates
2. **Offline Support**: Zustand's persistence can be extended for offline functionality
3. **State Synchronization**: Multiple tabs can share state through storage events
4. **Advanced Caching**: Can implement more sophisticated caching strategies
## Testing
The stores can be tested independently:
```typescript
import { renderHook, act } from '@testing-library/react';
import { useDashboardStore } from './dashboardStore';
test('should toggle favorite', () => {
const { result } = renderHook(() => useDashboardStore());
act(() => {
result.current.toggleFavorite('test-tool');
});
expect(result.current.favorites).toContain('test-tool');
});
```

View File

@@ -0,0 +1,680 @@
import { create } from 'zustand';
import { contentPlanningApi } from '../services/contentPlanningApi';
// Types
export interface ContentStrategy {
id: string;
name: string;
description: string;
industry: string;
target_audience: string;
content_pillars: string[];
created_at: string;
updated_at: string;
user_id?: number;
}
export interface CalendarEvent {
id: string;
title: string;
description: string;
date: string;
scheduled_date?: string;
platform: string;
content_type: string;
status: 'draft' | 'scheduled' | 'published';
strategy_id?: string;
user_id?: number;
created_at: string;
updated_at: string;
}
export interface ContentGapAnalysis {
id: string;
website_url: string;
competitors: string[];
keywords: string[];
gaps: string[];
recommendations: AIRecommendation[];
created_at: string;
user_id?: number;
}
export interface AIRecommendation {
id: string;
type: 'strategy' | 'topic' | 'timing' | 'platform' | 'optimization';
title: string;
description: string;
confidence: number;
reasoning: string;
action_items: string[];
status: 'pending' | 'accepted' | 'rejected' | 'modified';
}
export interface AIInsight {
id: string;
type: 'performance' | 'opportunity' | 'warning' | 'trend';
title: string;
description: string;
priority: 'low' | 'medium' | 'high';
created_at: string;
}
export interface PerformanceMetrics {
engagement: number;
reach: number;
conversion: number;
roi: number;
time_range: string;
}
// New Calendar Generation Types
export interface GeneratedCalendar {
user_id: number;
strategy_id?: number;
calendar_type: string;
industry: string;
business_size: string;
generated_at: string;
content_pillars: string[];
platform_strategies: any;
content_mix: Record<string, number>;
daily_schedule: any[];
weekly_themes: any[];
content_recommendations: any[];
optimal_timing: any;
performance_predictions: any;
trending_topics: any[];
repurposing_opportunities: any[];
ai_insights: any[];
competitor_analysis: any;
gap_analysis_insights: any;
strategy_insights: any;
onboarding_insights: any;
processing_time: number;
ai_confidence: number;
}
export interface ContentOptimization {
user_id: number;
event_id?: number;
original_content: any;
optimized_content: any;
platform_adaptations: string[];
visual_recommendations: string[];
hashtag_suggestions: string[];
keyword_optimization: any;
tone_adjustments: any;
length_optimization: any;
performance_prediction: any;
optimization_score: number;
recommendations?: any[];
created_at: string;
}
export interface PerformancePrediction {
user_id: number;
strategy_id?: number;
content_type: string;
platform: string;
predicted_engagement_rate: number;
predicted_reach: number;
predicted_conversions: number;
predicted_roi: number;
confidence_score: number;
recommendations: string[];
created_at: string;
}
export interface ContentRepurposing {
user_id: number;
strategy_id?: number;
original_content: any;
platform_adaptations: any[];
transformations: any[];
implementation_tips: string[];
gap_addresses: string[];
created_at: string;
}
export interface TrendingTopics {
user_id: number;
industry: string;
trending_topics: any[];
gap_relevance_scores: Record<string, number>;
audience_alignment_scores: Record<string, number>;
created_at: string;
}
// Store interface
interface ContentPlanningStore {
// State
strategies: ContentStrategy[];
currentStrategy: ContentStrategy | null;
calendarEvents: CalendarEvent[];
gapAnalyses: ContentGapAnalysis[];
aiRecommendations: AIRecommendation[];
aiInsights: AIInsight[];
performanceMetrics: PerformanceMetrics | null;
// New Calendar Generation State
generatedCalendar: GeneratedCalendar | null;
contentOptimization: ContentOptimization | null;
performancePrediction: PerformancePrediction | null;
contentRepurposing: ContentRepurposing | null;
trendingTopics: TrendingTopics | null;
calendarGenerationLoading: boolean;
calendarGenerationError: string | null;
// UI state
loading: boolean;
error: string | null;
activeTab: 'strategy' | 'calendar' | 'analytics' | 'gaps';
dataLoading: boolean;
// Actions
setLoading: (loading: boolean) => void;
setError: (error: string | null) => void;
setActiveTab: (tab: 'strategy' | 'calendar' | 'analytics' | 'gaps') => void;
// Strategy actions
createStrategy: (strategy: Omit<ContentStrategy, 'id' | 'created_at' | 'updated_at'>) => Promise<void>;
updateStrategy: (id: string, updates: Partial<ContentStrategy>) => Promise<void>;
deleteStrategy: (id: string) => Promise<void>;
setCurrentStrategy: (strategy: ContentStrategy | null) => void;
// Calendar actions
createEvent: (event: Omit<CalendarEvent, 'id' | 'created_at' | 'updated_at'>) => Promise<void>;
updateEvent: (id: string, updates: Partial<CalendarEvent>) => Promise<void>;
deleteEvent: (id: string) => Promise<void>;
// Gap analysis actions
createGapAnalysis: (analysis: Omit<ContentGapAnalysis, 'id' | 'created_at'>) => Promise<void>;
updateGapAnalysis: (id: string, updates: Partial<ContentGapAnalysis>) => Promise<void>;
analyzeContentGaps: (params: { website_url: string; competitors: string[]; keywords: string[] }) => Promise<void>;
// AI actions
addAIRecommendation: (recommendation: AIRecommendation) => void;
updateAIRecommendation: (id: string, status: AIRecommendation['status']) => void;
addAIInsight: (insight: AIInsight) => void;
// Analytics actions
setPerformanceMetrics: (metrics: PerformanceMetrics) => void;
// Load data
loadStrategies: () => Promise<void>;
loadCalendarEvents: () => Promise<void>;
loadGapAnalyses: () => Promise<void>;
loadAIInsights: () => Promise<void>;
loadAIRecommendations: () => Promise<void>;
// Update data (for orchestrator)
updateStrategies: (strategies: ContentStrategy[]) => void;
updateCalendarEvents: (events: CalendarEvent[]) => void;
updateGapAnalyses: (analyses: ContentGapAnalysis[]) => void;
updateAIInsights: (data: { insights: AIInsight[]; recommendations: AIRecommendation[] }) => void;
// Health checks
checkHealth: () => Promise<boolean>;
checkDatabaseHealth: () => Promise<boolean>;
// New Calendar Generation Actions
generateCalendar: (request: {
user_id: number;
strategy_id?: number;
calendar_type: string;
industry?: string;
business_size: string;
force_refresh?: boolean;
}) => Promise<void>;
optimizeContent: (request: {
user_id: number;
event_id?: number;
title: string;
description: string;
content_type: string;
target_platform: string;
original_content?: any;
}) => Promise<void>;
predictPerformance: (request: {
user_id: number;
strategy_id?: number;
content_type: string;
platform: string;
content_data: any;
}) => Promise<void>;
repurposeContent: (request: {
user_id: number;
strategy_id?: number;
original_content: any;
target_platforms: string[];
}) => Promise<void>;
getTrendingTopics: (request: {
user_id: number;
industry: string;
limit?: number;
}) => Promise<void>;
setCalendarGenerationLoading: (loading: boolean) => void;
setCalendarGenerationError: (error: string | null) => void;
clearCalendarGenerationData: () => void;
}
// Store implementation
export const useContentPlanningStore = create<ContentPlanningStore>((set, get) => ({
// Initial state
strategies: [],
currentStrategy: null,
calendarEvents: [],
gapAnalyses: [],
aiRecommendations: [],
aiInsights: [],
performanceMetrics: null,
// New Calendar Generation State
generatedCalendar: null,
contentOptimization: null,
performancePrediction: null,
contentRepurposing: null,
trendingTopics: null,
calendarGenerationLoading: false,
calendarGenerationError: null,
loading: false,
error: null,
activeTab: 'strategy',
dataLoading: false,
// UI actions
setLoading: (loading) => set({ loading }),
setError: (error) => set({ error }),
setActiveTab: (activeTab) => set({ activeTab }),
// Strategy actions
createStrategy: async (strategy) => {
set({ loading: true, error: null });
try {
const newStrategy = await contentPlanningApi.createStrategySafe({
name: strategy.name,
description: strategy.description,
industry: strategy.industry,
target_audience: strategy.target_audience,
content_pillars: strategy.content_pillars,
user_id: strategy.user_id
});
set((state) => ({
strategies: [...state.strategies, newStrategy],
loading: false,
}));
} catch (error: any) {
set({ error: error.message || 'Failed to create strategy', loading: false });
}
},
updateStrategy: async (id, updates) => {
set({ loading: true, error: null });
try {
const updatedStrategy = await contentPlanningApi.updateStrategy(id, updates);
set((state) => ({
strategies: state.strategies.map((strategy) =>
strategy.id === id ? updatedStrategy : strategy
),
loading: false,
}));
} catch (error: any) {
set({ error: error.message || 'Failed to update strategy', loading: false });
}
},
deleteStrategy: async (id) => {
set({ loading: true, error: null });
try {
await contentPlanningApi.deleteStrategy(id);
set((state) => ({
strategies: state.strategies.filter((strategy) => strategy.id !== id),
loading: false,
}));
} catch (error: any) {
set({ error: error.message || 'Failed to delete strategy', loading: false });
}
},
setCurrentStrategy: (strategy) => set({ currentStrategy: strategy }),
// Calendar actions
createEvent: async (event) => {
set({ loading: true, error: null });
try {
const newEvent = await contentPlanningApi.createEventSafe({
title: event.title,
description: event.description,
date: event.date,
platform: event.platform,
content_type: event.content_type,
status: event.status,
strategy_id: event.strategy_id,
user_id: event.user_id
});
set((state) => ({
calendarEvents: [...state.calendarEvents, newEvent],
loading: false,
}));
} catch (error: any) {
set({ error: error.message || 'Failed to create event', loading: false });
}
},
updateEvent: async (id, updates) => {
set({ loading: true, error: null });
try {
const updatedEvent = await contentPlanningApi.updateEvent(id, updates);
set((state) => ({
calendarEvents: state.calendarEvents.map((event) =>
event.id === id ? updatedEvent : event
),
loading: false,
}));
} catch (error: any) {
set({ error: error.message || 'Failed to update event', loading: false });
}
},
deleteEvent: async (id) => {
set({ loading: true, error: null });
try {
await contentPlanningApi.deleteEvent(id);
set((state) => ({
calendarEvents: state.calendarEvents.filter((event) => event.id !== id),
loading: false,
}));
} catch (error: any) {
set({ error: error.message || 'Failed to delete event', loading: false });
}
},
// Gap analysis actions
createGapAnalysis: async (analysis) => {
set({ loading: true, error: null });
try {
const newAnalysis = await contentPlanningApi.createGapAnalysisSafe({
website_url: analysis.website_url,
competitors: analysis.competitors,
keywords: analysis.keywords,
user_id: analysis.user_id
});
set((state) => ({
gapAnalyses: [...state.gapAnalyses, newAnalysis],
loading: false,
}));
} catch (error: any) {
set({ error: error.message || 'Failed to create gap analysis', loading: false });
}
},
updateGapAnalysis: async (id, updates) => {
set({ loading: true, error: null });
try {
const updatedAnalysis = await contentPlanningApi.updateGapAnalysis(id, updates);
set((state) => ({
gapAnalyses: state.gapAnalyses.map((analysis) =>
analysis.id === id ? updatedAnalysis : analysis
),
loading: false,
}));
} catch (error: any) {
set({ error: error.message || 'Failed to update gap analysis', loading: false });
}
},
analyzeContentGaps: async (params) => {
set({ loading: true, error: null });
try {
const analysisResult = await contentPlanningApi.analyzeContentGapsSafe(params);
// Add the analysis result to the store
set((state) => ({
gapAnalyses: [...state.gapAnalyses, analysisResult],
loading: false,
}));
} catch (error: any) {
set({ error: error.message || 'Failed to analyze content gaps', loading: false });
}
},
// AI actions
addAIRecommendation: (recommendation) => {
set((state) => ({
aiRecommendations: [...state.aiRecommendations, recommendation],
}));
},
updateAIRecommendation: (id, status) => {
set((state) => ({
aiRecommendations: state.aiRecommendations.map((rec) =>
rec.id === id ? { ...rec, status } : rec
),
}));
},
addAIInsight: (insight) => {
set((state) => ({
aiInsights: [...state.aiInsights, insight],
}));
},
// Analytics actions
setPerformanceMetrics: (metrics) => set({ performanceMetrics: metrics }),
// Load data actions
loadStrategies: async () => {
set({ loading: true, error: null });
try {
const strategies = await contentPlanningApi.getStrategiesSafe();
set({ strategies, loading: false });
} catch (error: any) {
set({ error: error.message || 'Failed to load strategies', loading: false });
}
},
loadCalendarEvents: async () => {
set({ loading: true, error: null });
try {
const events = await contentPlanningApi.getEventsSafe();
set({ calendarEvents: events, loading: false });
} catch (error: any) {
set({ error: error.message || 'Failed to load calendar events', loading: false });
}
},
loadGapAnalyses: async () => {
set({ loading: true, error: null });
try {
const analyses = await contentPlanningApi.getGapAnalyses();
set({ gapAnalyses: analyses, loading: false });
} catch (error: any) {
set({ error: error.message || 'Failed to load gap analyses', loading: false });
}
},
loadAIInsights: async () => {
set({ loading: true, error: null });
try {
const response = await contentPlanningApi.getAIAnalyticsSafe();
// Validate response structure
if (!response || typeof response !== 'object') {
console.warn('Invalid AI analytics response:', response);
set({ aiInsights: [], loading: false });
return;
}
// Handle the response structure - it returns an object with insights array
const insights = Array.isArray(response.insights) ? response.insights : [];
// If no insights from backend, create some default insights from recommendations
let transformedInsights = insights;
if (insights.length === 0 && response.recommendations && Array.isArray(response.recommendations)) {
transformedInsights = response.recommendations.slice(0, 3).map((rec: any, index: number) => ({
id: `insight_${Date.now()}_${index}`,
type: 'opportunity',
title: rec.title || 'AI Insight',
description: rec.description || 'AI-generated insight',
priority: rec.priority === 'High' ? 'high' : rec.priority === 'Medium' ? 'medium' : 'low',
created_at: new Date().toISOString()
}));
} else {
// Transform insights data to the expected format
transformedInsights = insights.map((insight: any) => ({
id: insight.id || `insight_${Date.now()}`,
type: insight.type || 'performance',
title: insight.title || 'AI Insight',
description: insight.description || 'AI-generated insight',
priority: insight.priority || 'medium',
created_at: insight.created_at || new Date().toISOString()
}));
}
set({ aiInsights: transformedInsights, loading: false });
} catch (error: any) {
console.error('Error loading AI insights:', error);
set({ error: error.message || 'Failed to load AI insights', loading: false, aiInsights: [] });
}
},
loadAIRecommendations: async () => {
set({ loading: true, error: null });
try {
const response = await contentPlanningApi.getAIAnalyticsSafe();
// Validate response structure
if (!response || typeof response !== 'object') {
console.warn('Invalid AI analytics response:', response);
set({ aiRecommendations: [], loading: false });
return;
}
// Handle the response structure - it returns an object with recommendations array
const recommendations = Array.isArray(response.recommendations) ? response.recommendations : [];
// Transform recommendations data to the expected format
const transformedRecommendations = recommendations.map((rec: any, index: number) => ({
id: rec.id || `rec_${Date.now()}_${index}`,
type: rec.type?.toLowerCase() || 'strategy',
title: rec.title || 'AI Recommendation',
description: rec.description || 'AI-generated recommendation',
confidence: rec.ai_confidence || rec.confidence || 0.8,
reasoning: rec.reasoning || rec.description || 'Generated by AI analysis',
action_items: Array.isArray(rec.content_suggestions) ? rec.content_suggestions : [],
status: rec.status || 'pending'
}));
set({ aiRecommendations: transformedRecommendations, loading: false });
} catch (error: any) {
console.error('Error loading AI recommendations:', error);
set({ error: error.message || 'Failed to load AI recommendations', loading: false, aiRecommendations: [] });
}
},
// Update data (for orchestrator)
updateStrategies: (strategies: ContentStrategy[]) => {
set({ strategies });
},
updateCalendarEvents: (events: CalendarEvent[]) => {
set({ calendarEvents: events });
},
updateGapAnalyses: (analyses: ContentGapAnalysis[]) => {
set({ gapAnalyses: analyses });
},
updateAIInsights: (data: { insights: AIInsight[]; recommendations: AIRecommendation[] }) => {
set({
aiInsights: data.insights,
aiRecommendations: data.recommendations
});
},
// Health checks
checkHealth: async () => {
try {
const health = await contentPlanningApi.checkHealth();
return health.status === 'healthy';
} catch (error) {
console.error('Health check failed:', error);
return false;
}
},
checkDatabaseHealth: async () => {
try {
const dbHealth = await contentPlanningApi.checkDatabaseHealth();
return dbHealth.status === 'healthy';
} catch (error) {
console.error('Database health check failed:', error);
return false;
}
},
// New Calendar Generation Actions
generateCalendar: async (request) => {
set({ calendarGenerationLoading: true, calendarGenerationError: null });
try {
const generatedCalendar = await contentPlanningApi.generateCalendar(request);
set({ generatedCalendar, calendarGenerationLoading: false });
} catch (error: any) {
set({ calendarGenerationError: error.message || 'Failed to generate calendar', calendarGenerationLoading: false });
}
},
optimizeContent: async (request) => {
set({ loading: true, error: null });
try {
const optimizedContent = await contentPlanningApi.optimizeContent(request);
set({ contentOptimization: optimizedContent, loading: false });
} catch (error: any) {
set({ error: error.message || 'Failed to optimize content', loading: false });
}
},
predictPerformance: async (request) => {
set({ loading: true, error: null });
try {
const performancePrediction = await contentPlanningApi.predictPerformance(request);
set({ performancePrediction, loading: false });
} catch (error: any) {
set({ error: error.message || 'Failed to predict performance', loading: false });
}
},
repurposeContent: async (request) => {
set({ loading: true, error: null });
try {
const contentRepurposing = await contentPlanningApi.repurposeContent(request);
set({ contentRepurposing, loading: false });
} catch (error: any) {
set({ error: error.message || 'Failed to repurpose content', loading: false });
}
},
getTrendingTopics: async (request) => {
set({ loading: true, error: null });
try {
const trendingTopics = await contentPlanningApi.getTrendingTopics(request);
set({ trendingTopics, loading: false });
} catch (error: any) {
set({ error: error.message || 'Failed to get trending topics', loading: false });
}
},
setCalendarGenerationLoading: (loading) => set({ calendarGenerationLoading: loading }),
setCalendarGenerationError: (error) => set({ calendarGenerationError: error }),
clearCalendarGenerationData: () => set({ generatedCalendar: null, contentOptimization: null, performancePrediction: null, contentRepurposing: null, trendingTopics: null }),
}));

View File

@@ -0,0 +1,110 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { DashboardState, SnackbarState } from '../components/shared/types';
export interface DashboardStore extends DashboardState {
// Actions
toggleFavorite: (toolName: string) => void;
setSearchQuery: (query: string) => void;
setSelectedCategory: (category: string | null) => void;
setSelectedSubCategory: (subCategory: string | null) => void;
setError: (error: string | null) => void;
setLoading: (loading: boolean) => void;
showSnackbar: (message: string, severity?: SnackbarState['severity']) => void;
hideSnackbar: () => void;
clearFilters: () => void;
}
export const useDashboardStore = create<DashboardStore>()(
persist(
(set, get) => ({
// Initial state
loading: false,
error: null,
searchQuery: '',
selectedCategory: null,
selectedSubCategory: null,
favorites: [],
snackbar: {
open: false,
message: '',
severity: 'info',
},
// Actions
toggleFavorite: (toolName: string) => {
const { favorites } = get();
const newFavorites = favorites.includes(toolName)
? favorites.filter(f => f !== toolName)
: [...favorites, toolName];
set({ favorites: newFavorites });
// Show snackbar
const message = favorites.includes(toolName)
? 'Removed from favorites'
: 'Added to favorites';
get().showSnackbar(message, 'success');
},
setSearchQuery: (query: string) => {
set({ searchQuery: query });
},
setSelectedCategory: (category: string | null) => {
set({
selectedCategory: category,
selectedSubCategory: null, // Reset sub-category when changing main category
});
},
setSelectedSubCategory: (subCategory: string | null) => {
set({ selectedSubCategory: subCategory });
},
setError: (error: string | null) => {
set({ error });
},
setLoading: (loading: boolean) => {
set({ loading });
},
showSnackbar: (message: string, severity: SnackbarState['severity'] = 'info') => {
set({
snackbar: {
open: true,
message,
severity,
},
});
},
hideSnackbar: () => {
set({
snackbar: {
...get().snackbar,
open: false,
},
});
},
clearFilters: () => {
set({
searchQuery: '',
selectedCategory: null,
selectedSubCategory: null,
});
},
}),
{
name: 'alwrity-dashboard-storage',
partialize: (state) => ({
favorites: state.favorites,
searchQuery: state.searchQuery,
selectedCategory: state.selectedCategory,
selectedSubCategory: state.selectedSubCategory,
}),
}
)
);

View File

@@ -0,0 +1,944 @@
import { create } from 'zustand';
import { contentPlanningApi } from '../services/contentPlanningApi';
// Enhanced Strategy Types
export interface EnhancedStrategy {
id: string;
user_id: number;
name: string;
industry: string;
// Business Context (8 inputs)
business_objectives?: any;
target_metrics?: any;
content_budget?: number;
team_size?: number;
implementation_timeline?: string;
market_share?: string;
competitive_position?: string;
performance_metrics?: any;
// Audience Intelligence (6 inputs)
content_preferences?: any;
consumption_patterns?: any;
audience_pain_points?: any;
buying_journey?: any;
seasonal_trends?: any;
engagement_metrics?: any;
// Competitive Intelligence (5 inputs)
top_competitors?: any;
competitor_content_strategies?: any;
market_gaps?: any;
industry_trends?: any;
emerging_trends?: any;
// Content Strategy (7 inputs)
preferred_formats?: any;
content_mix?: any;
content_frequency?: string;
optimal_timing?: any;
quality_metrics?: any;
editorial_guidelines?: any;
brand_voice?: any;
// Performance & Analytics (4 inputs)
traffic_sources?: any;
conversion_rates?: any;
content_roi_targets?: any;
ab_testing_capabilities?: boolean;
// Enhanced AI Analysis
comprehensive_ai_analysis?: any;
onboarding_data_used?: any;
strategic_scores?: any;
market_positioning?: any;
competitive_advantages?: any;
strategic_risks?: any;
opportunity_analysis?: any;
// Metadata
created_at: string;
updated_at: string;
completion_percentage: number;
data_source_transparency?: any;
}
export interface EnhancedAIAnalysis {
id: string;
user_id: number;
strategy_id: string;
analysis_type: string;
comprehensive_insights?: any;
audience_intelligence?: any;
competitive_intelligence?: any;
performance_optimization?: any;
content_calendar_optimization?: any;
onboarding_data_used?: any;
data_confidence_scores?: any;
recommendation_quality_scores?: any;
processing_time?: number;
ai_service_status: string;
prompt_version?: string;
created_at: string;
updated_at: string;
}
export interface OnboardingIntegration {
id: string;
user_id: number;
strategy_id: string;
website_analysis_data?: any;
research_preferences_data?: any;
api_keys_data?: any;
field_mappings?: any;
auto_populated_fields?: any;
user_overrides?: any;
data_quality_scores?: any;
confidence_levels?: any;
data_freshness?: any;
created_at: string;
updated_at: string;
}
export interface StrategicInputField {
id: string;
category: string;
label: string;
description: string;
tooltip: string;
type: 'text' | 'number' | 'select' | 'multiselect' | 'json' | 'boolean';
required: boolean;
options?: string[];
placeholder?: string;
validation?: any;
auto_populated?: boolean;
data_source?: string;
confidence_level?: number;
}
export interface ProgressiveDisclosureStep {
id: string;
title: string;
description: string;
fields: string[];
is_complete: boolean;
is_visible: boolean;
dependencies: string[];
}
export interface TooltipData {
field_id: string;
title: string;
description: string;
examples: string[];
best_practices: string[];
data_source?: string;
confidence_level?: number;
}
// Store interface
interface EnhancedStrategyStore {
// State
strategies: EnhancedStrategy[];
currentStrategy: EnhancedStrategy | null;
aiAnalyses: EnhancedAIAnalysis[];
onboardingIntegrations: OnboardingIntegration[];
// Progressive Disclosure
disclosureSteps: ProgressiveDisclosureStep[];
currentStep: number;
completedSteps: string[];
// Tooltips
tooltips: Record<string, TooltipData>;
// Form State
formData: Record<string, any>;
formErrors: Record<string, string>;
autoPopulatedFields: Record<string, any>;
dataSources: Record<string, string>;
// UI State
loading: boolean;
error: string | null;
saving: boolean;
aiGenerating: boolean;
// Actions
setLoading: (loading: boolean) => void;
setError: (error: string | null) => void;
setSaving: (saving: boolean) => void;
setAIGenerating: (generating: boolean) => void;
// Strategy actions
createEnhancedStrategy: (strategy: Partial<EnhancedStrategy>) => Promise<EnhancedStrategy>;
updateEnhancedStrategy: (id: string, updates: Partial<EnhancedStrategy>) => Promise<void>;
deleteEnhancedStrategy: (id: string) => Promise<void>;
setCurrentStrategy: (strategy: EnhancedStrategy | null) => void;
// Form actions
updateFormField: (fieldId: string, value: any) => void;
validateFormField: (fieldId: string) => boolean;
validateAllFields: () => boolean;
resetForm: () => void;
// Progressive disclosure actions
setCurrentStep: (step: number) => void;
completeStep: (stepId: string) => void;
canProceedToStep: (stepId: string) => boolean;
getNextStep: () => ProgressiveDisclosureStep | null;
getPreviousStep: () => ProgressiveDisclosureStep | null;
// Auto-population actions
autoPopulateFromOnboarding: () => Promise<void>;
updateAutoPopulatedField: (fieldId: string, value: any, source: string) => void;
overrideAutoPopulatedField: (fieldId: string, value: any) => void;
// AI Analysis actions
generateAIRecommendations: (strategyId: string) => Promise<void>;
regenerateAIAnalysis: (strategyId: string, analysisType: string) => Promise<void>;
// Data loading
loadEnhancedStrategies: () => Promise<void>;
loadAIAnalyses: (strategyId: string) => Promise<void>;
loadOnboardingIntegration: (strategyId: string) => Promise<void>;
// Tooltip actions
getTooltipData: (fieldId: string) => TooltipData | null;
updateTooltipData: (fieldId: string, data: TooltipData) => void;
// Completion tracking
calculateCompletionPercentage: () => number;
getCompletionStats: () => {
total_fields: number;
filled_fields: number;
completion_percentage: number;
category_completion: Record<string, number>;
};
}
// Strategic input fields configuration
export const STRATEGIC_INPUT_FIELDS: StrategicInputField[] = [
// Business Context
{
id: 'business_objectives',
category: 'business_context',
label: 'Business Objectives',
description: 'Primary and secondary business goals for content strategy',
tooltip: 'Define your main business goals that content will support. Include both primary objectives (e.g., brand awareness) and secondary objectives (e.g., lead generation).',
type: 'json',
required: true,
placeholder: 'Enter your business objectives'
},
{
id: 'target_metrics',
category: 'business_context',
label: 'Target Metrics',
description: 'KPIs and success metrics for content performance',
tooltip: 'Specify the key performance indicators (KPIs) that will measure the success of your content strategy. Include metrics like traffic growth, engagement rates, and conversion rates.',
type: 'json',
required: true,
placeholder: 'Define your target metrics'
},
{
id: 'content_budget',
category: 'business_context',
label: 'Content Budget',
description: 'Monthly or annual budget for content creation',
tooltip: 'Set your content marketing budget. This helps determine the scope and scale of your content strategy, including team size, tools, and content production capabilities.',
type: 'number',
required: false,
placeholder: 'Enter your content budget'
},
{
id: 'team_size',
category: 'business_context',
label: 'Team Size',
description: 'Number of people working on content',
tooltip: 'Specify the size of your content team. This affects content production capacity and helps determine realistic content frequency and volume.',
type: 'number',
required: false,
placeholder: 'Enter team size'
},
{
id: 'implementation_timeline',
category: 'business_context',
label: 'Implementation Timeline',
description: 'Timeline for strategy implementation',
tooltip: 'Define how long you plan to implement this content strategy. Common timelines include 3 months, 6 months, or 1 year.',
type: 'select',
required: false,
options: ['3 months', '6 months', '1 year', '2 years', 'Ongoing']
},
{
id: 'market_share',
category: 'business_context',
label: 'Market Share',
description: 'Current market share percentage',
tooltip: 'Your current market share helps determine your competitive position and content strategy approach. Leaders focus on thought leadership, while challengers focus on differentiation.',
type: 'text',
required: false,
placeholder: 'Enter market share percentage'
},
{
id: 'competitive_position',
category: 'business_context',
label: 'Competitive Position',
description: 'Market position relative to competitors',
tooltip: 'Define your competitive position in the market. Options include Leader, Challenger, Niche, or Emerging. This influences your content strategy approach.',
type: 'select',
required: false,
options: ['Leader', 'Challenger', 'Niche', 'Emerging']
},
{
id: 'performance_metrics',
category: 'business_context',
label: 'Current Performance Metrics',
description: 'Existing performance data and benchmarks',
tooltip: 'Provide your current content performance metrics as a baseline. This helps measure improvement and set realistic targets.',
type: 'json',
required: false,
placeholder: 'Enter current performance data'
},
// Audience Intelligence
{
id: 'content_preferences',
category: 'audience_intelligence',
label: 'Content Preferences',
description: 'Preferred content formats and topics',
tooltip: 'Identify what types of content your audience prefers. Consider formats (blog posts, videos, infographics) and topics that resonate most.',
type: 'json',
required: true,
placeholder: 'Define content preferences'
},
{
id: 'consumption_patterns',
category: 'audience_intelligence',
label: 'Consumption Patterns',
description: 'When and how audience consumes content',
tooltip: 'Understand when and how your audience consumes content. This includes peak times, preferred devices, and consumption channels.',
type: 'json',
required: false,
placeholder: 'Describe consumption patterns'
},
{
id: 'audience_pain_points',
category: 'audience_intelligence',
label: 'Audience Pain Points',
description: 'Key challenges and pain points',
tooltip: 'Identify the main challenges and pain points your audience faces. This helps create content that addresses real needs and provides value.',
type: 'json',
required: false,
placeholder: 'List audience pain points'
},
{
id: 'buying_journey',
category: 'audience_intelligence',
label: 'Buying Journey',
description: 'Customer journey stages and touchpoints',
tooltip: 'Map your audience\'s buying journey stages and the content touchpoints that influence their decisions.',
type: 'json',
required: false,
placeholder: 'Define buying journey stages'
},
{
id: 'seasonal_trends',
category: 'audience_intelligence',
label: 'Seasonal Trends',
description: 'Seasonal content opportunities',
tooltip: 'Identify seasonal trends and opportunities that affect your audience\'s content consumption and needs.',
type: 'json',
required: false,
placeholder: 'Define seasonal trends'
},
{
id: 'engagement_metrics',
category: 'audience_intelligence',
label: 'Engagement Metrics',
description: 'Current engagement data',
tooltip: 'Provide current engagement metrics to understand what content resonates with your audience.',
type: 'json',
required: false,
placeholder: 'Enter engagement metrics'
},
// Competitive Intelligence
{
id: 'top_competitors',
category: 'competitive_intelligence',
label: 'Top Competitors',
description: 'List of main competitors',
tooltip: 'Identify your main competitors in the market. This helps understand competitive landscape and identify content opportunities.',
type: 'json',
required: false,
placeholder: 'List top competitors'
},
{
id: 'competitor_content_strategies',
category: 'competitive_intelligence',
label: 'Competitor Content Strategies',
description: 'Analysis of competitor approaches',
tooltip: 'Analyze your competitors\' content strategies to identify gaps, opportunities, and differentiation possibilities.',
type: 'json',
required: false,
placeholder: 'Analyze competitor strategies'
},
{
id: 'market_gaps',
category: 'competitive_intelligence',
label: 'Market Gaps',
description: 'Identified market opportunities',
tooltip: 'Identify gaps in the market that your content can address. These are opportunities where competitors are not providing adequate content.',
type: 'json',
required: false,
placeholder: 'Identify market gaps'
},
{
id: 'industry_trends',
category: 'competitive_intelligence',
label: 'Industry Trends',
description: 'Current industry trends',
tooltip: 'Stay current with industry trends that affect your audience and content strategy.',
type: 'json',
required: false,
placeholder: 'List industry trends'
},
{
id: 'emerging_trends',
category: 'competitive_intelligence',
label: 'Emerging Trends',
description: 'Upcoming trends and opportunities',
tooltip: 'Identify emerging trends that could provide early-mover advantages in content creation.',
type: 'json',
required: false,
placeholder: 'Identify emerging trends'
},
// Content Strategy
{
id: 'preferred_formats',
category: 'content_strategy',
label: 'Preferred Formats',
description: 'Content formats to focus on',
tooltip: 'Choose the content formats that align with your audience preferences and business objectives.',
type: 'multiselect',
required: true,
options: ['Blog Posts', 'Videos', 'Infographics', 'Webinars', 'Podcasts', 'Case Studies', 'Whitepapers', 'Social Media Posts']
},
{
id: 'content_mix',
category: 'content_strategy',
label: 'Content Mix',
description: 'Distribution of content types',
tooltip: 'Define the percentage distribution of different content types in your strategy.',
type: 'json',
required: false,
placeholder: 'Define content mix percentages'
},
{
id: 'content_frequency',
category: 'content_strategy',
label: 'Content Frequency',
description: 'How often to publish content',
tooltip: 'Set realistic content publishing frequency based on your team capacity and audience expectations.',
type: 'select',
required: true,
options: ['Daily', 'Weekly', 'Bi-weekly', 'Monthly', 'Quarterly']
},
{
id: 'optimal_timing',
category: 'content_strategy',
label: 'Optimal Timing',
description: 'Best times for publishing',
tooltip: 'Identify the optimal times for publishing different types of content to maximize engagement.',
type: 'json',
required: false,
placeholder: 'Define optimal publishing times'
},
{
id: 'quality_metrics',
category: 'content_strategy',
label: 'Quality Metrics',
description: 'Content quality standards',
tooltip: 'Define the quality standards and metrics that will ensure your content meets your audience\'s expectations.',
type: 'json',
required: false,
placeholder: 'Define quality standards'
},
{
id: 'editorial_guidelines',
category: 'content_strategy',
label: 'Editorial Guidelines',
description: 'Style and tone guidelines',
tooltip: 'Establish editorial guidelines for consistent brand voice, tone, and style across all content.',
type: 'json',
required: false,
placeholder: 'Define editorial guidelines'
},
{
id: 'brand_voice',
category: 'content_strategy',
label: 'Brand Voice',
description: 'Brand personality and voice',
tooltip: 'Define your brand\'s personality and voice characteristics to ensure consistent messaging.',
type: 'json',
required: false,
placeholder: 'Define brand voice'
},
// Performance & Analytics
{
id: 'traffic_sources',
category: 'performance_analytics',
label: 'Traffic Sources',
description: 'Primary traffic sources',
tooltip: 'Identify your main traffic sources to understand where your audience comes from and optimize accordingly.',
type: 'json',
required: false,
placeholder: 'Define traffic sources'
},
{
id: 'conversion_rates',
category: 'performance_analytics',
label: 'Conversion Rates',
description: 'Current conversion data',
tooltip: 'Track conversion rates across different content types and channels to identify what drives results.',
type: 'json',
required: false,
placeholder: 'Enter conversion data'
},
{
id: 'content_roi_targets',
category: 'performance_analytics',
label: 'Content ROI Targets',
description: 'ROI goals and targets',
tooltip: 'Set realistic ROI targets for your content marketing efforts to measure return on investment.',
type: 'json',
required: false,
placeholder: 'Define ROI targets'
},
{
id: 'ab_testing_capabilities',
category: 'performance_analytics',
label: 'A/B Testing Capabilities',
description: 'A/B testing availability',
tooltip: 'Indicate whether you have A/B testing capabilities to optimize content performance.',
type: 'boolean',
required: false
}
];
// Progressive disclosure steps
const PROGRESSIVE_DISCLOSURE_STEPS: ProgressiveDisclosureStep[] = [
{
id: 'business_context',
title: 'Business Context',
description: 'Define your business objectives and context',
fields: ['business_objectives', 'target_metrics', 'content_budget', 'team_size', 'implementation_timeline', 'market_share', 'competitive_position', 'performance_metrics'],
is_complete: false,
is_visible: true,
dependencies: []
},
{
id: 'audience_intelligence',
title: 'Audience Intelligence',
description: 'Understand your target audience',
fields: ['content_preferences', 'consumption_patterns', 'audience_pain_points', 'buying_journey', 'seasonal_trends', 'engagement_metrics'],
is_complete: false,
is_visible: false,
dependencies: ['business_context']
},
{
id: 'competitive_intelligence',
title: 'Competitive Intelligence',
description: 'Analyze your competitive landscape',
fields: ['top_competitors', 'competitor_content_strategies', 'market_gaps', 'industry_trends', 'emerging_trends'],
is_complete: false,
is_visible: false,
dependencies: ['audience_intelligence']
},
{
id: 'content_strategy',
title: 'Content Strategy',
description: 'Define your content approach',
fields: ['preferred_formats', 'content_mix', 'content_frequency', 'optimal_timing', 'quality_metrics', 'editorial_guidelines', 'brand_voice'],
is_complete: false,
is_visible: false,
dependencies: ['competitive_intelligence']
},
{
id: 'performance_analytics',
title: 'Performance & Analytics',
description: 'Set up measurement and optimization',
fields: ['traffic_sources', 'conversion_rates', 'content_roi_targets', 'ab_testing_capabilities'],
is_complete: false,
is_visible: false,
dependencies: ['content_strategy']
}
];
// Store implementation
export const useEnhancedStrategyStore = create<EnhancedStrategyStore>((set, get) => ({
// Initial state
strategies: [],
currentStrategy: null,
aiAnalyses: [],
onboardingIntegrations: [],
// Progressive Disclosure
disclosureSteps: PROGRESSIVE_DISCLOSURE_STEPS,
currentStep: 0,
completedSteps: [],
// Tooltips
tooltips: {},
// Form State
formData: {},
formErrors: {},
autoPopulatedFields: {},
dataSources: {},
// UI State
loading: false,
error: null,
saving: false,
aiGenerating: false,
// Actions
setLoading: (loading) => set({ loading }),
setError: (error) => set({ error }),
setSaving: (saving) => set({ saving }),
setAIGenerating: (generating) => set({ aiGenerating: generating }),
// Strategy actions
createEnhancedStrategy: async (strategy) => {
set({ saving: true, error: null });
try {
const newStrategy = await contentPlanningApi.createEnhancedStrategy(strategy);
set((state) => ({
strategies: [...state.strategies, newStrategy],
saving: false,
}));
return newStrategy; // Return the created strategy
} catch (error: any) {
set({ error: error.message || 'Failed to create enhanced strategy', saving: false });
throw error; // Re-throw the error so the calling function can handle it
}
},
updateEnhancedStrategy: async (id, updates) => {
set({ saving: true, error: null });
try {
const updatedStrategy = await contentPlanningApi.updateEnhancedStrategy(id, updates);
set((state) => ({
strategies: state.strategies.map((strategy) =>
strategy.id === id ? updatedStrategy : strategy
),
saving: false,
}));
} catch (error: any) {
set({ error: error.message || 'Failed to update enhanced strategy', saving: false });
}
},
deleteEnhancedStrategy: async (id) => {
set({ saving: true, error: null });
try {
await contentPlanningApi.deleteEnhancedStrategy(id);
set((state) => ({
strategies: state.strategies.filter((strategy) => strategy.id !== id),
saving: false,
}));
} catch (error: any) {
set({ error: error.message || 'Failed to delete enhanced strategy', saving: false });
}
},
setCurrentStrategy: (strategy) => set({ currentStrategy: strategy }),
// Form actions
updateFormField: (fieldId, value) => {
set((state) => ({
formData: { ...state.formData, [fieldId]: value },
formErrors: { ...state.formErrors, [fieldId]: '' }
}));
},
validateFormField: (fieldId) => {
const field = STRATEGIC_INPUT_FIELDS.find(f => f.id === fieldId);
if (!field) return true;
const value = get().formData[fieldId];
if (field.required && (!value || (Array.isArray(value) && value.length === 0))) {
set((state) => ({
formErrors: { ...state.formErrors, [fieldId]: `${field.label} is required` }
}));
return false;
}
return true;
},
validateAllFields: () => {
const { formData } = get();
let isValid = true;
const errors: Record<string, string> = {};
STRATEGIC_INPUT_FIELDS.forEach(field => {
const value = formData[field.id];
if (field.required && (!value || (Array.isArray(value) && value.length === 0))) {
errors[field.id] = `${field.label} is required`;
isValid = false;
}
});
set({ formErrors: errors });
return isValid;
},
resetForm: () => {
set({
formData: {},
formErrors: {},
autoPopulatedFields: {},
dataSources: {},
currentStep: 0,
completedSteps: []
});
},
// Progressive disclosure actions
setCurrentStep: (step) => set({ currentStep: step }),
completeStep: (stepId) => {
set((state) => ({
completedSteps: [...state.completedSteps, stepId],
disclosureSteps: state.disclosureSteps.map(step =>
step.id === stepId ? { ...step, is_complete: true } : step
)
}));
},
canProceedToStep: (stepId) => {
const { disclosureSteps, completedSteps } = get();
const step = disclosureSteps.find(s => s.id === stepId);
if (!step) return false;
return step.dependencies.every(dep => completedSteps.includes(dep));
},
getNextStep: () => {
const { disclosureSteps, currentStep } = get();
const nextStep = disclosureSteps[currentStep + 1];
return nextStep && get().canProceedToStep(nextStep.id) ? nextStep : null;
},
getPreviousStep: () => {
const { disclosureSteps, currentStep } = get();
return currentStep > 0 ? disclosureSteps[currentStep - 1] : null;
},
// Auto-population actions
autoPopulateFromOnboarding: async () => {
set({ loading: true });
try {
console.log('🔄 Starting auto-population from onboarding data...');
// This would call the backend to get onboarding data and auto-populate fields
const response = await contentPlanningApi.getOnboardingData();
console.log('📡 Backend response:', response);
// Extract field values and sources from the new backend format
const fields = response.data?.fields || {};
const sources = response.data?.sources || {};
console.log('📋 Extracted fields:', fields);
console.log('🔗 Data sources:', sources);
// Transform the fields object to extract values for formData
const fieldValues: Record<string, any> = {};
const autoPopulatedFields: Record<string, any> = {};
Object.keys(fields).forEach(fieldId => {
const fieldData = fields[fieldId];
console.log(`🔍 Processing field ${fieldId}:`, fieldData);
if (fieldData && typeof fieldData === 'object' && 'value' in fieldData) {
fieldValues[fieldId] = fieldData.value;
autoPopulatedFields[fieldId] = fieldData.value;
console.log(`✅ Auto-populated ${fieldId}:`, fieldData.value);
} else {
console.log(`❌ Skipping ${fieldId} - invalid data structure`);
}
});
console.log('📝 Final field values:', fieldValues);
console.log('🔄 Final auto-populated fields:', autoPopulatedFields);
set((state) => ({
autoPopulatedFields,
dataSources: sources,
formData: { ...state.formData, ...fieldValues }
}));
console.log('✅ Auto-population completed successfully');
} catch (error: any) {
console.error('❌ Auto-population error:', error);
set({ error: error.message || 'Failed to auto-populate from onboarding' });
} finally {
set({ loading: false });
}
},
updateAutoPopulatedField: (fieldId, value, source) => {
set((state) => ({
autoPopulatedFields: { ...state.autoPopulatedFields, [fieldId]: value },
dataSources: { ...state.dataSources, [fieldId]: source }
}));
},
overrideAutoPopulatedField: (fieldId, value) => {
set((state) => ({
formData: { ...state.formData, [fieldId]: value },
autoPopulatedFields: { ...state.autoPopulatedFields, [fieldId]: value }
}));
},
// AI Analysis actions
generateAIRecommendations: async (strategyId) => {
set({ aiGenerating: true, error: null });
try {
const aiAnalysis = await contentPlanningApi.generateEnhancedAIRecommendations(strategyId);
set((state) => ({
aiAnalyses: [...state.aiAnalyses, aiAnalysis],
aiGenerating: false
}));
} catch (error: any) {
set({ error: error.message || 'Failed to generate AI recommendations', aiGenerating: false });
}
},
regenerateAIAnalysis: async (strategyId: string, analysisType: string) => {
set({ aiGenerating: true, error: null });
try {
const aiAnalysis = await contentPlanningApi.regenerateAIAnalysis(strategyId, analysisType);
set((state) => ({
aiAnalyses: state.aiAnalyses.map(analysis =>
analysis.strategy_id === strategyId && analysis.analysis_type === analysisType
? { ...analysis, ...aiAnalysis }
: analysis
),
aiGenerating: false
}));
} catch (error) {
set({
error: error instanceof Error ? error.message : 'Failed to regenerate AI analysis',
aiGenerating: false
});
}
},
// Data loading
loadEnhancedStrategies: async () => {
set({ loading: true, error: null });
try {
const strategies = await contentPlanningApi.getEnhancedStrategies();
set({ strategies, loading: false });
} catch (error: any) {
set({ error: error.message || 'Failed to load enhanced strategies', loading: false });
}
},
loadAIAnalyses: async (strategyId) => {
set({ loading: true, error: null });
try {
const analyses = await contentPlanningApi.getEnhancedAIAnalyses(strategyId);
set({ aiAnalyses: analyses, loading: false });
} catch (error: any) {
set({ error: error.message || 'Failed to load AI analyses', loading: false });
}
},
loadOnboardingIntegration: async (strategyId) => {
set({ loading: true, error: null });
try {
const integration = await contentPlanningApi.getOnboardingIntegration(strategyId);
set({ onboardingIntegrations: [integration], loading: false });
} catch (error: any) {
set({ error: error.message || 'Failed to load onboarding integration', loading: false });
}
},
// Tooltip actions
getTooltipData: (fieldId) => {
const field = STRATEGIC_INPUT_FIELDS.find(f => f.id === fieldId);
if (!field) return null;
const state = get();
const autoPopulatedFields = state.autoPopulatedFields || {};
const dataSources = state.dataSources || {};
return {
field_id: fieldId,
title: field.label,
description: field.tooltip,
examples: [],
best_practices: [],
data_source: dataSources[fieldId],
confidence_level: autoPopulatedFields[fieldId] ? 0.8 : undefined
};
},
updateTooltipData: (fieldId, data) => {
set((state) => ({
tooltips: { ...state.tooltips, [fieldId]: data }
}));
},
// Completion tracking
calculateCompletionPercentage: () => {
const { formData } = get();
const requiredFields = STRATEGIC_INPUT_FIELDS.filter(field => field.required);
const filledRequiredFields = requiredFields.filter(field =>
formData[field.id] &&
(typeof formData[field.id] === 'string' ? formData[field.id].trim() !== '' : true)
);
return (filledRequiredFields.length / requiredFields.length) * 100;
},
getCompletionStats: () => {
const { formData } = get();
const categories = ['business_context', 'audience_intelligence', 'competitive_intelligence', 'content_strategy', 'performance_analytics'];
const category_completion: Record<string, number> = {};
categories.forEach(category => {
const categoryFields = STRATEGIC_INPUT_FIELDS.filter(field => field.category === category);
const filledFields = categoryFields.filter(field =>
formData[field.id] &&
(typeof formData[field.id] === 'string' ? formData[field.id].trim() !== '' : true)
);
category_completion[category] = (filledFields.length / categoryFields.length) * 100;
});
const total_fields = STRATEGIC_INPUT_FIELDS.length;
const filled_fields = STRATEGIC_INPUT_FIELDS.filter(field =>
formData[field.id] &&
(typeof formData[field.id] === 'string' ? formData[field.id].trim() !== '' : true)
).length;
return {
total_fields,
filled_fields,
completion_percentage: (filled_fields / total_fields) * 100,
category_completion
};
}
}));

View File

@@ -0,0 +1,9 @@
// Export all stores
export { useDashboardStore } from './dashboardStore';
export { useSEODashboardStore } from './seoDashboardStore';
export { useSharedDashboardStore } from './sharedDashboardStore';
// Re-export types for convenience
export type { DashboardStore } from './dashboardStore';
export type { SEODashboardStore } from './seoDashboardStore';
export type { SharedDashboardState } from './sharedDashboardStore';

View File

@@ -0,0 +1,174 @@
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
import { SEODashboardData } from '../api/seoDashboard';
import { SEOAnalysisData } from '../components/shared/types';
import { seoAnalysisAPI } from '../api/seoAnalysis';
export interface SEODashboardStore {
// State
data: SEODashboardData | null;
loading: boolean;
error: string | null;
analysisData: SEOAnalysisData | null;
analysisLoading: boolean;
analysisError: string | null;
hasRunInitialAnalysis: boolean;
// Actions
setData: (data: SEODashboardData) => void;
setLoading: (loading: boolean) => void;
setError: (error: string | null) => void;
setAnalysisData: (data: SEOAnalysisData | null) => void;
setAnalysisLoading: (loading: boolean) => void;
setAnalysisError: (error: string | null) => void;
runSEOAnalysis: () => Promise<void>;
clearAnalysisError: () => void;
checkAndRunInitialAnalysis: () => void;
}
export const useSEODashboardStore = create<SEODashboardStore>()(
devtools(
(set, get) => ({
// Initial state
data: null,
loading: false,
error: null,
analysisData: null,
analysisLoading: false,
analysisError: null,
hasRunInitialAnalysis: false,
// Actions
setData: (data) => set({ data }),
setLoading: (loading) => set({ loading }),
setError: (error) => set({ error }),
setAnalysisData: (data) => set({ analysisData: data }),
setAnalysisLoading: (loading) => set({ analysisLoading: loading }),
setAnalysisError: (error) => set({ analysisError: error }),
clearAnalysisError: () => set({ analysisError: null }),
runSEOAnalysis: async () => {
const currentData = get().data;
// Get URL from onboarding data or use a fallback
let url = currentData?.website_url;
// If no URL from dashboard data, try to fetch from onboarding
if (!url) {
try {
// Import the user data API to get user's website URL
const { userDataAPI } = await import('../api/userData');
const userData = await userDataAPI.getUserData();
url = userData?.website_url || userData?.website_analysis?.website_url;
console.log('Fetched URL from user data:', url);
} catch (error) {
console.warn('Could not fetch URL from user data:', error);
}
}
// If still no URL, try the dedicated website URL endpoint
if (!url) {
try {
const { userDataAPI } = await import('../api/userData');
const websiteUrl = await userDataAPI.getWebsiteURL();
if (websiteUrl) {
url = websiteUrl;
console.log('Fetched URL from dedicated endpoint:', url);
}
} catch (error) {
console.warn('Could not fetch URL from dedicated endpoint:', error);
}
}
// Final fallback - only use if no URL was found from database
if (!url) {
url = 'https://example.com';
console.warn('Using fallback URL:', url);
}
console.log('Starting SEO analysis with URL:', url);
console.log('Current store state:', get());
set({ analysisLoading: true, analysisError: null });
try {
console.log(`Starting SEO analysis for URL: ${url}`);
const result = await seoAnalysisAPI.analyzeURL(url);
console.log('API result received:', result);
if (result) {
console.log('SEO analysis completed successfully:', result);
set({
analysisData: result,
analysisLoading: false,
hasRunInitialAnalysis: true
});
console.log('Store state after setting analysis data:', get());
// Update main dashboard data based on analysis
if (currentData) {
const updatedData = {
...currentData,
health_score: {
score: result.overall_score,
change: 0,
trend: 'stable',
label: result.health_status.replace('_', ' ').toUpperCase(),
color: result.health_status === 'poor' ? '#D32F2F' :
result.health_status === 'needs_improvement' ? '#FF9800' : '#4CAF50'
},
key_insight: result.critical_issues.length > 0
? `${result.critical_issues.length} critical issues found`
: 'SEO analysis completed successfully',
priority_alert: result.health_status === 'poor'
? 'Immediate attention required'
: result.health_status === 'needs_improvement'
? 'Improvements recommended'
: 'Good SEO health',
website_url: url // Update the website URL with the actual URL used
};
set({ data: updatedData });
}
} else {
console.error('Analysis returned null result');
set({
analysisError: 'Analysis failed to return results',
analysisLoading: false
});
}
} catch (error: any) {
console.error('SEO Analysis error:', error);
let errorMessage = 'Analysis failed';
if (error.code === 'ECONNABORTED') {
errorMessage = 'Analysis timed out. Please try again.';
} else if (error.response?.status === 500) {
errorMessage = 'Server error. Please try again later.';
} else if (error.response?.status === 404) {
errorMessage = 'Analysis service not found.';
} else if (error.message) {
errorMessage = error.message;
}
set({
analysisError: errorMessage,
analysisLoading: false
});
}
},
checkAndRunInitialAnalysis: () => {
const { analysisData, hasRunInitialAnalysis, data } = get();
if (!analysisData && !hasRunInitialAnalysis && data) {
get().runSEOAnalysis();
}
}
}),
{
name: 'seo-dashboard-store',
}
)
);

View File

@@ -0,0 +1,100 @@
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
export interface SharedDashboardState {
// Common state
isSidebarOpen: boolean;
currentTheme: 'light' | 'dark' | 'auto';
notifications: Array<{
id: string;
message: string;
type: 'info' | 'success' | 'warning' | 'error';
timestamp: Date;
read: boolean;
}>;
// Actions
toggleSidebar: () => void;
setSidebarOpen: (open: boolean) => void;
setTheme: (theme: 'light' | 'dark' | 'auto') => void;
addNotification: (message: string, type?: 'info' | 'success' | 'warning' | 'error') => void;
markNotificationAsRead: (id: string) => void;
clearNotifications: () => void;
clearOldNotifications: () => void;
}
export const useSharedDashboardStore = create<SharedDashboardState>()(
devtools(
(set, get) => ({
// Initial state
isSidebarOpen: false,
currentTheme: 'auto',
notifications: [],
// Actions
toggleSidebar: () => {
set((state) => ({ isSidebarOpen: !state.isSidebarOpen }));
},
setSidebarOpen: (open: boolean) => {
set({ isSidebarOpen: open });
},
setTheme: (theme: 'light' | 'dark' | 'auto') => {
set({ currentTheme: theme });
// Apply theme to document
if (theme === 'dark') {
document.documentElement.classList.add('dark');
} else if (theme === 'light') {
document.documentElement.classList.remove('dark');
} else {
// Auto theme - check system preference
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
if (prefersDark) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
}
},
addNotification: (message: string, type: 'info' | 'success' | 'warning' | 'error' = 'info') => {
const notification = {
id: Date.now().toString(),
message,
type,
timestamp: new Date(),
read: false,
};
set((state) => ({
notifications: [notification, ...state.notifications].slice(0, 10), // Keep only last 10
}));
},
markNotificationAsRead: (id: string) => {
set((state) => ({
notifications: state.notifications.map((notification) =>
notification.id === id ? { ...notification, read: true } : notification
),
}));
},
clearNotifications: () => {
set({ notifications: [] });
},
clearOldNotifications: () => {
const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);
set((state) => ({
notifications: state.notifications.filter(
(notification) => notification.timestamp > oneDayAgo
),
}));
},
}),
{
name: 'shared-dashboard-store',
}
)
);