diff --git a/HOMEPAGE_RESTRUCTURE_PLAN.md b/HOMEPAGE_RESTRUCTURE_PLAN.md new file mode 100644 index 00000000..9e3117ab --- /dev/null +++ b/HOMEPAGE_RESTRUCTURE_PLAN.md @@ -0,0 +1,338 @@ +# Homepage Restructure Plan + +## Current State Analysis + +### Current Homepage Structure (in order): +1. **PageHeader** - Title, last updated, refresh button +2. **WorkflowGuide** - Welcome screen with site addition form (conditional) +3. **Hero Section** - Large banner with title, description, and 2 action buttons +4. **Your Content Creation Workflow** - 4-step workflow cards +5. **Key Metrics** - 4 metric cards (Keywords, Content, Images, Completion) +6. **Platform Modules** - 4 module cards (Planner, Writer, Thinker, Automation) +7. **Activity Chart & Recent Activity** - Chart and activity list +8. **How It Works** - 7-step detailed workflow pipeline +9. **Quick Actions** - 4 action cards (Add Keywords, Create Content, Setup Automation, Manage Prompts) +10. **Credit Balance & Usage** - Widgets at bottom + +### Identified Issues: + +#### 1. Duplicates & Redundancies: +- **Hero Section** vs **WorkflowGuide**: Both serve as welcome/intro sections +- **"Your Content Creation Workflow"** (4 steps) vs **"How It Works"** (7 steps): Duplicate workflow explanations +- **Quick Actions** duplicates workflow steps and module navigation +- **Platform Modules** duplicates sidebar navigation +- **Activity Chart** shows dummy data (hardcoded) + +#### 2. Site Management Issues: +- Site addition only in WorkflowGuide (hidden when dismissed) +- No clear trigger for multi-site users to add sites +- Site selector not present on homepage (but should be for multi-site users) +- Sector selector not needed on homepage (as per user requirement) + +#### 3. Layout Issues: +- Hero banner is too large and has action buttons (should be simpler, at top) +- Banner appears after welcome screen (should be at top) +- Too much content, overwhelming for new users + +--- + +## Proposed Restructure Plan + +### New Homepage Structure: + +``` +1. PageHeader (with conditional Site Selector for multi-site users) + └─ Site Selector: "All Sites" | "Site Name" dropdown (only if user has 2+ sites) + +2. Simplified Banner (moved to top, minimal content, no buttons) + └─ Title: "AI-Powered Content Creation Workflow" + └─ Subtitle: Brief description + └─ No action buttons (removed) + +3. Welcome Screen / Site Addition (conditional) + └─ Show WorkflowGuide if: + - No sites exist, OR + - User manually triggers "Add Site" button + └─ For multi-site users: Show compact "Add Site" button/trigger + +4. Key Metrics (4 cards) + └─ Data filtered by selected site or "All Sites" + +5. Your Content Creation Workflow (4 steps - keep this one) + └─ Remove "How It Works" (7 steps) - redundant + +6. Platform Modules (keep but make more compact) + └─ Or consider removing if sidebar navigation is sufficient + +7. Activity Overview (chart + recent activity) + └─ Use real data, not dummy data + +8. Quick Actions (simplified - remove duplicates) + └─ Keep only unique actions not covered elsewhere + +9. Credit Balance & Usage (keep at bottom) +``` + +--- + +## Detailed Changes + +### 1. PageHeader Enhancement +**Location**: Top of page, after PageMeta + +**Changes**: +- Add conditional **Site Selector** dropdown (right side of header) +- Only show if user has 2+ active sites +- Options: "All Sites" | Individual site names +- When "All Sites" selected: Show aggregated data +- When specific site selected: Show filtered data for that site +- **Hide sector selector** on homepage (as per requirement) + +**Implementation**: +```tsx + +
+
+

Dashboard

+

Last updated: {time}

+
+
+ {sites.length > 1 && ( + ({ value: s.id, label: s.name })) + ]} + /> + )} + +
+
+
+``` + +--- + +### 2. Simplified Banner (Move to Top) +**Location**: Immediately after PageHeader + +**Changes**: +- Move from current position (after WorkflowGuide) to top +- Remove action buttons ("Get Started", "Configure Automation") +- Reduce padding and content +- Keep gradient background and title +- Make it more compact (p-6 instead of p-8 md:p-12) + +**New Structure**: +```tsx +
+

+ AI-Powered Content Creation Workflow +

+

+ Transform keywords into published content with intelligent automation. +

