/** * Site Dashboard (Advanced) * Phase 7: UI Components & Prompt Management * Site overview with statistics and analytics */ import React, { useState, useEffect } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import PageMeta from '../../components/common/PageMeta'; import PageHeader from '../../components/common/PageHeader'; import ComponentCard from '../../components/common/ComponentCard'; import SiteInfoBar from '../../components/common/SiteInfoBar'; import { Card } from '../../components/ui/card'; import Button from '../../components/ui/button/Button'; import { useToast } from '../../components/ui/toast/ToastContainer'; import { fetchAPI, fetchSiteSectors, setActiveSite as apiSetActiveSite } from '../../services/api'; import { getDashboardStats } from '../../services/billing.api'; import SiteSetupChecklist from '../../components/sites/SiteSetupChecklist'; import { integrationApi } from '../../services/integration.api'; import SiteConfigWidget from '../../components/dashboard/SiteConfigWidget'; import OperationsCostsWidget from '../../components/dashboard/OperationsCostsWidget'; import CreditAvailabilityWidget from '../../components/dashboard/CreditAvailabilityWidget'; import { useBillingStore } from '../../store/billingStore'; import { useSiteStore } from '../../store/siteStore'; import { FileIcon, PlugInIcon, GridIcon, BoltIcon, PageIcon, ArrowRightIcon, ArrowUpIcon, ClockIcon, ChevronRightIcon, } from '../../icons'; interface Site { id: number; name: string; slug: string; site_type: string; hosting_type: string; status: string; is_active: boolean; created_at: string; updated_at: string; domain?: string; industry?: string; industry_name?: string; } interface SiteSetupState { hasIndustry: boolean; hasSectors: boolean; sectorsCount: number; hasWordPressIntegration: boolean; hasKeywords: boolean; keywordsCount: number; hasAuthorProfiles: boolean; authorProfilesCount: number; } interface OperationStat { type: 'clustering' | 'ideas' | 'content' | 'images'; count: number; creditsUsed: number; avgCreditsPerOp: number; } export default function SiteDashboard() { const { id: siteId } = useParams<{ id: string }>(); const navigate = useNavigate(); const toast = useToast(); const { balance, loadBalance } = useBillingStore(); const { setActiveSite } = useSiteStore(); const [site, setSite] = useState(null); const [setupState, setSetupState] = useState({ hasIndustry: false, hasSectors: false, sectorsCount: 0, hasWordPressIntegration: false, hasKeywords: false, keywordsCount: 0, hasAuthorProfiles: false, authorProfilesCount: 0, }); const [operations, setOperations] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { if (siteId) { // Create a local copy of siteId to use in async operations const currentSiteId = siteId; // Reset state when site changes setOperations([]); setSite(null); setLoading(true); // Load data for this specific siteId loadSiteData(currentSiteId); loadBalance(); } }, [siteId]); const loadSiteData = async (currentSiteId: string) => { try { // Load site data const siteData = await fetchAPI(`/v1/auth/sites/${currentSiteId}/`); // CRITICAL: Verify we're still on the same site before updating state // This prevents race conditions when user rapidly switches sites if (siteData) { setSite(siteData); // Update global site store so site selector shows correct site setActiveSite(siteData); // Also set as active site in backend await apiSetActiveSite(siteData.id).catch(() => {}); // Check setup state const hasIndustry = !!siteData.industry || !!siteData.industry_name; // Load sectors let hasSectors = false; let sectorsCount = 0; try { const sectors = await fetchSiteSectors(Number(currentSiteId)); hasSectors = sectors && sectors.length > 0; sectorsCount = sectors?.length || 0; } catch (err) { console.log('Could not load sectors'); } // Check WordPress integration let hasWordPressIntegration = false; try { const wpIntegration = await integrationApi.getWordPressIntegration(Number(currentSiteId)); hasWordPressIntegration = !!wpIntegration; } catch (err) { // No integration is fine } // Check keywords - try to load keywords for this site let hasKeywords = false; let keywordsCount = 0; try { const { fetchKeywords } = await import('../../services/api'); const keywordsData = await fetchKeywords({ site_id: Number(currentSiteId), page_size: 1 }); hasKeywords = keywordsData?.results?.length > 0 || keywordsData?.count > 0; keywordsCount = keywordsData?.count || 0; } catch (err) { // No keywords is fine } // Check author profiles let hasAuthorProfiles = false; let authorProfilesCount = 0; try { const authorsData = await fetchAPI(`/v1/thinker/author-profiles/?site_id=${currentSiteId}&page_size=1`); hasAuthorProfiles = authorsData?.count > 0; authorProfilesCount = authorsData?.count || 0; } catch (err) { // No profiles is fine } setSetupState({ hasIndustry, hasSectors, sectorsCount, hasWordPressIntegration, hasKeywords, keywordsCount, hasAuthorProfiles, authorProfilesCount, }); // Load operation stats from real API data try { const stats = await getDashboardStats({ site_id: Number(currentSiteId), days: 7 }); // Map operation types from API to display types const operationTypeMap: Record = { 'clustering': 'clustering', 'idea_generation': 'ideas', 'content_generation': 'content', 'image_generation': 'images', 'image_prompt_extraction': 'images', }; const mappedOperations: OperationStat[] = []; const expectedTypes: Array<'clustering' | 'ideas' | 'content' | 'images'> = ['clustering', 'ideas', 'content', 'images']; // Initialize with zeros const opTotals: Record = {}; expectedTypes.forEach(t => { opTotals[t] = { count: 0, credits: 0 }; }); // Sum up operations by mapped type if (stats.ai_operations?.operations) { stats.ai_operations.operations.forEach(op => { const mappedType = operationTypeMap[op.type] || op.type; if (opTotals[mappedType]) { opTotals[mappedType].count += op.count; opTotals[mappedType].credits += op.credits; } }); } // Convert to array with avgCreditsPerOp expectedTypes.forEach(type => { const data = opTotals[type]; mappedOperations.push({ type, count: data.count, creditsUsed: data.credits, avgCreditsPerOp: data.count > 0 ? data.credits / data.count : 0, }); }); setOperations(mappedOperations); } catch (err) { console.log('Could not load operations stats:', err); // Set empty operations if API fails setOperations([]); } } } catch (error: any) { toast.error(`Failed to load site data: ${error.message}`); } finally { setLoading(false); } }; if (loading) { return (
Loading site dashboard...
); } if (!site) { return (

Site not found

); } return (
, color: 'blue' }} hideSiteSector /> {/* Site Info Bar */} {/* Site Setup Progress + Quick Actions - Side by Side */}
{/* Site Setup Checklist - Left Half */} {/* Quick Actions - Right Half */}
{/* Manage Pages */} {/* Manage Content */} {/* Integrations */} {/* Sync Dashboard */} {/* Deploy Site */} {/* Content Calendar */}
{/* Site Insights - 3 Column Grid */}
{/* Recent Activity - Placeholder */}

Recent Activity

No recent activity

); }