site settigns

This commit is contained in:
alorig
2025-11-26 06:08:44 +05:00
parent 51bb2eafd0
commit 451594bd29
6 changed files with 2981 additions and 256 deletions

View File

@@ -66,17 +66,8 @@ export default function SiteList() {
// Site Management Modals
const [selectedSite, setSelectedSite] = useState<Site | null>(null);
const [showSiteModal, setShowSiteModal] = useState(false);
const [showSectorsModal, setShowSectorsModal] = useState(false);
const [isSaving, setIsSaving] = useState(false);
const [togglingSiteId, setTogglingSiteId] = useState<number | null>(null);
const [industries, setIndustries] = useState<Industry[]>([]);
const [selectedIndustry, setSelectedIndustry] = useState<string>('');
const [selectedSectors, setSelectedSectors] = useState<string[]>([]);
const [isSelectingSectors, setIsSelectingSectors] = useState(false);
const [userPreferences, setUserPreferences] = useState<{
selectedIndustry?: string;
selectedSectors?: string[];
} | null>(null);
// Form state for site creation/editing
const [formData, setFormData] = useState({
@@ -95,8 +86,6 @@ export default function SiteList() {
useEffect(() => {
loadSites();
loadIndustries();
loadUserPreferences();
}, []);
const loadUserPreferences = async () => {
@@ -155,33 +144,6 @@ export default function SiteList() {
}
};
const loadIndustries = async () => {
try {
const response = await fetchIndustries();
let allIndustries = response.industries || [];
// Filter to show only user's pre-selected industries/sectors from account preferences
try {
const { fetchAccountSetting } = await import('../../services/api');
const setting = await fetchAccountSetting('user_preferences');
const preferences = setting.config as { selectedIndustry?: string; selectedSectors?: string[] } | undefined;
if (preferences?.selectedIndustry) {
// Filter industries to only show the user's pre-selected industry
allIndustries = allIndustries.filter(i => i.slug === preferences.selectedIndustry);
}
} catch (error: any) {
// 404 means preferences don't exist yet - show all industries (expected for new users)
// 500 and other errors - show all industries (graceful degradation)
// Silently handle errors - user can still use the page
}
setIndustries(allIndustries);
} catch (error: any) {
console.error('Failed to load industries:', error);
}
};
const applyFilters = () => {
let filtered = [...sites];
@@ -638,15 +600,6 @@ export default function SiteList() {
</div>
<div className="flex items-center justify-between">
<div className="flex gap-2">
<Button
variant="outline"
size="sm"
onClick={() => handleSettings(site)}
title="Configure Sectors"
>
<GridIcon className="w-4 h-4 mr-1" />
<span className="text-xs">Sectors</span>
</Button>
<Button
variant="outline"
size="sm"
@@ -927,110 +880,6 @@ export default function SiteList() {
fields={getSiteFormFields()}
isLoading={isSaving}
/>
{/* Sectors Selection Modal */}
<FormModal
isOpen={showSectorsModal}
onClose={() => 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={
<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
value={selectedIndustry}
onChange={(e) => {
setSelectedIndustry(e.target.value);
setSelectedSectors([]);
}}
className="h-9 w-full rounded-lg border border-gray-300 bg-transparent px-3 py-2 text-sm shadow-theme-xs text-gray-800 placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
>
<option value="">Select an industry...</option>
{industries.map((industry) => (
<option key={industry.slug} value={industry.slug}>
{industry.name}
</option>
))}
</select>
{selectedIndustry && (
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
{industries.find(i => i.slug === selectedIndustry)?.description}
</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-64 overflow-y-auto border border-gray-200 rounded-lg p-4 dark:border-gray-700">
{getIndustrySectors().map((sector) => (
<label
key={sector.slug}
className="flex items-start space-x-3 p-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800 cursor-pointer"
>
<input
type="checkbox"
checked={selectedSectors.includes(sector.slug)}
onChange={(e) => {
if (e.target.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));
}
}}
className="mt-1 h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
/>
<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>
</label>
))}
</div>
<p className="mt-2 text-xs text-gray-500 dark:text-gray-400">
Selected: {selectedSectors.length} / 5 sectors
</p>
</div>
)}
</div>
}
customFooter={
<div className="flex justify-end gap-3 pt-4 border-t border-gray-200 dark:border-gray-700">
<Button
type="button"
variant="outline"
onClick={() => setShowSectorsModal(false)}
disabled={isSelectingSectors}
>
Cancel
</Button>
<Button
type="submit"
variant="primary"
disabled={!selectedIndustry || selectedSectors.length === 0 || isSelectingSectors}
>
{isSelectingSectors ? 'Saving...' : 'Save Sectors'}
</Button>
</div>
}
/>
</div>
);
}

View File

@@ -1,7 +1,7 @@
/**
* Site Settings (Advanced)
* Phase 7: Advanced Site Management
* Features: SEO (meta tags, Open Graph, schema.org)
* Features: SEO (meta tags, Open Graph, schema.org), Industry & Sectors Configuration
*/
import React, { useState, useEffect, useRef } from 'react';
import { useParams, useNavigate, useSearchParams } from 'react-router-dom';
@@ -14,7 +14,13 @@ import SelectDropdown from '../../components/form/SelectDropdown';
import Checkbox from '../../components/form/input/Checkbox';
import TextArea from '../../components/form/input/TextArea';
import { useToast } from '../../components/ui/toast/ToastContainer';
import { fetchAPI, runSync, fetchSites, Site } from '../../services/api';
import {
fetchAPI,
fetchSites,
fetchIndustries,
Site,
Industry,
} from '../../services/api';
import WordPressIntegrationForm from '../../components/sites/WordPressIntegrationForm';
import { integrationApi, SiteIntegration } from '../../services/integration.api';
import { GridIcon, PlugInIcon, PaperPlaneIcon, DocsIcon, BoltIcon, FileIcon, ChevronDownIcon } from '../../icons';
@@ -40,10 +46,21 @@ export default function SiteSettings() {
const siteSelectorRef = useRef<HTMLButtonElement>(null);
// Check for tab parameter in URL
const initialTab = (searchParams.get('tab') as 'general' | 'seo' | 'og' | 'schema' | 'integrations' | 'content-types') || 'general';
const [activeTab, setActiveTab] = useState<'general' | 'seo' | 'og' | 'schema' | 'integrations' | 'content-types'>(initialTab);
const initialTab = (searchParams.get('tab') as 'general' | 'integrations' | 'content-types') || 'general';
const [activeTab, setActiveTab] = useState<'general' | 'integrations' | 'content-types'>(initialTab);
const [contentTypes, setContentTypes] = useState<any>(null);
const [contentTypesLoading, setContentTypesLoading] = useState(false);
// Sectors selection state
const [industries, setIndustries] = useState<Industry[]>([]);
const [selectedIndustry, setSelectedIndustry] = useState<string>('');
const [selectedSectors, setSelectedSectors] = useState<string[]>([]);
const [isSelectingSectors, setIsSelectingSectors] = useState(false);
const [userPreferences, setUserPreferences] = useState<{
selectedIndustry?: string;
selectedSectors?: string[];
} | null>(null);
const [formData, setFormData] = useState({
name: '',
slug: '',
@@ -78,13 +95,14 @@ export default function SiteSettings() {
// Load new site data
loadSite();
loadIntegrations();
loadIndustries();
}
}, [siteId]);
useEffect(() => {
// Update tab if URL parameter changes
const tab = searchParams.get('tab');
if (tab && ['general', 'seo', 'og', 'schema', 'integrations', 'content-types'].includes(tab)) {
if (tab && ['general', 'integrations', 'content-types'].includes(tab)) {
setActiveTab(tab as typeof activeTab);
}
}, [searchParams]);
@@ -98,8 +116,16 @@ export default function SiteSettings() {
// Load sites for selector
useEffect(() => {
loadSites();
loadUserPreferences();
}, []);
// Load site sectors when site and industries are loaded
useEffect(() => {
if (site && industries.length > 0) {
loadSiteSectors();
}
}, [site, industries]);
const loadSites = async () => {
try {
setSitesLoading(true);
@@ -171,6 +197,110 @@ export default function SiteSettings() {
}
};
const loadIndustries = async () => {
try {
const response = await fetchIndustries();
let allIndustries = response.industries || [];
// Filter to show only user's pre-selected industries/sectors from account preferences
try {
const { fetchAccountSetting } = await import('../../services/api');
const setting = await fetchAccountSetting('user_preferences');
const preferences = setting.config as { selectedIndustry?: string; selectedSectors?: string[] } | undefined;
if (preferences?.selectedIndustry) {
// Filter industries to only show the user's pre-selected industry
allIndustries = allIndustries.filter(i => i.slug === preferences.selectedIndustry);
}
} catch (error: any) {
// Silently handle errors - show all industries
}
setIndustries(allIndustries);
} catch (error: any) {
console.error('Failed to load industries:', error);
}
};
const loadUserPreferences = async () => {
try {
const { fetchAccountSetting } = await import('../../services/api');
const setting = await fetchAccountSetting('user_preferences');
const preferences = setting.config as { selectedIndustry?: string; selectedSectors?: string[] } | undefined;
if (preferences) {
setUserPreferences(preferences);
}
} catch (error: any) {
// Silently handle errors
}
};
const loadSiteSectors = async () => {
if (!siteId) return;
try {
const { fetchSiteSectors } = await import('../../services/api');
const sectors = await fetchSiteSectors(Number(siteId));
const sectorSlugs = sectors.map((s: any) => s.slug);
setSelectedSectors(sectorSlugs);
if (site?.industry_slug) {
setSelectedIndustry(site.industry_slug);
} else {
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 getIndustrySectors = () => {
if (!selectedIndustry) return [];
const industry = industries.find(i => i.slug === selectedIndustry);
let sectors = industry?.sectors || [];
// Filter to show only user's pre-selected sectors from account preferences
if (userPreferences?.selectedSectors && userPreferences.selectedSectors.length > 0) {
sectors = sectors.filter(s => userPreferences.selectedSectors!.includes(s.slug));
}
return sectors;
};
const handleSelectSectors = async () => {
if (!siteId || !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 { selectSectorsForSite } = await import('../../services/api');
await selectSectorsForSite(
Number(siteId),
selectedIndustry,
selectedSectors
);
toast.success('Sectors configured successfully');
await loadSite();
await loadSiteSectors();
} catch (error: any) {
toast.error(`Failed to configure sectors: ${error.message}`);
} finally {
setIsSelectingSectors(false);
}
};
const handleIntegrationUpdate = async (integration: SiteIntegration) => {
setWordPressIntegration(integration);
await loadIntegrations();
@@ -447,51 +577,6 @@ export default function SiteSettings() {
<GridIcon className="w-4 h-4 inline mr-2" />
General
</button>
<button
type="button"
onClick={() => {
setActiveTab('seo');
navigate(`/sites/${siteId}/settings?tab=seo`, { replace: true });
}}
className={`px-4 py-2 font-medium border-b-2 transition-colors ${
activeTab === 'seo'
? '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'
}`}
>
<DocsIcon className="w-4 h-4 inline mr-2" />
SEO Meta Tags
</button>
<button
type="button"
onClick={() => {
setActiveTab('og');
navigate(`/sites/${siteId}/settings?tab=og`, { replace: true });
}}
className={`px-4 py-2 font-medium border-b-2 transition-colors ${
activeTab === 'og'
? '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'
}`}
>
<PaperPlaneIcon className="w-4 h-4 inline mr-2" />
Open Graph
</button>
<button
type="button"
onClick={() => {
setActiveTab('schema');
navigate(`/sites/${siteId}/settings?tab=schema`, { replace: true });
}}
className={`px-4 py-2 font-medium border-b-2 transition-colors ${
activeTab === 'schema'
? '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'
}`}
>
<BoltIcon className="w-4 h-4 inline mr-2" />
Schema.org
</button>
<button
type="button"
onClick={() => {
@@ -632,66 +717,373 @@ export default function SiteSettings() {
<div className="space-y-6">
{/* General Tab */}
{activeTab === 'general' && (
<Card className="p-6">
<div className="space-y-4">
<div>
<Label>Site Name</Label>
<input
type="text"
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
className="mt-1 w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md dark:bg-gray-800 dark:text-white"
/>
</div>
<>
{/* 4-Card Layout for Basic Settings, SEO, Open Graph, and Schema */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
{/* Card 1: Basic Site Settings */}
<Card className="p-6">
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
<GridIcon className="w-5 h-5 text-brand-500" />
Basic Settings
</h3>
<div className="space-y-4">
<div>
<Label>Site Name</Label>
<input
type="text"
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
className="mt-1 w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md dark:bg-gray-800 dark:text-white"
/>
</div>
<div>
<Label>Slug</Label>
<input
type="text"
value={formData.slug}
onChange={(e) => setFormData({ ...formData, slug: e.target.value })}
className="mt-1 w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md dark:bg-gray-800 dark:text-white"
/>
</div>
<div>
<Label>Slug</Label>
<input
type="text"
value={formData.slug}
onChange={(e) => setFormData({ ...formData, slug: e.target.value })}
className="mt-1 w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md dark:bg-gray-800 dark:text-white"
/>
</div>
<div>
<Label>Site URL</Label>
<input
type="text"
value={formData.site_url}
onChange={(e) => setFormData({ ...formData, site_url: e.target.value })}
placeholder="https://example.com"
className="mt-1 w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md dark:bg-gray-800 dark:text-white"
/>
</div>
<div>
<Label>Site URL</Label>
<input
type="text"
value={formData.site_url}
onChange={(e) => setFormData({ ...formData, site_url: e.target.value })}
placeholder="https://example.com"
className="mt-1 w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md dark:bg-gray-800 dark:text-white"
/>
</div>
<div>
<Label>Site Type</Label>
<SelectDropdown
options={SITE_TYPES}
value={formData.site_type}
onChange={(value) => setFormData({ ...formData, site_type: value })}
/>
</div>
<div>
<Label>Site Type</Label>
<SelectDropdown
options={SITE_TYPES}
value={formData.site_type}
onChange={(value) => setFormData({ ...formData, site_type: value })}
/>
</div>
<div>
<Label>Hosting Type</Label>
<SelectDropdown
options={HOSTING_TYPES}
value={formData.hosting_type}
onChange={(value) => setFormData({ ...formData, hosting_type: value })}
/>
</div>
<div>
<Label>Hosting Type</Label>
<SelectDropdown
options={HOSTING_TYPES}
value={formData.hosting_type}
onChange={(value) => setFormData({ ...formData, hosting_type: value })}
/>
</div>
<div>
<Checkbox
checked={formData.is_active}
onChange={(e) => setFormData({ ...formData, is_active: e.target.checked })}
label="Active"
/>
</div>
<div>
<Checkbox
checked={formData.is_active}
onChange={(e) => setFormData({ ...formData, is_active: e.target.checked })}
label="Active"
/>
</div>
</div>
</Card>
{/* Card 2: SEO Meta Tags */}
<Card className="p-6">
<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
</h3>
<div className="space-y-4">
<div>
<Label>Meta Title</Label>
<input
type="text"
value={formData.meta_title}
onChange={(e) => setFormData({ ...formData, meta_title: e.target.value })}
placeholder="SEO title (recommended: 50-60 characters)"
maxLength={60}
className="mt-1 w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md dark:bg-gray-800 dark:text-white"
/>
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
{formData.meta_title.length}/60 characters
</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>
<div>
<Label>Meta Keywords (comma-separated)</Label>
<input
type="text"
value={formData.meta_keywords}
onChange={(e) => setFormData({ ...formData, meta_keywords: e.target.value })}
placeholder="keyword1, keyword2, keyword3"
className="mt-1 w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md dark:bg-gray-800 dark:text-white"
/>
<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>
<input
type="text"
value={formData.og_title}
onChange={(e) => setFormData({ ...formData, og_title: e.target.value })}
placeholder="Open Graph title"
className="mt-1 w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md dark:bg-gray-800 dark:text-white"
/>
</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>
<input
type="url"
value={formData.og_image}
onChange={(e) => setFormData({ ...formData, og_image: e.target.value })}
placeholder="https://example.com/image.jpg"
className="mt-1 w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md dark:bg-gray-800 dark:text-white"
/>
<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>
<input
type="text"
value={formData.og_site_name}
onChange={(e) => setFormData({ ...formData, og_site_name: e.target.value })}
placeholder="Site name for social sharing"
className="mt-1 w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md dark:bg-gray-800 dark:text-white"
/>
</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>
<input
type="text"
value={formData.schema_name}
onChange={(e) => setFormData({ ...formData, schema_name: e.target.value })}
placeholder="Organization name"
className="mt-1 w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md dark:bg-gray-800 dark:text-white"
/>
</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>
<input
type="url"
value={formData.schema_url}
onChange={(e) => setFormData({ ...formData, schema_url: e.target.value })}
placeholder="https://example.com"
className="mt-1 w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md dark:bg-gray-800 dark:text-white"
/>
</div>
<div>
<Label>Schema Logo URL</Label>
<input
type="url"
value={formData.schema_logo}
onChange={(e) => setFormData({ ...formData, schema_logo: e.target.value })}
placeholder="https://example.com/logo.png"
className="mt-1 w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md dark:bg-gray-800 dark:text-white"
/>
</div>
<div>
<Label>Same As URLs (comma-separated)</Label>
<input
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"
className="mt-1 w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md dark:bg-gray-800 dark:text-white"
/>
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
Social media profiles and other related URLs
</p>
</div>
</div>
</Card>
</div>
</Card>
{/* 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
value={selectedIndustry}
onChange={(e) => {
setSelectedIndustry(e.target.value);
setSelectedSectors([]);
}}
className="h-9 w-full rounded-lg border border-gray-300 bg-transparent px-3 py-2 text-sm shadow-theme-xs text-gray-800 placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
>
<option value="">Select an industry...</option>
{industries.map((industry) => (
<option key={industry.slug} value={industry.slug}>
{industry.name}
</option>
))}
</select>
{selectedIndustry && (
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
{industries.find(i => i.slug === selectedIndustry)?.description}
</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-64 overflow-y-auto border border-gray-200 rounded-lg p-4 dark:border-gray-700">
{getIndustrySectors().map((sector) => (
<label
key={sector.slug}
className="flex items-start space-x-3 p-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800 cursor-pointer"
>
<input
type="checkbox"
checked={selectedSectors.includes(sector.slug)}
onChange={(e) => {
if (e.target.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));
}
}}
className="mt-1 h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
/>
<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>
</label>
))}
</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>
)}
</div>
</Card>
</>
)}
{/* SEO Meta Tags Tab */}
@@ -905,7 +1297,7 @@ export default function SiteSettings() {
)}
{/* Save Button */}
{activeTab !== 'integrations' && activeTab !== 'content-types' && (
{activeTab === 'general' && (
<div className="flex justify-end">
<Button onClick={handleSave} variant="primary" disabled={saving}>
{saving ? 'Saving...' : 'Save Settings'}