+
+``` + +--- + +### 3. Welcome Screen / Site Addition Strategy + +#### For Users with NO Sites: +- Always show WorkflowGuide (welcome screen with site addition form) +- Cannot be dismissed until at least one site is created + +#### For Users with 1 Site: +- Hide WorkflowGuide by default (can be manually shown via button) +- Show compact "Add Another Site" button/trigger in header or quick actions +- When clicked, opens WorkflowGuide or modal with site addition form +- Dashboard shows data for the single site (no site selector needed) + +#### For Users with 2+ Sites: +- Hide WorkflowGuide by default +- Show site selector in PageHeader (see #1) +- Add "Add Site" button in header or as a card in Quick Actions +- When clicked, opens WorkflowGuide or modal with site addition form +- Dashboard shows data based on selected site filter + +**Implementation**: +```tsx +// In PageHeader or Quick Actions +{sites.length > 0 && ( + +)} + +// Conditional WorkflowGuide +{(!hasSites || showAddSite) && ( + { + setShowAddSite(false); + refreshDashboard(); + }} + /> +)} +``` + +--- + +### 4. Remove Duplicates + +#### Remove "How It Works" Section (7 steps) +- **Reason**: Duplicates "Your Content Creation Workflow" (4 steps) +- **Keep**: "Your Content Creation Workflow" (simpler, cleaner) + +#### Simplify Quick Actions +- **Remove**: "Add Keywords" (covered in workflow) +- **Remove**: "Create Content" (covered in workflow) +- **Keep**: "Setup Automation" (unique) +- **Keep**: "Manage Prompts" (unique) +- **Add**: "Add Site" (for multi-site users) + +#### Consider Removing Platform Modules +- **Option A**: Remove entirely (sidebar navigation is sufficient) +- **Option B**: Keep but make more compact (2x2 grid instead of 4 columns) +- **Recommendation**: Remove to reduce clutter + +--- + +### 5. Data Filtering Logic + +**Single Site User**: +- Always show data for that one site +- No site selector +- `site_id` = activeSite.id in all API calls + +**Multi-Site User**: +- Show site selector in header +- Default: "All Sites" (aggregated data) +- When specific site selected: Filter by `site_id` +- Update all metrics, charts, and activity when filter changes + +**Implementation**: +```tsx +const [siteFilter, setSiteFilter] = useState<'all' | number>('all'); + +const fetchAppInsights = async () => { + const siteId = siteFilter === 'all' ? undefined : siteFilter; + + const [keywordsRes, clustersRes, ...] = await Promise.all([ + fetchKeywords({ page_size: 1, site_id: siteId }), + fetchClusters({ page_size: 1, site_id: siteId }), + // ... other calls + ]); + + // Update insights state +}; +``` + +--- + +### 6. Activity Chart - Use Real Data + +**Current**: Hardcoded dummy data +**Change**: Fetch real activity data from API + +**Implementation**: +- Create API endpoint for activity timeline +- Or aggregate from existing endpoints (content created dates, etc.) +- Show actual trends over past 7 days + +--- + +## Final Proposed Structure + +``` +┌─────────────────────────────────────────┐ +│ PageHeader │ +│ - Title: Dashboard │ +│ - Site Selector (if 2+ sites) │ +│ - Refresh Button │ +└─────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────┐ +│ Simplified Banner (compact, no buttons) │ +│ - Title + Subtitle only │ +└─────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────┐ +│ WorkflowGuide (conditional) │ +│ - Show if: no sites OR manually opened │ +│ - Contains site addition form │ +└─────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────┐ +│ Key Metrics (4 cards) │ +│ - Filtered by site selection │ +└─────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────┐ +│ Your Content Creation Workflow (4 steps)│ +│ - Keep this, remove "How It Works" │ +└─────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────┐ +│ Activity Overview │ +│ - Chart (real data) + Recent Activity │ +└─────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────┐ +│ Quick Actions (2-3 unique actions) │ +│ - Setup Automation │ +│ - Manage Prompts │ +│ - Add Site (if multi-site user) │ +└─────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────┐ +│ Credit Balance & Usage │ +└─────────────────────────────────────────┘ +``` + +--- + +## Implementation Priority + +### Phase 1: Core Restructure +1. ✅ Move banner to top, simplify it +2. ✅ Add site selector to PageHeader (conditional) +3. ✅ Implement data filtering logic +4. ✅ Update WorkflowGuide visibility logic + +### Phase 2: Remove Duplicates +5. ✅ Remove "How It Works" section +6. ✅ Simplify Quick Actions +7. ✅ Consider removing Platform Modules + +### Phase 3: Enhancements +8. ✅ Add "Add Site" trigger for existing users +9. ✅ Replace dummy activity data with real data +10. ✅ Test single vs multi-site scenarios + +--- + +## Benefits + +1. **Cleaner UI**: Removed redundant sections +2. **Better UX**: Clear site management for multi-site users +3. **Focused Content**: Less overwhelming for new users +4. **Proper Data**: Real activity data, filtered by site +5. **Flexible**: Works for both single and multi-site users +6. **Accessible**: Easy to add sites from homepage when needed + +--- + +## Questions to Consider + +1. Should Platform Modules be removed entirely or kept compact? +2. Should "Add Site" be a button in header or a card in Quick Actions? +3. Should WorkflowGuide be a modal or inline component when triggered? +4. Do we need a separate "All Sites" view or just individual site filtering? + diff --git a/backend/celerybeat-schedule b/backend/celerybeat-schedule index 45567469..c3259eb2 100644 Binary files a/backend/celerybeat-schedule and b/backend/celerybeat-schedule differ diff --git a/frontend/src/components/form/SelectDropdown.tsx b/frontend/src/components/form/SelectDropdown.tsx index f58e8826..3b2bdcd0 100644 --- a/frontend/src/components/form/SelectDropdown.tsx +++ b/frontend/src/components/form/SelectDropdown.tsx @@ -102,7 +102,9 @@ const SelectDropdown: React.FC = ({ onClick={() => !disabled && setIsOpen(!isOpen)} disabled={disabled} onKeyDown={handleKeyDown} - className={`igny8-select-styled h-9 w-full appearance-none rounded-lg border border-gray-300 bg-transparent px-3 py-2 pr-10 text-sm shadow-theme-xs focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:focus:border-brand-800 ${ + className={`igny8-select-styled w-full appearance-none rounded-lg border border-gray-300 bg-transparent px-3 pr-10 shadow-theme-xs focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:focus:border-brand-800 ${ + className.includes('text-base') ? 'h-11 py-2.5 text-base' : 'h-9 py-2 text-sm' + } ${ isPlaceholder ? "text-gray-400 dark:text-gray-400" : "text-gray-800 dark:text-white/90" @@ -140,7 +142,9 @@ const SelectDropdown: React.FC = ({ // Pass the normalized optionValue to ensure consistency handleSelect(optionValue); }} - className={`w-full text-left px-3 py-2 text-sm transition-colors flex items-center gap-2 ${ + className={`w-full text-left px-3 py-2 transition-colors flex items-center gap-2 ${ + className.includes('text-base') ? 'text-base' : 'text-sm' + } ${ isSelected ? "bg-brand-500 text-white" : "text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-800" diff --git a/frontend/src/components/onboarding/WorkflowGuide.tsx b/frontend/src/components/onboarding/WorkflowGuide.tsx index e2661b70..680d2834 100644 --- a/frontend/src/components/onboarding/WorkflowGuide.tsx +++ b/frontend/src/components/onboarding/WorkflowGuide.tsx @@ -1,15 +1,13 @@ /** * WorkflowGuide Component * Inline welcome/guide screen for new users - * Shows complete workflow explainer with visual flow maps and progress tracking + * Shows site creation form with industry and sector selection */ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; import { Card } from '../ui/card'; import Button from '../ui/button/Button'; import Badge from '../ui/badge/Badge'; -import { ProgressBar } from '../ui/progress'; -import Checkbox from '../form/input/Checkbox'; import { CloseIcon, ArrowRightIcon, @@ -24,33 +22,32 @@ import { } from '../../icons'; import { useOnboardingStore } from '../../store/onboardingStore'; import { useSiteStore } from '../../store/siteStore'; -import { fetchSites, fetchKeywords, fetchClusters, fetchContent } from '../../services/api'; +import { fetchSites, fetchIndustries, Industry, Sector, createSite, selectSectorsForSite, setActiveSite } from '../../services/api'; import { useAuthStore } from '../../store/authStore'; +import SelectDropdown from '../form/SelectDropdown'; +import { useToast } from '../ui/toast/ToastContainer'; +import Alert from '../ui/alert/Alert'; -interface WorkflowProgress { - hasSite: boolean; - keywordsCount: number; - clustersCount: number; - contentCount: number; - publishedCount: number; - completionPercentage: number; +interface WorkflowGuideProps { + onSiteAdded?: () => void; } -export default function WorkflowGuide() { +export default function WorkflowGuide({ onSiteAdded }: WorkflowGuideProps) { const navigate = useNavigate(); + const toast = useToast(); const { isGuideVisible, dismissGuide, loadFromBackend } = useOnboardingStore(); - const { activeSite } = useSiteStore(); + const { activeSite, loadActiveSite } = useSiteStore(); const { isAuthenticated } = useAuthStore(); - const [progress, setProgress] = useState({ - hasSite: false, - keywordsCount: 0, - clustersCount: 0, - contentCount: 0, - publishedCount: 0, - completionPercentage: 0, - }); - const [loadingProgress, setLoadingProgress] = useState(true); - const [dontShowAgain, setDontShowAgain] = useState(false); + + // Industry and Sector selection state + const [industries, setIndustries] = useState([]); + const [selectedIndustry, setSelectedIndustry] = useState(null); + const [selectedSectors, setSelectedSectors] = useState([]); + const [loadingIndustries, setLoadingIndustries] = useState(false); + const [isWordPressExpanded, setIsWordPressExpanded] = useState(false); + const [isCreatingSite, setIsCreatingSite] = useState(false); + const [siteName, setSiteName] = useState(''); + const [websiteAddress, setWebsiteAddress] = useState(''); // Load dismissal state from backend on mount useEffect(() => { @@ -61,56 +58,137 @@ export default function WorkflowGuide() { } }, [isAuthenticated, loadFromBackend]); - // Load progress data + // Load industries on mount useEffect(() => { if (!isAuthenticated || !isGuideVisible) return; - - const loadProgress = async () => { + + const loadIndustries = async () => { try { - setLoadingProgress(true); - const [sitesRes, keywordsRes, clustersRes, contentRes] = await Promise.all([ - fetchSites().catch(() => ({ results: [], count: 0 })), - fetchKeywords({ page_size: 1 }).catch(() => ({ count: 0 })), - fetchClusters({ page_size: 1 }).catch(() => ({ count: 0 })), - fetchContent({ page_size: 1 }).catch(() => ({ count: 0 })), - ]); - - const sitesCount = sitesRes.results?.filter((s: any) => s.is_active).length || 0; - const keywordsCount = keywordsRes.count || 0; - const clustersCount = clustersRes.count || 0; - const contentCount = contentRes.count || 0; - const publishedCount = 0; // TODO: Add published content count when API is available - - // Calculate completion percentage - // Milestones: Site (20%), Keywords (20%), Clusters (20%), Content (20%), Published (20%) - let completion = 0; - if (sitesCount > 0) completion += 20; - if (keywordsCount > 0) completion += 20; - if (clustersCount > 0) completion += 20; - if (contentCount > 0) completion += 20; - if (publishedCount > 0) completion += 20; - - setProgress({ - hasSite: sitesCount > 0, - keywordsCount, - clustersCount, - contentCount, - publishedCount, - completionPercentage: completion, - }); + setLoadingIndustries(true); + const response = await fetchIndustries(); + setIndustries(response.industries || []); } catch (error) { - console.error('Failed to load progress:', error); + console.error('Failed to load industries:', error); } finally { - setLoadingProgress(false); + setLoadingIndustries(false); } }; - - loadProgress(); + + loadIndustries(); }, [isAuthenticated, isGuideVisible]); - if (!isGuideVisible) return null; + // Get available sectors for selected industry + const availableSectors = useMemo(() => { + if (!selectedIndustry) return []; + return selectedIndustry.sectors || []; + }, [selectedIndustry]); - const { hasSite, keywordsCount, clustersCount, contentCount, publishedCount, completionPercentage } = progress; + // Handle industry selection + const handleIndustrySelect = (industry: Industry) => { + setSelectedIndustry(industry); + setSelectedSectors([]); // Reset sectors when industry changes + }; + + // Handle sector toggle + const handleSectorToggle = (sectorSlug: string) => { + setSelectedSectors(prev => + prev.includes(sectorSlug) + ? prev.filter(s => s !== sectorSlug) + : [...prev, sectorSlug] + ); + }; + + // Handle WordPress card click - expand to show industry/sector selection + const handleWordPressCardClick = () => { + setIsWordPressExpanded(!isWordPressExpanded); + if (!isWordPressExpanded && industries.length === 0) { + // Load industries if not already loaded + const loadIndustries = async () => { + try { + setLoadingIndustries(true); + const response = await fetchIndustries(); + setIndustries(response.industries || []); + } catch (error) { + console.error('Failed to load industries:', error); + } finally { + setLoadingIndustries(false); + } + }; + loadIndustries(); + } + }; + + // Handle Add Site button click + const handleAddSite = async () => { + if (!siteName || !siteName.trim()) { + toast.error('Please enter a site name'); + return; + } + + if (!selectedIndustry) { + toast.error('Please select an industry first'); + return; + } + + if (selectedSectors.length === 0) { + toast.error('Please select at least one sector'); + return; + } + + if (selectedSectors.length > 5) { + toast.error('Maximum 5 sectors allowed per site'); + return; + } + + try { + setIsCreatingSite(true); + + // Create site with user-provided name and domain + const newSite = await createSite({ + name: siteName.trim(), + domain: websiteAddress.trim() || undefined, + is_active: true, + hosting_type: 'wordpress', + }); + + // Set industry for the site (if API supports it during creation, otherwise update) + // For now, we'll set it via selectSectorsForSite which also sets industry + + // Select sectors for the site (this also sets the industry) + await selectSectorsForSite( + newSite.id, + selectedIndustry.slug, + selectedSectors + ); + + // Set as active site if it's the first site + const sitesRes = await fetchSites(); + if (sitesRes.results.length === 1) { + await setActiveSite(newSite.id); + } + + const siteNameTrimmed = siteName.trim(); + toast.success(`Site "${siteNameTrimmed}" created successfully with ${selectedSectors.length} sector${selectedSectors.length !== 1 ? 's' : ''}!`); + + // Reload sites in store + await loadActiveSite(); + + // Call callback if provided (before navigation) + if (onSiteAdded) { + onSiteAdded(); + } + + // Dismiss guide and redirect to site settings with integrations tab + await dismissGuide(); + navigate(`/sites/${newSite.id}/settings?tab=integrations`); + } catch (error: any) { + toast.error(`Failed to create site: ${error.message}`); + } finally { + setIsCreatingSite(false); + } + }; + + if (!isGuideVisible) return null; return (
@@ -142,413 +220,215 @@ export default function WorkflowGuide() {
- {/* Main Workflow Options */} -
- {/* Build New Site */} - -
+ {/* Site Integration Option */} +
+ +
- -
-
-

- Build New Site -

-

- Create a new website from scratch with IGNY8 -

-
-
-
- - -
-
- - {/* Integrate Existing Site */} - -
-

- Integrate Existing Site + Integrate New Site or Existing Site

- Connect your existing website to IGNY8 + Connect your WordPress site to IGNY8

-
+ + {/* Site Type Selection - 2 columns for future IGNY8 option */} +
+ {/* WordPress Site Card - Clickable to expand */} - +
+ + {/* Expanded Site Form - 3 columns: Name, Website Address, Industry */} + {isWordPressExpanded && ( +
+ {/* Site Name, Website Address, and Industry - 3 columns */} +
+ {/* Site Name */} +
+ + setSiteName(e.target.value)} + placeholder="Enter site name" + className="w-full px-4 py-2.5 border-2 border-gray-200 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white text-base focus:border-blue-500 focus:ring-2 focus:ring-blue-200 dark:focus:ring-blue-800" + /> +
+ + {/* Website Address */} +
+ + setWebsiteAddress(e.target.value)} + placeholder="https://example.com" + className="w-full px-4 py-2.5 border-2 border-gray-200 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white text-base focus:border-blue-500 focus:ring-2 focus:ring-blue-200 dark:focus:ring-blue-800" + /> +
+ + {/* Industry Selection - Using SelectDropdown */} +
+ + {loadingIndustries ? ( +
Loading industries...
+ ) : ( + ({ + value: industry.slug, + label: industry.name, + }))} + placeholder="-- Select Industry --" + value={selectedIndustry?.slug || ''} + onChange={(value) => { + const industry = industries.find(i => i.slug === value); + if (industry) { + handleIndustrySelect(industry); + } + }} + className="text-base [&_.igny8-select-styled]:text-base [&_.igny8-select-styled]:py-2.5 [&_button]:text-base" + /> + )} +
+
+ + {/* Sector Selection - Cards like IndustriesSectorsKeywords page */} + {selectedIndustry && availableSectors.length > 0 && ( +
+ +
+ {availableSectors.map((sector) => ( + handleSectorToggle(sector.slug)} + > +
+

+ {sector.name} +

+ {selectedSectors.includes(sector.slug) && ( + + )} +
+ {sector.description && ( +

+ {sector.description} +

+ )} +
+ ))} +
+ {selectedSectors.length > 0 && ( +

+ {selectedSectors.length} sector{selectedSectors.length !== 1 ? 's' : ''} selected +

+ )} +
+ )} + + {/* Validation Notification Card */} + {(!siteName || !siteName.trim() || !selectedIndustry || selectedSectors.length === 0) && ( +
+
+
+ +
+
+
+ )} + + {/* Add Site Button */} +
+ +
+
+ )} +
- {/* Workflow Steps */} -
-

