import { useState, useEffect, useCallback } from 'react'; import PageMeta from '../../components/common/PageMeta'; import SiteCard from '../../components/common/SiteCard'; import FormModal, { FormField } from '../../components/common/FormModal'; import Button from '../../components/ui/button/Button'; import { useToast } from '../../components/ui/toast/ToastContainer'; import Alert from '../../components/ui/alert/Alert'; import Select from '../../components/form/Select'; import Checkbox from '../../components/form/input/Checkbox'; import { fetchSites, createSite, updateSite, deleteSite, setActiveSite, selectSectorsForSite, fetchIndustries, fetchSiteSectors, Site, Industry, Sector, } from '../../services/api'; import Badge from '../../components/ui/badge/Badge'; // Site Icon SVG - Globe const SiteIcon = () => ( ); export default function Sites() { const toast = useToast(); const [sites, setSites] = useState([]); const [loading, setLoading] = useState(true); const [selectedSite, setSelectedSite] = useState(null); const [showSiteModal, setShowSiteModal] = useState(false); const [showSectorsModal, setShowSectorsModal] = useState(false); const [showDetailsModal, setShowDetailsModal] = useState(false); const [isSaving, setIsSaving] = useState(false); const [togglingSiteId, setTogglingSiteId] = useState(null); const [industries, setIndustries] = useState([]); const [selectedIndustry, setSelectedIndustry] = useState(''); const [selectedSectors, setSelectedSectors] = useState([]); const [isSelectingSectors, setIsSelectingSectors] = useState(false); // Form state for site creation/editing const [formData, setFormData] = useState({ name: '', domain: '', description: '', is_active: true, // Default to true to match backend model default industry: undefined as number | undefined, // Industry ID - required by backend }); // Load sites and industries useEffect(() => { loadSites(); loadIndustries(); }, []); const loadSites = async () => { try { setLoading(true); const response = await fetchSites(); setSites(response.results || []); } catch (error: any) { toast.error(`Failed to load sites: ${error.message}`); } finally { setLoading(false); } }; const loadIndustries = async () => { try { const response = await fetchIndustries(); setIndustries(response.industries || []); } catch (error: any) { toast.error(`Failed to load industries: ${error.message}`); } }; const handleToggle = async (siteId: number, enabled: boolean) => { // Prevent multiple simultaneous toggle operations if (togglingSiteId !== null) { toast.error('Please wait for the current operation to complete'); return; } try { setTogglingSiteId(siteId); if (enabled) { // Activate site (multiple sites can be active simultaneously) await setActiveSite(siteId); toast.success('Site activated successfully'); } else { // Deactivate site - only this specific site const site = sites.find(s => s.id === siteId); if (site) { await updateSite(siteId, { is_active: false }); toast.success('Site deactivated successfully'); } } await loadSites(); } catch (error: any) { toast.error(`Failed to update site: ${error.message}`); } finally { setTogglingSiteId(null); } }; const handleSettings = (site: Site) => { setSelectedSite(site); setShowSectorsModal(true); // Load current sectors for this site loadSiteSectors(site); }; const loadSiteSectors = async (site: Site) => { try { const sectors = await fetchSiteSectors(site.id); const sectorSlugs = sectors.map((s: any) => s.slug); setSelectedSectors(sectorSlugs); // Use site's industry if available, otherwise try to determine from sectors if (site.industry_slug) { setSelectedIndustry(site.industry_slug); } else { // Fallback: try to determine industry from sectors for (const industry of industries) { const matchingSectors = industry.sectors.filter(s => sectorSlugs.includes(s.slug)); if (matchingSectors.length > 0) { setSelectedIndustry(industry.slug); break; } } } } catch (error: any) { console.error('Failed to load site sectors:', error); } }; const handleDetails = (site: Site) => { setSelectedSite(site); setFormData({ name: site.name || '', domain: site.domain || '', description: site.description || '', is_active: site.is_active || false, industry: site.industry, }); setShowDetailsModal(true); }; const handleSaveDetails = async () => { if (!selectedSite) return; try { setIsSaving(true); // Normalize domain before sending const normalizedFormData = { ...formData, domain: formData.domain ? normalizeDomain(formData.domain) : formData.domain, }; await updateSite(selectedSite.id, normalizedFormData); toast.success('Site updated successfully'); setShowDetailsModal(false); await loadSites(); } catch (error: any) { toast.error(`Failed to update site: ${error.message}`); } finally { setIsSaving(false); } }; const handleCreateSite = () => { setSelectedSite(null); setFormData({ name: '', domain: '', description: '', is_active: true, // Default to true to match backend model default industry: undefined, }); setShowSiteModal(true); }; const handleEditSite = (site: Site) => { setSelectedSite(site); setFormData({ name: site.name || '', domain: site.domain || '', description: site.description || '', is_active: site.is_active || false, industry: site.industry, }); setShowSiteModal(true); }; // Helper function to normalize domain URL const normalizeDomain = (domain: string): string => { if (!domain || !domain.trim()) { return domain; } const trimmed = domain.trim(); // If it already starts with https://, keep it as is if (trimmed.startsWith('https://')) { return trimmed; } // If it starts with http://, replace with https:// if (trimmed.startsWith('http://')) { return trimmed.replace('http://', 'https://'); } // Otherwise, add https:// return `https://${trimmed}`; }; const handleSaveSite = async () => { try { setIsSaving(true); // Normalize domain before sending const normalizedFormData = { ...formData, domain: formData.domain ? normalizeDomain(formData.domain) : formData.domain, }; if (selectedSite) { // Update existing site await updateSite(selectedSite.id, normalizedFormData); toast.success('Site updated successfully'); } else { // Create new site const newSite = await createSite({ ...normalizedFormData, is_active: normalizedFormData.is_active || false, }); toast.success('Site created successfully'); // If this is the first site or user wants it active, activate it if (sites.length === 0 || normalizedFormData.is_active) { await setActiveSite(newSite.id); } } setShowSiteModal(false); setSelectedSite(null); setFormData({ name: '', domain: '', description: '', is_active: false, industry: undefined, }); await loadSites(); } catch (error: any) { toast.error(`Failed to save site: ${error.message}`); } finally { setIsSaving(false); } }; const handleSelectSectors = async () => { if (!selectedSite || !selectedIndustry || selectedSectors.length === 0) { toast.error('Please select an industry and at least one sector'); return; } if (selectedSectors.length > 5) { toast.error('Maximum 5 sectors allowed per site'); return; } try { setIsSelectingSectors(true); const result = await selectSectorsForSite( selectedSite.id, selectedIndustry, selectedSectors ); toast.success(result.message || 'Sectors selected successfully'); setShowSectorsModal(false); await loadSites(); } catch (error: any) { toast.error(`Failed to select sectors: ${error.message}`); } finally { setIsSelectingSectors(false); } }; const handleDeleteSite = async (site: Site) => { if (!window.confirm(`Are you sure you want to delete "${site.name}"? This action cannot be undone.`)) { return; } try { await deleteSite(site.id); toast.success('Site deleted successfully'); await loadSites(); if (showDetailsModal) { setShowDetailsModal(false); } } catch (error: any) { toast.error(`Failed to delete site: ${error.message}`); } }; const getSiteFormFields = (): FormField[] => [ { key: 'name', label: 'Site Name', type: 'text', value: formData.name, onChange: (value: any) => setFormData({ ...formData, name: value }), required: true, placeholder: 'Enter site name', }, { key: 'industry', label: 'Industry', type: 'select', value: formData.industry?.toString() || '', onChange: (value: any) => setFormData({ ...formData, industry: value ? parseInt(value) : undefined }), required: true, placeholder: 'Select an industry', options: [ { value: '', label: 'Select an industry...' }, ...industries.map(industry => ({ value: industry.id.toString(), label: industry.name, })), ], }, { key: 'domain', label: 'Domain', type: 'text', value: formData.domain, onChange: (value: any) => setFormData({ ...formData, domain: value }), required: false, placeholder: 'example.com (https:// will be added automatically)', }, { key: 'description', label: 'Description', type: 'textarea', value: formData.description, onChange: (value: any) => setFormData({ ...formData, description: value }), required: false, placeholder: 'Enter site description', rows: 4, }, { key: 'is_active', label: 'Set as Active Site', type: 'select', value: formData.is_active ? 'true' : 'false', onChange: (value: any) => setFormData({ ...formData, is_active: value === 'true' }), required: false, options: [ { value: 'true', label: 'Active' }, { value: 'false', label: 'Inactive' }, ], }, ]; const getIndustrySectors = () => { if (!selectedIndustry) return []; const industry = industries.find(i => i.slug === selectedIndustry); return industry?.sectors || []; }; return ( <>

Your Websites

Manage all your websites here - Add new sites, configure settings, and track content for each one

{/* Info Alert */} {/* Sites Grid */} {sites.length === 0 ? (

No sites yet

Create your first site to get started

) : (
{sites.map((site) => ( } onToggle={handleToggle} onSettings={handleSettings} onDetails={handleDetails} isToggling={togglingSiteId === site.id} /> ))}
)} {/* Create/Edit Site Modal */} { setShowSiteModal(false); setSelectedSite(null); setFormData({ name: '', domain: '', description: '', is_active: false, industry: undefined, }); }} onSubmit={handleSaveSite} title={selectedSite ? 'Edit Site' : 'Create New Site'} submitLabel={selectedSite ? 'Update Site' : 'Create Site'} fields={getSiteFormFields()} isLoading={isSaving} /> {/* Sectors Selection Modal */} setShowSectorsModal(false)} onSubmit={handleSelectSectors} title={selectedSite ? `Configure Sectors for ${selectedSite.name}` : 'Configure Sectors'} submitLabel={isSelectingSectors ? 'Saving...' : 'Save Sectors'} cancelLabel="Cancel" isLoading={isSelectingSectors} className="max-w-2xl" customBody={