Section 2 Part 3

This commit is contained in:
IGNY8 VPS (Salman)
2026-01-03 08:11:41 +00:00
parent 935c7234b1
commit 4d6ee21408
15 changed files with 1209 additions and 895 deletions

View File

@@ -24,19 +24,23 @@ import {
fetchIndustries,
Site,
Industry,
setActiveSite as apiSetActiveSite,
} from '../../services/api';
import { useSiteStore } from '../../store/siteStore';
import WordPressIntegrationForm from '../../components/sites/WordPressIntegrationForm';
import { integrationApi, SiteIntegration } from '../../services/integration.api';
import { GridIcon, PlugInIcon, PaperPlaneIcon, DocsIcon, BoltIcon, FileIcon, ChevronDownIcon, CloseIcon, PlusIcon, RefreshCwIcon, FileTextIcon, ImageIcon, SaveIcon, Loader2Icon } from '../../icons';
import { GridIcon, PlugInIcon, PaperPlaneIcon, DocsIcon, BoltIcon, FileIcon, ChevronDownIcon, CloseIcon, PlusIcon, RefreshCwIcon, FileTextIcon, ImageIcon, SaveIcon, Loader2Icon, ArrowRightIcon, SettingsIcon, GlobeIcon, LayersIcon } from '../../icons';
import Badge from '../../components/ui/badge/Badge';
import { Dropdown } from '../../components/ui/dropdown/Dropdown';
import { DropdownItem } from '../../components/ui/dropdown/DropdownItem';
import SiteInfoBar from '../../components/common/SiteInfoBar';
export default function SiteSettings() {
const { id: siteId } = useParams<{ id: string }>();
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const toast = useToast();
const { setActiveSite } = useSiteStore();
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [site, setSite] = useState<any>(null);
@@ -55,6 +59,9 @@ export default function SiteSettings() {
const [contentTypes, setContentTypes] = useState<any>(null);
const [contentTypesLoading, setContentTypesLoading] = useState(false);
// Advanced Settings toggle
const [showAdvancedSettings, setShowAdvancedSettings] = useState(false);
// Publishing settings state
const [publishingSettings, setPublishingSettings] = useState<any>(null);
const [publishingSettingsLoading, setPublishingSettingsLoading] = useState(false);
@@ -281,6 +288,10 @@ export default function SiteSettings() {
const data = await fetchAPI(`/v1/auth/sites/${siteId}/`);
if (data) {
setSite(data);
// Update global site store so site selector shows correct site
setActiveSite(data);
// Also set as active site in backend
await apiSetActiveSite(data.id).catch(() => {});
const seoData = data.seo_metadata || data.metadata || {};
setFormData({
name: data.name || '',
@@ -746,116 +757,46 @@ export default function SiteSettings() {
return (
<div className="p-6">
<PageMeta title="Site Settings - IGNY8" />
<PageHeader
title="Site Settings"
badge={{ icon: <GridIcon />, color: 'blue' }}
hideSiteSector
/>
<div className="flex items-center justify-between gap-4 mb-6">
<div className="flex items-center gap-4">
<PageHeader
title={site?.name ? `${site.name} — Site Settings` : 'Site Settings'}
badge={{ icon: <GridIcon />, color: 'blue' }}
hideSiteSector
/>
{/* Integration status indicator */}
<div className="flex items-center gap-3 ml-2">
<span
className={`inline-block w-6 h-6 rounded-full ${
integrationStatus === 'connected' ? 'bg-success-500' :
integrationStatus === 'configured' ? 'bg-brand-500' : 'bg-gray-300'
}`}
title={`Integration status: ${
integrationStatus === 'connected' ? 'Connected & Authenticated' :
integrationStatus === 'configured' ? 'Configured (testing...)' : 'Not configured'
}`}
/>
<span className="text-sm text-gray-600 dark:text-gray-300">
{integrationStatus === 'connected' && 'Connected'}
{integrationStatus === 'configured' && (testingAuth ? 'Testing...' : 'Configured')}
{integrationStatus === 'not_configured' && 'Not configured'}
</span>
</div>
</div>
{/* Site Selector - Only show if more than 1 site */}
{!sitesLoading && sites.length > 1 && (
<div className="relative inline-block">
<Button
ref={siteSelectorRef}
onClick={() => setIsSiteSelectorOpen(!isSiteSelectorOpen)}
variant="outline"
className="flex items-center gap-2"
aria-label="Switch site"
>
<span className="flex items-center gap-2">
<GridIcon className="w-4 h-4 text-brand-500 dark:text-brand-400" />
<span className="max-w-[150px] truncate">{site?.name || 'Select Site'}</span>
</span>
<ChevronDownIcon className={`w-4 h-4 text-brand-500 dark:text-brand-400 transition-transform ${isSiteSelectorOpen ? 'rotate-180' : ''}`} />
</Button>
<Dropdown
isOpen={isSiteSelectorOpen}
onClose={() => setIsSiteSelectorOpen(false)}
anchorRef={siteSelectorRef}
>
{sites.map((s) => (
<DropdownItem
key={s.id}
onItemClick={() => handleSiteSelect(s.id)}
className={`flex items-center gap-3 px-3 py-2 font-medium rounded-lg text-sm text-left ${
site?.id === s.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"
}`}
>
<span className="flex-1">{s.name}</span>
{site?.id === s.id && (
<svg
className="w-4 h-4 text-brand-600 dark:text-brand-400"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
fillRule="evenodd"
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
clipRule="evenodd"
/>
</svg>
)}
</DropdownItem>
))}
</Dropdown>
</div>
)}
</div>
{/* Site Info Bar */}
<SiteInfoBar site={site} currentPage="settings" />
{/* Tabs */}
<div className="mb-6 border-b border-gray-200 dark:border-gray-700">
<div className="flex gap-4 overflow-x-auto">
<Button
variant="ghost"
onClick={() => {
setActiveTab('general');
navigate(`/sites/${siteId}/settings`, { replace: true });
}}
className={`px-4 py-2 font-medium border-b-2 rounded-none transition-colors whitespace-nowrap ${
activeTab === 'general'
? 'border-brand-500 text-brand-600 dark:text-brand-400'
: 'border-transparent text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300'
}`}
startIcon={<GridIcon className="w-4 h-4" />}
>
General
</Button>
<Button
variant="ghost"
onClick={() => {
setActiveTab('content-generation');
<div className="flex items-center justify-between gap-4">
<div className="flex gap-4 overflow-x-auto">
<Button
variant="ghost"
onClick={() => {
setActiveTab('general');
navigate(`/sites/${siteId}/settings`, { replace: true });
}}
className={`px-4 py-2 font-medium border-b-2 rounded-none transition-colors whitespace-nowrap ${
activeTab === 'general'
? 'border-brand-500 text-brand-600 dark:text-brand-400'
: 'border-transparent text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300'
}`}
startIcon={<GridIcon className={`w-4 h-4 ${activeTab === 'general' ? 'text-brand-500' : ''}`} />}
>
General
</Button>
<Button
variant="ghost"
onClick={() => {
setActiveTab('content-generation');
navigate(`/sites/${siteId}/settings?tab=content-generation`, { replace: true });
}}
className={`px-4 py-2 font-medium border-b-2 rounded-none transition-colors whitespace-nowrap ${
activeTab === 'content-generation'
? 'border-brand-500 text-brand-600 dark:text-brand-400'
? 'border-success-500 text-success-600 dark:text-success-400'
: 'border-transparent text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300'
}`}
startIcon={<FileTextIcon className="w-4 h-4" />}
startIcon={<FileTextIcon className={`w-4 h-4 ${activeTab === 'content-generation' ? 'text-success-500' : ''}`} />}
>
Content
</Button>
@@ -867,10 +808,10 @@ export default function SiteSettings() {
}}
className={`px-4 py-2 font-medium border-b-2 rounded-none transition-colors whitespace-nowrap ${
activeTab === 'image-settings'
? 'border-brand-500 text-brand-600 dark:text-brand-400'
? 'border-purple-500 text-purple-600 dark:text-purple-400'
: 'border-transparent text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300'
}`}
startIcon={<ImageIcon className="w-4 h-4" />}
startIcon={<ImageIcon className={`w-4 h-4 ${activeTab === 'image-settings' ? 'text-purple-500' : ''}`} />}
>
Images
</Button>
@@ -882,10 +823,10 @@ export default function SiteSettings() {
}}
className={`px-4 py-2 font-medium border-b-2 rounded-none transition-colors whitespace-nowrap ${
activeTab === 'integrations'
? 'border-brand-500 text-brand-600 dark:text-brand-400'
? 'border-warning-500 text-warning-600 dark:text-warning-400'
: 'border-transparent text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300'
}`}
startIcon={<PlugInIcon className="w-4 h-4" />}
startIcon={<PlugInIcon className={`w-4 h-4 ${activeTab === 'integrations' ? 'text-warning-500' : ''}`} />}
>
Integrations
</Button>
@@ -897,10 +838,10 @@ export default function SiteSettings() {
}}
className={`px-4 py-2 font-medium border-b-2 rounded-none transition-colors whitespace-nowrap ${
activeTab === 'publishing'
? 'border-brand-500 text-brand-600 dark:text-brand-400'
? 'border-info-500 text-info-600 dark:text-info-400'
: 'border-transparent text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300'
}`}
startIcon={<PaperPlaneIcon className="w-4 h-4" />}
startIcon={<PaperPlaneIcon className={`w-4 h-4 ${activeTab === 'publishing' ? 'text-info-500' : ''}`} />}
>
Publishing
</Button>
@@ -913,21 +854,37 @@ export default function SiteSettings() {
}}
className={`px-4 py-2 font-medium border-b-2 rounded-none transition-colors whitespace-nowrap ${
activeTab === 'content-types'
? 'border-brand-500 text-brand-600 dark:text-brand-400'
? 'border-error-500 text-error-600 dark:text-error-400'
: 'border-transparent text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300'
}`}
startIcon={<FileIcon className="w-4 h-4" />}
startIcon={<FileIcon className={`w-4 h-4 ${activeTab === 'content-types' ? 'text-error-500' : ''}`} />}
>
Content Types
</Button>
)}
</div>
{/* Integration Status Indicator - Larger */}
<div className="flex items-center gap-3 px-4 py-2 rounded-lg bg-gray-50 dark:bg-gray-800 flex-shrink-0">
<span
className={`inline-block w-4 h-4 rounded-full ${
integrationStatus === 'connected' ? 'bg-success-500' :
integrationStatus === 'configured' ? 'bg-brand-500' : 'bg-gray-300'
}`}
/>
<span className="text-sm font-medium text-gray-700 dark:text-gray-200">
{integrationStatus === 'connected' && 'Connected'}
{integrationStatus === 'configured' && (testingAuth ? 'Testing...' : 'Configured')}
{integrationStatus === 'not_configured' && 'Not Configured'}
</span>
</div>
</div>
</div>
{/* Content Generation Tab */}
{activeTab === 'content-generation' && (
<div className="space-y-6 max-w-4xl">
<Card className="p-6">
<Card className="p-6 border-l-4 border-l-brand-500">
<div className="flex items-center gap-3 mb-6">
<div className="p-2 bg-brand-100 dark:bg-brand-900/30 rounded-lg">
<FileTextIcon className="w-5 h-5 text-brand-600 dark:text-brand-400" />
@@ -1010,7 +967,7 @@ export default function SiteSettings() {
{/* Image Settings Tab */}
{activeTab === 'image-settings' && (
<div className="space-y-6 max-w-4xl">
<Card className="p-6">
<Card className="p-6 border-l-4 border-l-purple-500">
<div className="flex items-center gap-3 mb-6">
<div className="p-2 bg-purple-100 dark:bg-purple-900/30 rounded-lg">
<ImageIcon className="w-5 h-5 text-purple-600 dark:text-purple-400" />
@@ -1533,10 +1490,10 @@ export default function SiteSettings() {
{/* General Tab */}
{activeTab === 'general' && (
<>
{/* 4-Card Layout for Basic Settings, SEO, Open Graph, and Schema */}
{/* Row 1: Basic Settings and Industry/Sectors side by side */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
{/* Card 1: Basic Site Settings */}
<Card className="p-6">
<Card className="p-6 border-l-4 border-l-brand-500">
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
<GridIcon className="w-5 h-5 text-brand-500" />
Basic Settings
@@ -1598,289 +1555,316 @@ export default function SiteSettings() {
</div>
</Card>
{/* Card 2: SEO Meta Tags */}
<Card className="p-6">
{/* Card 2: Industry & Sectors Configuration */}
<Card className="p-6 border-l-4 border-l-info-500">
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
<DocsIcon className="w-5 h-5 text-brand-500" />
SEO Meta Tags
<LayersIcon className="w-5 h-5 text-info-500" />
Industry & Sectors
</h3>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-4">
Configure up to 5 sectors for content targeting.
</p>
<div className="space-y-4">
<div>
<Label>Meta Title</Label>
<InputField
type="text"
value={formData.meta_title}
onChange={(e) => setFormData({ ...formData, meta_title: e.target.value })}
placeholder="SEO title (recommended: 50-60 characters)"
max="60"
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Select Industry
</label>
<Select
options={industries.map((industry) => ({
value: industry.slug,
label: industry.name,
}))}
placeholder="Select an industry..."
defaultValue={selectedIndustry}
onChange={(value) => {
setSelectedIndustry(value);
setSelectedSectors([]);
}}
/>
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
{formData.meta_title.length}/60 characters
</p>
{selectedIndustry && (
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
{industries.find(i => i.slug === selectedIndustry)?.description}
</p>
)}
</div>
<div>
<Label>Meta Description</Label>
<TextArea
value={formData.meta_description}
onChange={(value) => setFormData({ ...formData, meta_description: value })}
rows={4}
placeholder="SEO description (recommended: 150-160 characters)"
maxLength={160}
className="mt-1"
/>
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
{formData.meta_description.length}/160 characters
</p>
</div>
{selectedIndustry && (
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Select Sectors (max 5)
</label>
<div className="space-y-2 max-h-48 overflow-y-auto border border-gray-200 rounded-lg p-3 dark:border-gray-700">
{getIndustrySectors().map((sector) => (
<div
key={sector.slug}
className="flex items-start space-x-3 p-2 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800"
>
<Checkbox
checked={selectedSectors.includes(sector.slug)}
onChange={(checked) => {
if (checked) {
if (selectedSectors.length >= 5) {
toast.error('Maximum 5 sectors allowed per site');
return;
}
setSelectedSectors([...selectedSectors, sector.slug]);
} else {
setSelectedSectors(selectedSectors.filter(s => s !== sector.slug));
}
}}
/>
<div className="flex-1">
<div className="font-medium text-sm text-gray-900 dark:text-white">
{sector.name}
</div>
<div className="text-xs text-gray-500 dark:text-gray-400 mt-0.5 line-clamp-1">
{sector.description}
</div>
</div>
</div>
))}
</div>
<p className="mt-2 text-xs text-gray-500 dark:text-gray-400">
Selected: {selectedSectors.length} / 5 sectors
</p>
</div>
)}
<div>
<Label>Meta Keywords (comma-separated)</Label>
<InputField
type="text"
value={formData.meta_keywords}
onChange={(e) => setFormData({ ...formData, meta_keywords: e.target.value })}
placeholder="keyword1, keyword2, keyword3"
/>
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
Separate keywords with commas
</p>
</div>
</div>
</Card>
{/* Card 3: Open Graph */}
<Card className="p-6">
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
<PaperPlaneIcon className="w-5 h-5 text-brand-500" />
Open Graph
</h3>
<div className="space-y-4">
<div>
<Label>OG Title</Label>
<InputField
type="text"
value={formData.og_title}
onChange={(e) => setFormData({ ...formData, og_title: e.target.value })}
placeholder="Open Graph title"
/>
</div>
<div>
<Label>OG Description</Label>
<TextArea
value={formData.og_description}
onChange={(value) => setFormData({ ...formData, og_description: value })}
rows={3}
placeholder="Open Graph description"
className="mt-1"
/>
</div>
<div>
<Label>OG Image URL</Label>
<InputField
type="url"
value={formData.og_image}
onChange={(e) => setFormData({ ...formData, og_image: e.target.value })}
placeholder="https://example.com/image.jpg"
/>
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
Recommended: 1200x630px image
</p>
</div>
<div>
<Label>OG Type</Label>
<SelectDropdown
options={[
{ value: 'website', label: 'Website' },
{ value: 'article', label: 'Article' },
{ value: 'business.business', label: 'Business' },
{ value: 'product', label: 'Product' },
]}
value={formData.og_type}
onChange={(value) => setFormData({ ...formData, og_type: value })}
/>
</div>
<div>
<Label>OG Site Name</Label>
<InputField
type="text"
value={formData.og_site_name}
onChange={(e) => setFormData({ ...formData, og_site_name: e.target.value })}
placeholder="Site name for social sharing"
/>
</div>
</div>
</Card>
{/* Card 4: Schema.org */}
<Card className="p-6">
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
<BoltIcon className="w-5 h-5 text-brand-500" />
Schema.org
</h3>
<div className="space-y-4">
<div>
<Label>Schema Type</Label>
<SelectDropdown
options={[
{ value: 'Organization', label: 'Organization' },
{ value: 'LocalBusiness', label: 'Local Business' },
{ value: 'WebSite', label: 'Website' },
{ value: 'Corporation', label: 'Corporation' },
{ value: 'NGO', label: 'NGO' },
]}
value={formData.schema_type}
onChange={(value) => setFormData({ ...formData, schema_type: value })}
/>
</div>
<div>
<Label>Schema Name</Label>
<InputField
type="text"
value={formData.schema_name}
onChange={(e) => setFormData({ ...formData, schema_name: e.target.value })}
placeholder="Organization name"
/>
</div>
<div>
<Label>Schema Description</Label>
<TextArea
value={formData.schema_description}
onChange={(value) => setFormData({ ...formData, schema_description: value })}
rows={3}
placeholder="Organization description"
className="mt-1"
/>
</div>
<div>
<Label>Schema URL</Label>
<InputField
type="url"
value={formData.schema_url}
onChange={(e) => setFormData({ ...formData, schema_url: e.target.value })}
placeholder="https://example.com"
/>
</div>
<div>
<Label>Schema Logo URL</Label>
<InputField
type="url"
value={formData.schema_logo}
onChange={(e) => setFormData({ ...formData, schema_logo: e.target.value })}
placeholder="https://example.com/logo.png"
/>
</div>
<div>
<Label>Same As URLs (comma-separated)</Label>
<InputField
type="text"
value={formData.schema_same_as}
onChange={(e) => setFormData({ ...formData, schema_same_as: e.target.value })}
placeholder="https://facebook.com/page, https://twitter.com/page"
/>
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
Social media profiles and other related URLs
</p>
</div>
{selectedIndustry && selectedSectors.length > 0 && (
<div className="flex justify-end">
<Button
onClick={handleSelectSectors}
variant="primary"
size="sm"
disabled={isSelectingSectors}
>
{isSelectingSectors ? 'Saving...' : 'Save Sectors'}
</Button>
</div>
)}
</div>
</Card>
</div>
{/* Sectors Configuration Section */}
<Card className="p-6">
<h3 className="text-lg font-semibold mb-4">Industry & Sectors Configuration</h3>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-6">
Configure up to 5 sectors from your selected industry. Keywords and clusters are automatically associated with sectors.
</p>
<div className="space-y-6">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Select Industry
</label>
<Select
options={industries.map((industry) => ({
value: industry.slug,
label: industry.name,
}))}
placeholder="Select an industry..."
defaultValue={selectedIndustry}
onChange={(value) => {
setSelectedIndustry(value);
setSelectedSectors([]);
}}
/>
{selectedIndustry && (
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
{industries.find(i => i.slug === selectedIndustry)?.description}
</p>
)}
</div>
{/* Advanced Settings Toggle */}
<div className="mb-6">
<Button
variant="outline"
onClick={() => setShowAdvancedSettings(!showAdvancedSettings)}
className="w-full justify-between"
endIcon={<ChevronDownIcon className={`w-4 h-4 transition-transform ${showAdvancedSettings ? 'rotate-180' : ''}`} />}
>
<span className="flex items-center gap-2">
<SettingsIcon className="w-4 h-4" />
Advanced Settings (SEO, Open Graph, Schema)
</span>
</Button>
</div>
{selectedIndustry && (
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Select Sectors (max 5)
</label>
<div className="space-y-2 max-h-64 overflow-y-auto border border-gray-200 rounded-lg p-4 dark:border-gray-700">
{getIndustrySectors().map((sector) => (
<div
key={sector.slug}
className="flex items-start space-x-3 p-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800"
>
<Checkbox
checked={selectedSectors.includes(sector.slug)}
onChange={(checked) => {
if (checked) {
if (selectedSectors.length >= 5) {
toast.error('Maximum 5 sectors allowed per site');
return;
}
setSelectedSectors([...selectedSectors, sector.slug]);
} else {
setSelectedSectors(selectedSectors.filter(s => s !== sector.slug));
}
}}
/>
<div className="flex-1">
<div className="font-medium text-sm text-gray-900 dark:text-white">
{sector.name}
</div>
<div className="text-xs text-gray-500 dark:text-gray-400 mt-1">
{sector.description}
</div>
</div>
</div>
))}
{/* Advanced Settings - 3 Column Grid */}
{showAdvancedSettings && (
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-6">
{/* SEO Meta Tags */}
<Card className="p-6 border-l-4 border-l-success-500">
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
<DocsIcon className="w-5 h-5 text-success-500" />
SEO Meta Tags
</h3>
<div className="space-y-4">
<div>
<Label>Meta Title</Label>
<InputField
type="text"
value={formData.meta_title}
onChange={(e) => setFormData({ ...formData, meta_title: e.target.value })}
placeholder="SEO title (50-60 chars)"
max="60"
/>
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
{formData.meta_title.length}/60 characters
</p>
</div>
<p className="mt-2 text-xs text-gray-500 dark:text-gray-400">
Selected: {selectedSectors.length} / 5 sectors
</p>
</div>
)}
{selectedIndustry && selectedSectors.length > 0 && (
<div className="flex justify-end">
<Button
onClick={handleSelectSectors}
variant="primary"
disabled={isSelectingSectors}
>
{isSelectingSectors ? 'Saving Sectors...' : 'Save Sectors'}
</Button>
<div>
<Label>Meta Description</Label>
<TextArea
value={formData.meta_description}
onChange={(value) => setFormData({ ...formData, meta_description: value })}
rows={3}
placeholder="SEO description (150-160 chars)"
maxLength={160}
className="mt-1"
/>
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
{formData.meta_description.length}/160 characters
</p>
</div>
<div>
<Label>Meta Keywords</Label>
<InputField
type="text"
value={formData.meta_keywords}
onChange={(e) => setFormData({ ...formData, meta_keywords: e.target.value })}
placeholder="keyword1, keyword2"
/>
</div>
</div>
)}
</Card>
{/* Open Graph */}
<Card className="p-6 border-l-4 border-l-purple-500">
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
<PaperPlaneIcon className="w-5 h-5 text-purple-500" />
Open Graph
</h3>
<div className="space-y-4">
<div>
<Label>OG Title</Label>
<InputField
type="text"
value={formData.og_title}
onChange={(e) => setFormData({ ...formData, og_title: e.target.value })}
placeholder="Open Graph title"
/>
</div>
<div>
<Label>OG Description</Label>
<TextArea
value={formData.og_description}
onChange={(value) => setFormData({ ...formData, og_description: value })}
rows={3}
placeholder="Open Graph description"
className="mt-1"
/>
</div>
<div>
<Label>OG Image URL</Label>
<InputField
type="url"
value={formData.og_image}
onChange={(e) => setFormData({ ...formData, og_image: e.target.value })}
placeholder="https://example.com/image.jpg"
/>
</div>
<div>
<Label>OG Type</Label>
<SelectDropdown
options={[
{ value: 'website', label: 'Website' },
{ value: 'article', label: 'Article' },
{ value: 'business.business', label: 'Business' },
{ value: 'product', label: 'Product' },
]}
value={formData.og_type}
onChange={(value) => setFormData({ ...formData, og_type: value })}
/>
</div>
<div>
<Label>OG Site Name</Label>
<InputField
type="text"
value={formData.og_site_name}
onChange={(e) => setFormData({ ...formData, og_site_name: e.target.value })}
placeholder="Site name"
/>
</div>
</div>
</Card>
{/* Schema.org */}
<Card className="p-6 border-l-4 border-l-warning-500">
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
<BoltIcon className="w-5 h-5 text-warning-500" />
Schema.org
</h3>
<div className="space-y-4">
<div>
<Label>Schema Type</Label>
<SelectDropdown
options={[
{ value: 'Organization', label: 'Organization' },
{ value: 'LocalBusiness', label: 'Local Business' },
{ value: 'WebSite', label: 'Website' },
{ value: 'Corporation', label: 'Corporation' },
{ value: 'NGO', label: 'NGO' },
]}
value={formData.schema_type}
onChange={(value) => setFormData({ ...formData, schema_type: value })}
/>
</div>
<div>
<Label>Schema Name</Label>
<InputField
type="text"
value={formData.schema_name}
onChange={(e) => setFormData({ ...formData, schema_name: e.target.value })}
placeholder="Organization name"
/>
</div>
<div>
<Label>Schema Description</Label>
<TextArea
value={formData.schema_description}
onChange={(value) => setFormData({ ...formData, schema_description: value })}
rows={3}
placeholder="Description"
className="mt-1"
/>
</div>
<div>
<Label>Schema URL</Label>
<InputField
type="url"
value={formData.schema_url}
onChange={(e) => setFormData({ ...formData, schema_url: e.target.value })}
placeholder="https://example.com"
/>
</div>
<div>
<Label>Schema Logo URL</Label>
<InputField
type="url"
value={formData.schema_logo}
onChange={(e) => setFormData({ ...formData, schema_logo: e.target.value })}
placeholder="https://example.com/logo.png"
/>
</div>
<div>
<Label>Same As URLs</Label>
<InputField
type="text"
value={formData.schema_same_as}
onChange={(e) => setFormData({ ...formData, schema_same_as: e.target.value })}
placeholder="Social profiles (comma-separated)"
/>
</div>
</div>
</Card>
</div>
</Card>
)}
{/* Save Button */}
<div className="flex justify-end">
<Button
onClick={handleSave}
variant="primary"
disabled={saving}
startIcon={saving ? <Loader2Icon className="w-4 h-4 animate-spin" /> : <SaveIcon className="w-4 h-4" />}
>
{saving ? 'Saving...' : 'Save Settings'}
</Button>
</div>
</>
)}
@@ -2084,15 +2068,6 @@ export default function SiteSettings() {
onIntegrationUpdate={handleIntegrationUpdate}
/>
)}
{/* Save Button */}
{activeTab === 'general' && (
<div className="flex justify-end">
<Button onClick={handleSave} variant="primary" disabled={saving}>
{saving ? 'Saving...' : 'Save Settings'}
</Button>
</div>
)}
</div>
)}