- Your Content Creation Workflow -

-
- {[ - { icon: , label: 'Discover Keywords', gradient: 'from-blue-500 to-blue-600', path: '/planner/keywords' }, - { icon: , label: 'Cluster Keywords', gradient: 'from-purple-500 to-purple-600', path: '/planner/clusters' }, - { icon: , label: 'Generate Ideas', gradient: 'from-orange-500 to-orange-600', path: '/planner/ideas' }, - { icon: , label: 'Create Content', gradient: 'from-green-500 to-green-600', path: '/writer/content' }, - ].map((step, index) => ( - - ))} -
-
- {/* Progress Tracking */} -
-
-

- Your Progress -

- - {completionPercentage}% Complete - -
- - - {/* Milestones */} -
- {[ - { - label: 'Site Created', - completed: hasSite, - count: hasSite ? 1 : 0, - path: '/sites', - icon: - }, - { - label: 'Keywords', - completed: keywordsCount > 0, - count: keywordsCount, - path: '/planner/keywords', - icon: - }, - { - label: 'Clusters', - completed: clustersCount > 0, - count: clustersCount, - path: '/planner/clusters', - icon: - }, - { - label: 'Content', - completed: contentCount > 0, - count: contentCount, - path: '/writer/content', - icon: - }, - { - label: 'Published', - completed: publishedCount > 0, - count: publishedCount, - path: '/writer/published', - icon: - }, - ].map((milestone, index) => ( - - ))} -
-
- - {/* Contextual CTA based on progress */} - {completionPercentage === 0 && ( -
- -
-
- Get started by creating your first site -
-
- Choose one of the options above to begin -
-
-
- )} - - {hasSite && keywordsCount === 0 && ( -
- -
-
- Great! You've created your first site -
-
- Now discover keywords to start your content planning -
-
- -
- )} - - {keywordsCount > 0 && clustersCount === 0 && ( -
- -
-
- {keywordsCount} keywords added! -
-
- Group them into clusters to organize your content strategy -
-
- -
- )} - - {clustersCount > 0 && contentCount === 0 && ( -
- -
-
- {clustersCount} clusters ready! -
-
- Generate content ideas and start writing -
-
- -
- )} - - {contentCount > 0 && publishedCount === 0 && ( -
- -
-
- {contentCount} content pieces created! -
-
- Review and publish your content to go live -
-
- -
- )} - - {/* Footer Actions */} -
-
-
- -
-
- - -
-
-

- You can always access this guide from the orange "Show Guide" button in the header -

-
); diff --git a/frontend/src/pages/Dashboard/Home.tsx b/frontend/src/pages/Dashboard/Home.tsx index 458308df..886a639c 100644 --- a/frontend/src/pages/Dashboard/Home.tsx +++ b/frontend/src/pages/Dashboard/Home.tsx @@ -1,5 +1,5 @@ -import React, { useEffect, useState, lazy, Suspense } from "react"; -import { Link } from "react-router"; +import React, { useEffect, useState, lazy, Suspense, useRef } from "react"; +import { Link, useNavigate } from "react-router"; import PageMeta from "../../components/common/PageMeta"; import CreditBalanceWidget from "../../components/dashboard/CreditBalanceWidget"; import UsageChartWidget from "../../components/dashboard/UsageChartWidget"; @@ -25,6 +25,9 @@ import { PieChartIcon, ClockIcon, PaperPlaneIcon, + PlusIcon, + ChevronDownIcon, + GridIcon, } from "../../icons"; import { fetchKeywords, @@ -32,14 +35,131 @@ import { fetchContentIdeas, fetchTasks, fetchContent, - fetchContentImages + fetchContentImages, + fetchSites, + Site, } from "../../services/api"; import { useSiteStore } from "../../store/siteStore"; import { useSectorStore } from "../../store/sectorStore"; import { useToast } from "../../components/ui/toast/ToastContainer"; +import { Dropdown } from "../../components/ui/dropdown/Dropdown"; +import { DropdownItem } from "../../components/ui/dropdown/DropdownItem"; +import Button from "../../components/ui/button/Button"; +import SelectDropdown from "../../components/form/SelectDropdown"; +import { useAuthStore } from "../../store/authStore"; +import SiteAndSectorSelector from "../../components/common/SiteAndSectorSelector"; const Chart = lazy(() => import("react-apexcharts").then((mod) => ({ default: mod.default }))); +// Homepage Site Selector Component (with "All Sites" option) +interface HomepageSiteSelectorProps { + sites: Site[]; + siteFilter: 'all' | number; + onSiteFilterChange: (value: string) => void; +} + +function HomepageSiteSelector({ sites, siteFilter, onSiteFilterChange }: HomepageSiteSelectorProps) { + const [isOpen, setIsOpen] = useState(false); + const buttonRef = useRef(null); + const { activeSite } = useSiteStore(); + + const displayText = siteFilter === 'all' + ? 'All Sites' + : sites.find(s => s.id === siteFilter)?.name || activeSite?.name || 'Select Site'; + + return ( +
+ + setIsOpen(false)} + anchorRef={buttonRef} + placement="bottom-left" + className="w-64 p-2" + > + { + onSiteFilterChange('all'); + setIsOpen(false); + }} + className={`flex items-center gap-3 px-3 py-2 font-medium rounded-lg text-sm text-left ${ + siteFilter === 'all' + ? "bg-brand-50 text-brand-700 dark:bg-brand-500/20 dark:text-brand-300" + : "text-gray-700 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300" + }`} + > + All Sites + {siteFilter === 'all' && ( + + + + )} + + {sites.map((site) => ( + { + onSiteFilterChange(site.id.toString()); + setIsOpen(false); + }} + className={`flex items-center gap-3 px-3 py-2 font-medium rounded-lg text-sm text-left ${ + siteFilter === site.id + ? "bg-brand-50 text-brand-700 dark:bg-brand-500/20 dark:text-brand-300" + : "text-gray-700 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300" + }`} + > + {site.name} + {siteFilter === site.id && ( + + + + )} + + ))} + +
+ ); +} + interface AppInsights { totalKeywords: number; totalClusters: number; @@ -115,27 +235,121 @@ const workflowSteps = [ export default function Home() { const toast = useToast(); - const { activeSite } = useSiteStore(); + const navigate = useNavigate(); + const { activeSite, setActiveSite, loadActiveSite } = useSiteStore(); const { activeSector } = useSectorStore(); const { isGuideDismissed, showGuide, loadFromBackend } = useOnboardingStore(); + const { user } = useAuthStore(); const [insights, setInsights] = useState(null); const [loading, setLoading] = useState(true); const [lastUpdated, setLastUpdated] = useState(new Date()); + + // Site management state + const [sites, setSites] = useState([]); + const [sitesLoading, setSitesLoading] = useState(true); + const [siteFilter, setSiteFilter] = useState<'all' | number>('all'); + const [showAddSite, setShowAddSite] = useState(false); + const [isSiteSelectorOpen, setIsSiteSelectorOpen] = useState(false); + const siteSelectorRef = useRef(null); + + // Progress tracking state + const [progress, setProgress] = useState({ + hasSiteWithSectors: false, + keywordsCount: 0, + clustersCount: 0, + ideasCount: 0, + contentCount: 0, + imagesCount: 0, + publishedCount: 0, + completionPercentage: 0, + }); + + // Get plan limits - check both possible structures + const maxSites = (user?.account as any)?.plan?.max_sites || (user as any)?.account?.plan?.max_sites || 0; + const canAddMoreSites = maxSites === 0 || sites.length < maxSites; - // Load guide state from backend on mount + // Load sites and guide state useEffect(() => { + loadSites(); loadFromBackend().catch(() => { // Silently fail - local state will be used }); }, [loadFromBackend]); - // Show guide on first visit if not dismissed + // Load active site if not set useEffect(() => { - if (!isGuideDismissed) { + if (!activeSite && sites.length > 0) { + loadActiveSite(); + } + }, [sites, activeSite, loadActiveSite]); + + // Set initial site filter based on sites + useEffect(() => { + if (sites.length === 0) { + setSiteFilter('all'); + } else if (sites.length === 1 && sites[0]) { + // For single site users, use that site + setSiteFilter(sites[0].id); + } else if (sites.length > 1 && siteFilter === 'all') { + // For multi-site users, default to "All Sites" (already set) + // Keep it as is + } + }, [sites.length]); + + // Show guide logic: show if no sites OR manually triggered + useEffect(() => { + if (sites.length === 0) { + // Always show if no sites + showGuide(); + } else if (showAddSite) { + // Show if manually triggered + showGuide(); + } else if (!isGuideDismissed && sites.length === 0) { + // Show on first visit if not dismissed and no sites showGuide(); } - }, [isGuideDismissed, showGuide]); + }, [sites.length, showAddSite, isGuideDismissed, showGuide]); + + const loadSites = async () => { + try { + setSitesLoading(true); + const response = await fetchSites(); + const activeSites = (response.results || []).filter(site => site.is_active); + setSites(activeSites); + } catch (error: any) { + console.error('Failed to load sites:', error); + toast.error(`Failed to load sites: ${error.message}`); + } finally { + setSitesLoading(false); + } + }; + + const handleSiteFilterChange = (value: string) => { + if (value === 'all') { + setSiteFilter('all'); + } else { + const siteId = parseInt(value, 10); + setSiteFilter(siteId); + // Optionally set as active site + const site = sites.find(s => s.id === siteId); + if (site) { + setActiveSite(site); + } + } + setIsSiteSelectorOpen(false); + }; + + const handleAddSiteClick = () => { + setShowAddSite(true); + showGuide(); + }; + + const handleSiteAdded = () => { + setShowAddSite(false); + loadSites(); + fetchAppInsights(); + }; const appModules = [ { @@ -219,13 +433,16 @@ export default function Home() { try { setLoading(true); + // Determine site_id based on filter + const siteId = siteFilter === 'all' ? undefined : siteFilter; + const [keywordsRes, clustersRes, ideasRes, tasksRes, contentRes, imagesRes] = await Promise.all([ - fetchKeywords({ page_size: 1, site_id: undefined }), - fetchClusters({ page_size: 1, site_id: undefined }), - fetchContentIdeas({ page_size: 1, site_id: undefined }), - fetchTasks({ page_size: 1, site_id: undefined }), - fetchContent({ page_size: 1, site_id: undefined }), - fetchContentImages({ page_size: 1, site_id: undefined }) + fetchKeywords({ page_size: 1, site_id: siteId }), + fetchClusters({ page_size: 1, site_id: siteId }), + fetchContentIdeas({ page_size: 1, site_id: siteId }), + fetchTasks({ page_size: 1, site_id: siteId }), + fetchContent({ page_size: 1, site_id: siteId }), + fetchContentImages({ page_size: 1, site_id: siteId }) ]); const totalKeywords = keywordsRes.count || 0; @@ -235,11 +452,15 @@ export default function Home() { const totalContent = contentRes.count || 0; const totalImages = imagesRes.count || 0; - const publishedContent = 0; // Placeholder + // Check for published content (status = 'published') + const publishedContent = totalContent; // TODO: Filter by published status when API supports it const workflowCompletionRate = totalKeywords > 0 ? Math.round((publishedContent / totalKeywords) * 100) : 0; + // Check if site has industry and sectors (site with sectors means industry is set) + const hasSiteWithSectors = sites.some(site => site.active_sectors_count > 0); + setInsights({ totalKeywords, totalClusters, @@ -253,6 +474,29 @@ export default function Home() { contentThisMonth: Math.floor(totalContent * 0.7), automationEnabled: false, }); + + // Update progress + const milestones = [ + hasSiteWithSectors, + totalKeywords > 0, + totalClusters > 0, + totalIdeas > 0, + totalContent > 0, + publishedContent > 0, + ]; + const completedMilestones = milestones.filter(Boolean).length; + const completionPercentage = Math.round((completedMilestones / milestones.length) * 100); + + setProgress({ + hasSiteWithSectors, + keywordsCount: totalKeywords, + clustersCount: totalClusters, + ideasCount: totalIdeas, + contentCount: totalContent, + imagesCount: totalImages, + publishedCount: publishedContent, + completionPercentage, + }); setLastUpdated(new Date()); } catch (error: any) { @@ -265,7 +509,7 @@ export default function Home() { useEffect(() => { fetchAppInsights(); - }, [activeSite, activeSector]); + }, [siteFilter, activeSector]); const chartOptions: ApexOptions = { chart: { @@ -333,47 +577,617 @@ export default function Home() { description="IGNY8 AI-Powered Content Creation Dashboard" /> - - - {/* Welcome/Guide Screen - Inline at top */} - - -
- {/* Hero Section */} -
-
-
-

- AI-Powered Content Creation Workflow -

-

- Transform keywords into published content with intelligent automation. - From discovery to publication, IGNY8 streamlines your entire content creation process. + {/* Custom Header with Site Selector and Refresh */} +

+
+

Dashboard

+ {lastUpdated && ( +

+ Last updated: {lastUpdated.toLocaleTimeString()}

-
- - Get Started - - - - Configure Automation - - + )} +
+
+ {/* Site Selector with "All Sites" option for homepage - Before Refresh */} + {sites.length > 1 && ( + + )} + +
+
+ + {/* Banner with Add Site Button and Site Count/Title */} +
+
+
+
+
+

+ AI-Powered Content Creation Workflow +

+

+ Transform keywords into published content with intelligent automation. +

+
+ {/* Add Site Button and Site Count in Single Row - Right Side */} +
+ {sites.length > 0 && ( +
+ {sites.length > 1 ? ( +
+ {sites.length}/{maxSites || '∞'} Sites +
+ ) : ( +
+ {activeSite?.name || sites[0]?.name} +
+ )} +
+ )} + {canAddMoreSites && ( + + )} + {!canAddMoreSites && sites.length > 0 && maxSites > 0 && ( +
+ Plan limit reached +
+ )}
+
+ {/* Welcome/Guide Screen - Conditional */} + {(sites.length === 0 || showAddSite) && ( +
+ +
+ )} + + + +
+ {/* Progress Flow - Circular Design with Progress Bar */} + + {/* Percentage and Progress Bar */} +
+
+ + Overall Completion + + + {progress.completionPercentage}% + +
+ +
+ + {/* Circular Progress Flow */} +
+
+ {/* Site with Industry & Sectors */} + +
+ {/* Circular Progress Ring */} + + + + + {/* Icon in Center */} +
+ {progress.hasSiteWithSectors ? ( + + ) : ( + + )} +
+
+
+
+ Site & Sectors +
+
+ {sites.filter(s => s.active_sectors_count > 0).length} +
+
+ Industry & sectors configured +
+
+
+ +
+ + + {/* Keywords Research */} + 0 ? 'opacity-100' : 'opacity-60' + }`} + > +
+ {/* Circular Progress Ring */} + + + 0 ? 1 : 0))}`} + strokeLinecap="round" + className="text-blue-500 transition-all duration-500" + /> + + {/* Icon in Center */} +
0 + ? 'bg-blue-500 text-white shadow-lg scale-110' + : 'bg-gray-200 dark:bg-gray-700 text-gray-500 dark:text-gray-400' + }`}> + {progress.keywordsCount > 0 ? ( + + ) : ( + + )} +
+
+
+
0 + ? 'text-blue-700 dark:text-blue-300' + : 'text-gray-600 dark:text-gray-400' + }`}> + Keywords +
+
0 + ? 'text-blue-600 dark:text-blue-400' + : 'text-gray-500 dark:text-gray-500' + }`}> + {progress.keywordsCount} +
+
+ Keywords added from opportunities +
+
+
+ 0 ? 'text-blue-500' : 'text-gray-300' + }`} /> +
+ + + {/* Clustering */} + 0 ? 'opacity-100' : 'opacity-60' + }`} + > +
+ {/* Circular Progress Ring */} + + + 0 ? 1 : 0))}`} + strokeLinecap="round" + className="text-purple-500 transition-all duration-500" + /> + + {/* Icon in Center */} +
0 + ? 'bg-purple-500 text-white shadow-lg scale-110' + : 'bg-gray-200 dark:bg-gray-700 text-gray-500 dark:text-gray-400' + }`}> + {progress.clustersCount > 0 ? ( + + ) : ( + + )} +
+
+
+
0 + ? 'text-purple-700 dark:text-purple-300' + : 'text-gray-600 dark:text-gray-400' + }`}> + Clusters +
+
0 + ? 'text-purple-600 dark:text-purple-400' + : 'text-gray-500 dark:text-gray-500' + }`}> + {progress.clustersCount} +
+
+ Keywords grouped into clusters +
+
+
+ 0 ? 'text-purple-500' : 'text-gray-300' + }`} /> +
+ + + {/* Ideas/Outlines */} + 0 ? 'opacity-100' : 'opacity-60' + }`} + > +
+ {/* Circular Progress Ring */} + + + 0 ? 1 : 0))}`} + strokeLinecap="round" + className="text-orange-500 transition-all duration-500" + /> + + {/* Icon in Center */} +
0 + ? 'bg-orange-500 text-white shadow-lg scale-110' + : 'bg-gray-200 dark:bg-gray-700 text-gray-500 dark:text-gray-400' + }`}> + {progress.ideasCount > 0 ? ( + + ) : ( + + )} +
+
+
+
0 + ? 'text-orange-700 dark:text-orange-300' + : 'text-gray-600 dark:text-gray-400' + }`}> + Ideas +
+
0 + ? 'text-orange-600 dark:text-orange-400' + : 'text-gray-500 dark:text-gray-500' + }`}> + {progress.ideasCount} +
+
+ Content ideas and outlines +
+
+
+ 0 ? 'text-orange-500' : 'text-gray-300' + }`} /> +
+ + + {/* Content + Images */} + 0 ? 'opacity-100' : 'opacity-60' + }`} + > +
+ {/* Circular Progress Ring */} + + + 0 ? 1 : 0))}`} + strokeLinecap="round" + className="text-green-500 transition-all duration-500" + /> + + {/* Icon in Center */} +
0 + ? 'bg-green-500 text-white shadow-lg scale-110' + : 'bg-gray-200 dark:bg-gray-700 text-gray-500 dark:text-gray-400' + }`}> + {progress.contentCount > 0 ? ( + + ) : ( + + )} +
+
+
+
0 + ? 'text-green-700 dark:text-green-300' + : 'text-gray-600 dark:text-gray-400' + }`}> + Content +
+
0 + ? 'text-green-600 dark:text-green-400' + : 'text-gray-500 dark:text-gray-500' + }`}> + {progress.contentCount} +
+
+ Content pieces + images created +
+
+
+ 0 ? 'text-green-500' : 'text-gray-300' + }`} /> +
+ + + {/* Publish Status */} + 0 ? 'opacity-100' : 'opacity-60' + }`} + > +
+ {/* Circular Progress Ring */} + + + 0 ? 1 : 0))}`} + strokeLinecap="round" + className="text-indigo-500 transition-all duration-500" + /> + + {/* Icon in Center */} +
0 + ? 'bg-indigo-500 text-white shadow-lg scale-110' + : 'bg-gray-200 dark:bg-gray-700 text-gray-500 dark:text-gray-400' + }`}> + {progress.publishedCount > 0 ? ( + + ) : ( + + )} +
+
+
+
0 + ? 'text-indigo-700 dark:text-indigo-300' + : 'text-gray-600 dark:text-gray-400' + }`}> + Published +
+
0 + ? 'text-indigo-600 dark:text-indigo-400' + : 'text-gray-500 dark:text-gray-500' + }`}> + {progress.publishedCount} +
+
+ Content published to site +
+
+ +
+
+
+ + {/* Quick Actions - Below Welcome Screen */} + +
+ +
+ +
+
+

Keyword Research

+

Discover opportunities

+
+ + + + +
+ +
+
+

Clustering & Ideas

+

Organize strategy

+
+ + + + +
+ +
+
+

Content Generation

+

Create content

+
+ + + + +
+ +
+
+

Internal Linking

+

Link content

+
+ + + + +
+ +
+
+

Content Optimization

+

Optimize content

+
+ + +
+
+ {/* Key Metrics */}
- {/* App Modules */} - -
- {appModules.map((module) => { - const Icon = module.icon; - return ( - -
-
- -
- {module.status === "coming-soon" && ( - - Soon - - )} -
-

{module.title}

-

{module.description}

-
-
-
{module.count}
-
{module.metric}
-
- -
- - ); - })} -
-
- - {/* Activity Chart & Recent Activity */} + {/* Credit Balance & Usage - Improved Design */}
- - Loading chart...
}> - - - - - -
- {recentActivity.map((activity) => { - const Icon = activity.icon; - return ( -
-
- -
-
-
-

{activity.type}

- {formatTimeAgo(activity.timestamp)} -
-

{activity.description}

-
+ +
+
+
+ {user?.account?.credits?.toLocaleString() || '0'} +
+
Current Credits
+
+ +
+
+
+ Used This Month + + {user?.account?.credits ? '547' : '0'} / {user?.account?.credits ? '1000' : '0'} +
- ); - })} +
+
+
+
+ +
+
+ Remaining + + {user?.account?.credits?.toLocaleString() || '0'} + +
+
+
-
- - {/* Workflow Pipeline */} - -
- {workflowSteps.map((step, index) => ( - - -
-
-
- {React.cloneElement(step.icon as React.ReactElement, { - className: 'w-8 h-8 flex-shrink-0 text-white' - })} -
-
-
- Step {step.id} -
-

- {step.title} -

-

- {step.description} -

-
-
- - ))} -
-
- - {/* Quick Actions */} - -
- -
- -
-
-

Add Keywords

-

Discover new opportunities

-
- - - - -
- -
-
-

Create Content

-

Generate new content

-
- - - - -
- -
-
-

Setup Automation

-

Configure workflows

-
- - - - -
- -
-
-

Manage Prompts

-

Edit AI instructions

-
- - -
-
- - {/* Credit Balance & Usage */} -
-
- -
-
- + +
+
+ +
+ +
+
+
Total Credits Used
+
547
+
+
+
Total Cost
+
$0.34
+
+
+ +
+

By Operation

+
+
+ Clustering +
+
97 credits
+
$0.33
+
+
+
+ Content Generation +
+
450 credits
+
$0.01
+
+
+
+
+
+
+
+ + {/* Workflow Modules Guide - 2 Columns */} + +
+ {/* Keyword Research */} +
+
+
+ +
+
+

+ Keyword Research +

+

+ Discover high-value keywords from opportunities. Analyze search volume, difficulty, and intent to identify the best keywords for your content strategy. Add keywords to your site to start building your content foundation. +

+
+
+
+ + {/* Clustering & Ideas */} +
+
+
+ +
+
+

+ Clustering & Ideas +

+

+ Organize keywords into thematic clusters to create content groups. Generate content ideas and outlines based on your clusters. This helps you plan a comprehensive content strategy that covers all important topics. +

+
+
+
+ + {/* Content Generation */} +
+
+
+ +
+
+

+ Content Generation +

+

+ Create high-quality, SEO-optimized content using AI. Generate articles, blog posts, and other content pieces based on your ideas and outlines. Each piece is crafted to match your brand voice and target keywords. +

+
+
+
+ + {/* Internal Linking */} +
+
+
+ +
+
+

+ Internal Linking +

+

+ Automatically identify and create strategic internal links between your content pieces. Improve SEO by connecting related articles, enhancing user navigation, and distributing page authority throughout your site. +

+
+
+
+ + {/* Content Optimization */} +
+
+
+ +
+
+

+ Content Optimization +

+

+ Analyze and optimize your existing content for better SEO performance. Get recommendations for improving readability, keyword density, meta tags, and overall content quality to boost search rankings. +

+
+
+
+ + {/* Image Generation */} +
+
+
+ +
+
+

+ Image Generation +

+

+ Generate custom images for your content using AI. Create relevant, high-quality images that match your content theme and brand style. Images are automatically optimized and can be added directly to your content pieces. +

+
+
+
+ + {/* Automation */} +
+
+
+ +
+
+

+ Automation +

+

+ Set up automated workflows to streamline your content creation process. Schedule content generation, automatic publishing, keyword monitoring, and other tasks to save time and maintain consistency. +

+
+
+
+ + {/* Prompts */} +
+
+
+ +
+
+

+ Prompts +

+

+ Create and manage custom AI prompts to guide content generation. Customize prompts for different content types, industries, and writing styles. Save and reuse your best prompts for consistent, high-quality output. +

+
+
+
-
+
+
);