componenets standardization 1

This commit is contained in:
IGNY8 VPS (Salman)
2026-01-01 21:42:04 +00:00
parent c880e24fc0
commit a4691ad2da
95 changed files with 3597 additions and 1745 deletions

View File

@@ -9,6 +9,8 @@ import PageMeta from '../../components/common/PageMeta';
import PageHeader from '../../components/common/PageHeader';
import { Card } from '../../components/ui/card';
import Button from '../../components/ui/button/Button';
import InputField from '../../components/form/input/InputField';
import Select from '../../components/form/Select';
import { useToast } from '../../components/ui/toast/ToastContainer';
import { fetchAPI } from '../../services/api';
import { SearchIcon } from '../../icons';
@@ -140,63 +142,50 @@ export default function SiteContentManager() {
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="relative">
<SearchIcon className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" />
<input
type="text"
<InputField
placeholder="Search content..."
value={searchTerm}
onChange={(e) => {
setSearchTerm(e.target.value);
setCurrentPage(1);
}}
className="w-full pl-10 pr-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg dark:bg-gray-800 dark:text-white"
className="pl-10"
/>
</div>
<select
value={statusFilter}
onChange={(e) => {
setStatusFilter(e.target.value);
<Select
options={STATUS_OPTIONS}
defaultValue={statusFilter}
onChange={(value) => {
setStatusFilter(value);
setCurrentPage(1);
}}
className="px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md dark:bg-gray-800 dark:text-white"
>
{STATUS_OPTIONS.map((opt) => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
))}
</select>
/>
<select
value={sourceFilter}
onChange={(e) => {
setSourceFilter(e.target.value);
<Select
options={SOURCE_OPTIONS}
defaultValue={sourceFilter}
onChange={(value) => {
setSourceFilter(value);
setCurrentPage(1);
}}
className="px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md dark:bg-gray-800 dark:text-white"
>
{SOURCE_OPTIONS.map((opt) => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
))}
</select>
/>
<select
value={`${sortBy}-${sortDirection}`}
onChange={(e) => {
const [field, direction] = e.target.value.split('-');
<Select
options={[
{ value: 'created_at-desc', label: 'Newest First' },
{ value: 'created_at-asc', label: 'Oldest First' },
{ value: 'updated_at-desc', label: 'Recently Updated' },
{ value: 'title-asc', label: 'Title A-Z' },
{ value: 'title-desc', label: 'Title Z-A' },
]}
defaultValue={`${sortBy}-${sortDirection}`}
onChange={(value) => {
const [field, direction] = value.split('-');
setSortBy(field as typeof sortBy);
setSortDirection(direction as 'asc' | 'desc');
}}
className="px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md dark:bg-gray-800 dark:text-white"
>
<option value="created_at-desc">Newest First</option>
<option value="created_at-asc">Oldest First</option>
<option value="updated_at-desc">Recently Updated</option>
<option value="title-asc">Title A-Z</option>
<option value="title-desc">Title Z-A</option>
</select>
/>
</div>
</Card>

View File

@@ -263,9 +263,11 @@ export default function SiteDashboard() {
{/* Quick Actions */}
<ComponentCard title="Quick Actions" desc="Common site management tasks">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<button
<Button
onClick={() => navigate(`/sites/${siteId}/pages`)}
className="flex items-center gap-4 p-6 rounded-xl border-2 border-gray-200 bg-white hover:border-[var(--color-primary)] hover:shadow-lg transition-all group"
variant="ghost"
tone="neutral"
className="flex items-center gap-4 p-6 rounded-xl border-2 border-gray-200 bg-white hover:border-[var(--color-primary)] hover:shadow-lg transition-all group h-auto justify-start"
>
<div className="size-12 rounded-xl bg-gradient-to-br from-[var(--color-primary)] to-[var(--color-primary-dark)] flex items-center justify-center text-white shadow-lg">
<PageIcon className="h-6 w-6" />
@@ -275,11 +277,13 @@ export default function SiteDashboard() {
<p className="text-sm text-gray-600">View and edit pages</p>
</div>
<ArrowRightIcon className="h-5 w-5 text-gray-400 group-hover:text-[var(--color-primary)] transition" />
</button>
</Button>
<button
<Button
onClick={() => navigate(`/sites/${siteId}/content`)}
className="flex items-center gap-4 p-6 rounded-xl border-2 border-gray-200 bg-white hover:border-[var(--color-success)] hover:shadow-lg transition-all group"
variant="ghost"
tone="neutral"
className="flex items-center gap-4 p-6 rounded-xl border-2 border-gray-200 bg-white hover:border-[var(--color-success)] hover:shadow-lg transition-all group h-auto justify-start"
>
<div className="size-12 rounded-xl bg-gradient-to-br from-[var(--color-success)] to-[var(--color-success-dark)] flex items-center justify-center text-white shadow-lg">
<FileIcon className="h-6 w-6" />
@@ -289,11 +293,13 @@ export default function SiteDashboard() {
<p className="text-sm text-gray-600">View and edit content</p>
</div>
<ArrowRightIcon className="h-5 w-5 text-gray-400 group-hover:text-[var(--color-success)] transition" />
</button>
</Button>
<button
<Button
onClick={() => navigate(`/sites/${siteId}/settings?tab=integrations`)}
className="flex items-center gap-4 p-6 rounded-xl border-2 border-gray-200 bg-white hover:border-[var(--color-purple)] hover:shadow-lg transition-all group"
variant="ghost"
tone="neutral"
className="flex items-center gap-4 p-6 rounded-xl border-2 border-gray-200 bg-white hover:border-[var(--color-purple)] hover:shadow-lg transition-all group h-auto justify-start"
>
<div className="size-12 rounded-xl bg-gradient-to-br from-[var(--color-purple)] to-[var(--color-purple-dark)] flex items-center justify-center text-white shadow-lg">
<PlugInIcon className="h-6 w-6" />
@@ -303,11 +309,13 @@ export default function SiteDashboard() {
<p className="text-sm text-gray-600">Manage connections</p>
</div>
<ArrowRightIcon className="h-5 w-5 text-gray-400 group-hover:text-[var(--color-purple)] transition" />
</button>
</Button>
<button
<Button
onClick={() => navigate(`/sites/${siteId}/sync`)}
className="flex items-center gap-4 p-6 rounded-xl border-2 border-gray-200 bg-white hover:border-[var(--color-warning)] hover:shadow-lg transition-all group"
variant="ghost"
tone="neutral"
className="flex items-center gap-4 p-6 rounded-xl border-2 border-gray-200 bg-white hover:border-[var(--color-warning)] hover:shadow-lg transition-all group h-auto justify-start"
>
<div className="size-12 rounded-xl bg-gradient-to-br from-[var(--color-warning)] to-[var(--color-warning-dark)] flex items-center justify-center text-white shadow-lg">
<BoltIcon className="h-6 w-6" />
@@ -317,11 +325,13 @@ export default function SiteDashboard() {
<p className="text-sm text-gray-600">View sync status</p>
</div>
<ArrowRightIcon className="h-5 w-5 text-gray-400 group-hover:text-[var(--color-warning)] transition" />
</button>
</Button>
<button
<Button
onClick={() => navigate(`/sites/${siteId}/deploy`)}
className="flex items-center gap-4 p-6 rounded-xl border-2 border-gray-200 bg-white hover:border-[var(--color-primary)] hover:shadow-lg transition-all group"
variant="ghost"
tone="neutral"
className="flex items-center gap-4 p-6 rounded-xl border-2 border-gray-200 bg-white hover:border-[var(--color-primary)] hover:shadow-lg transition-all group h-auto justify-start"
>
<div className="size-12 rounded-xl bg-gradient-to-br from-[var(--color-primary)] to-[var(--color-primary-dark)] flex items-center justify-center text-white shadow-lg">
<ArrowUpIcon className="h-6 w-6" />
@@ -331,11 +341,13 @@ export default function SiteDashboard() {
<p className="text-sm text-gray-600">Deploy to production</p>
</div>
<ArrowRightIcon className="h-5 w-5 text-gray-400 group-hover:text-[var(--color-primary)] transition" />
</button>
</Button>
<button
<Button
onClick={() => navigate(`/sites/${siteId}/publishing-queue`)}
className="flex items-center gap-4 p-6 rounded-xl border-2 border-gray-200 bg-white hover:border-amber-500 hover:shadow-lg transition-all group"
variant="ghost"
tone="neutral"
className="flex items-center gap-4 p-6 rounded-xl border-2 border-gray-200 bg-white hover:border-amber-500 hover:shadow-lg transition-all group h-auto justify-start"
>
<div className="size-12 rounded-xl bg-gradient-to-br from-amber-500 to-amber-600 flex items-center justify-center text-white shadow-lg">
<ClockIcon className="h-6 w-6" />
@@ -345,7 +357,7 @@ export default function SiteDashboard() {
<p className="text-sm text-gray-600">View scheduled content</p>
</div>
<ArrowRightIcon className="h-5 w-5 text-gray-400 group-hover:text-warning-500 transition" />
</button>
</Button>
</div>
</ComponentCard>

View File

@@ -13,6 +13,8 @@ import Button from '../../components/ui/button/Button';
import Badge from '../../components/ui/badge/Badge';
import Alert from '../../components/ui/alert/Alert';
import Switch from '../../components/form/switch/Switch';
import InputField from '../../components/form/input/InputField';
import Select from '../../components/form/Select';
import ViewToggle from '../../components/common/ViewToggle';
import WorkflowGuide from '../../components/onboarding/WorkflowGuide';
import {
@@ -605,40 +607,38 @@ export default function SiteList() {
>
<div className="flex flex-nowrap gap-3 items-center justify-between w-full">
<div className="flex flex-nowrap gap-3 items-center flex-1 min-w-0 w-full">
<input
type="text"
placeholder="Search sites..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="flex-1 min-w-[200px] h-9 px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-brand-500"
/>
<select
value={siteTypeFilter}
onChange={(e) => setSiteTypeFilter(e.target.value)}
className="flex-1 min-w-[140px] h-9 px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-brand-500"
>
{SITE_TYPES.map(opt => (
<option key={opt.value} value={opt.value}>{opt.label}</option>
))}
</select>
<select
value={hostingTypeFilter}
onChange={(e) => setHostingTypeFilter(e.target.value)}
className="flex-1 min-w-[140px] h-9 px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-brand-500"
>
{HOSTING_TYPES.map(opt => (
<option key={opt.value} value={opt.value}>{opt.label}</option>
))}
</select>
<select
value={statusFilter}
onChange={(e) => setStatusFilter(e.target.value)}
className="flex-1 min-w-[140px] h-9 px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-brand-500"
>
{STATUS_OPTIONS.map(opt => (
<option key={opt.value} value={opt.value}>{opt.label}</option>
))}
</select>
<div className="flex-1 min-w-[200px]">
<InputField
type="text"
placeholder="Search sites..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>
<div className="flex-1 min-w-[140px]">
<Select
options={SITE_TYPES}
placeholder="Show All Types"
defaultValue={siteTypeFilter}
onChange={(val) => setSiteTypeFilter(val)}
/>
</div>
<div className="flex-1 min-w-[140px]">
<Select
options={HOSTING_TYPES}
placeholder="Show All Hosting"
defaultValue={hostingTypeFilter}
onChange={(val) => setHostingTypeFilter(val)}
/>
</div>
<div className="flex-1 min-w-[140px]">
<Select
options={STATUS_OPTIONS}
placeholder="Show All Status"
defaultValue={statusFilter}
onChange={(val) => setStatusFilter(val)}
/>
</div>
</div>
{hasActiveFilters && (
<Button

View File

@@ -76,7 +76,8 @@ const DraggablePageItem: React.FC<{
e.stopPropagation();
onSelect(page.id);
}}
className="cursor-pointer"
className="cursor-pointer p-1 rounded hover:bg-gray-100 dark:hover:bg-gray-700"
aria-label={isSelected ? 'Deselect page' : 'Select page'}
>
{isSelected ? (
<CheckLineIcon className="w-5 h-5 text-brand-600 dark:text-brand-400" />
@@ -365,8 +366,9 @@ export default function PageManager() {
<Card className="p-6">
<div className="mb-4 flex items-center justify-between">
<button
type="button"
<Button
variant="ghost"
size="sm"
onClick={handleSelectAll}
className="flex items-center gap-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white"
>
@@ -376,7 +378,7 @@ export default function PageManager() {
<div className="w-5 h-5 border-2 border-gray-400 rounded" />
)}
<span>Select All</span>
</button>
</Button>
<p className="text-sm text-gray-500 dark:text-gray-400">
Drag and drop to reorder pages
</p>

View File

@@ -11,6 +11,7 @@ import { Card } from '../../components/ui/card';
import Button from '../../components/ui/button/Button';
import Label from '../../components/form/Label';
import TextArea from '../../components/form/input/TextArea';
import InputField from '../../components/form/input/InputField';
import SelectDropdown from '../../components/form/SelectDropdown';
import { useToast } from '../../components/ui/toast/ToastContainer';
import { fetchAPI, fetchContentValidation, validateContent, ContentValidationResult } from '../../services/api';
@@ -266,51 +267,39 @@ export default function PostEditor() {
{/* Tabs */}
<div className="mb-6 border-b border-gray-200 dark:border-gray-700">
<div className="flex gap-4">
<button
type="button"
<Button
variant={activeTab === 'content' ? 'primary' : 'ghost'}
size="sm"
onClick={() => setActiveTab('content')}
className={`px-4 py-2 font-medium border-b-2 transition-colors ${
activeTab === 'content'
? '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'
}`}
>
<FileTextIcon className="w-4 h-4 inline mr-2" />
<FileTextIcon className="w-4 h-4" />
Content
</button>
<button
type="button"
</Button>
<Button
variant={activeTab === 'taxonomy' ? 'primary' : 'ghost'}
size="sm"
onClick={() => setActiveTab('taxonomy')}
className={`px-4 py-2 font-medium border-b-2 transition-colors ${
activeTab === 'taxonomy'
? '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'
}`}
>
<TagIcon className="w-4 h-4 inline mr-2" />
<TagIcon className="w-4 h-4" />
Taxonomy & Cluster
</button>
</Button>
{content.id && (
<button
type="button"
<Button
variant={activeTab === 'validation' ? 'primary' : 'ghost'}
size="sm"
onClick={() => {
setActiveTab('validation');
loadValidation();
}}
className={`px-4 py-2 font-medium border-b-2 transition-colors ${
activeTab === 'validation'
? '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'
}`}
>
<CheckCircleIcon className="w-4 h-4 inline mr-2" />
<CheckCircleIcon className="w-4 h-4" />
Validation
{validationResult && !validationResult.is_valid && (
<span className="ml-2 px-2 py-0.5 text-xs bg-error-100 dark:bg-error-900 text-error-600 dark:text-error-400 rounded-full">
{validationResult.validation_errors.length}
</span>
)}
</button>
</Button>
)}
</div>
</div>
@@ -322,12 +311,12 @@ export default function PostEditor() {
<div className="space-y-4">
<div>
<Label>Title *</Label>
<input
<InputField
type="text"
value={content.title}
onChange={(e) => setContent({ ...content, title: e.target.value })}
placeholder="Enter post 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"
className="mt-1"
/>
</div>

View File

@@ -10,6 +10,8 @@ import PageHeader from '../../components/common/PageHeader';
import ComponentCard from '../../components/common/ComponentCard';
import { Card } from '../../components/ui/card';
import Button from '../../components/ui/button/Button';
import IconButton from '../../components/ui/button/IconButton';
import { ButtonGroup, ButtonGroupItem } from '../../components/ui/button-group/ButtonGroup';
import { useToast } from '../../components/ui/toast/ToastContainer';
import { fetchContent, Content } from '../../services/api';
import {
@@ -277,30 +279,22 @@ export default function PublishingQueue() {
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">
{queueItems.length} items in queue
</h2>
<div className="flex items-center gap-2 bg-gray-100 dark:bg-gray-800 rounded-lg p-1">
<button
<ButtonGroup>
<ButtonGroupItem
isActive={viewMode === 'list'}
onClick={() => setViewMode('list')}
className={`flex items-center gap-2 px-3 py-1.5 rounded-md text-sm font-medium transition-colors ${
viewMode === 'list'
? 'bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-sm'
: 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white'
}`}
>
<ListIcon className="w-4 h-4" />
<ListIcon className="w-4 h-4 mr-1.5" />
List
</button>
<button
</ButtonGroupItem>
<ButtonGroupItem
isActive={viewMode === 'calendar'}
onClick={() => setViewMode('calendar')}
className={`flex items-center gap-2 px-3 py-1.5 rounded-md text-sm font-medium transition-colors ${
viewMode === 'calendar'
? 'bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-sm'
: 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white'
}`}
>
<CalendarIcon className="w-4 h-4" />
<CalendarIcon className="w-4 h-4 mr-1.5" />
Calendar
</button>
</div>
</ButtonGroupItem>
</ButtonGroup>
</div>
{/* Queue Content */}
@@ -361,27 +355,30 @@ export default function PublishingQueue() {
{/* Actions */}
<div className="flex items-center gap-1">
<button
<IconButton
icon={<EyeIcon className="w-4 h-4" />}
variant="ghost"
tone="neutral"
size="sm"
onClick={() => handleViewContent(item)}
className="p-2 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700"
title="View content"
>
<EyeIcon className="w-4 h-4" />
</button>
<button
/>
<IconButton
icon={item.isPaused ? <PlayIcon className="w-4 h-4" /> : <PauseIcon className="w-4 h-4" />}
variant="ghost"
tone="neutral"
size="sm"
onClick={() => handlePauseItem(item)}
className="p-2 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700"
title={item.isPaused ? 'Resume' : 'Pause'}
>
{item.isPaused ? <PlayIcon className="w-4 h-4" /> : <PauseIcon className="w-4 h-4" />}
</button>
<button
/>
<IconButton
icon={<TrashBinIcon className="w-4 h-4" />}
variant="ghost"
tone="danger"
size="sm"
onClick={() => handleRemoveFromQueue(item)}
className="p-2 text-error-500 hover:text-error-700 dark:text-error-400 dark:hover:text-error-300 rounded-lg hover:bg-error-50 dark:hover:bg-error-900/20"
title="Remove from queue"
>
<TrashBinIcon className="w-4 h-4" />
</button>
/>
</div>
</div>
))}

View File

@@ -9,10 +9,14 @@ import PageMeta from '../../components/common/PageMeta';
import PageHeader from '../../components/common/PageHeader';
import { Card } from '../../components/ui/card';
import Button from '../../components/ui/button/Button';
import IconButton from '../../components/ui/button/IconButton';
import Label from '../../components/form/Label';
import InputField from '../../components/form/input/InputField';
import Select from '../../components/form/Select';
import SelectDropdown from '../../components/form/SelectDropdown';
import Checkbox from '../../components/form/input/Checkbox';
import TextArea from '../../components/form/input/TextArea';
import Switch from '../../components/form/switch/Switch';
import { useToast } from '../../components/ui/toast/ToastContainer';
import {
fetchAPI,
@@ -23,7 +27,7 @@ import {
} 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';
import { GridIcon, PlugInIcon, PaperPlaneIcon, DocsIcon, BoltIcon, FileIcon, ChevronDownIcon, CloseIcon, PlusIcon } from '../../icons';
import Badge from '../../components/ui/badge/Badge';
import { Dropdown } from '../../components/ui/dropdown/Dropdown';
import { DropdownItem } from '../../components/ui/dropdown/DropdownItem';
@@ -554,10 +558,11 @@ export default function SiteSettings() {
{/* Site Selector - Only show if more than 1 site */}
{!sitesLoading && sites.length > 1 && (
<div className="relative inline-block">
<button
<Button
ref={siteSelectorRef}
onClick={() => setIsSiteSelectorOpen(!isSiteSelectorOpen)}
className="flex items-center gap-2 px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-brand-200 rounded-lg hover:bg-brand-50 hover:border-brand-300 dark:bg-gray-800 dark:text-gray-300 dark:border-brand-700/50 dark:hover:bg-brand-500/10 dark:hover:border-brand-600/50 transition-colors"
variant="outline"
className="flex items-center gap-2"
aria-label="Switch site"
>
<span className="flex items-center gap-2">
@@ -565,7 +570,7 @@ export default function SiteSettings() {
<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>
</Button>
<Dropdown
isOpen={isSiteSelectorOpen}
onClose={() => setIsSiteSelectorOpen(false)}
@@ -605,13 +610,13 @@ export default function SiteSettings() {
{/* Tabs */}
<div className="mb-6 border-b border-gray-200 dark:border-gray-700">
<div className="flex gap-4">
<button
type="button"
<Button
variant="ghost"
onClick={() => {
setActiveTab('general');
navigate(`/sites/${siteId}/settings`, { replace: true });
}}
className={`px-4 py-2 font-medium border-b-2 transition-colors ${
className={`px-4 py-2 font-medium border-b-2 rounded-none transition-colors ${
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'
@@ -619,14 +624,14 @@ export default function SiteSettings() {
>
<GridIcon className="w-4 h-4 inline mr-2" />
General
</button>
<button
type="button"
</Button>
<Button
variant="ghost"
onClick={() => {
setActiveTab('integrations');
navigate(`/sites/${siteId}/settings?tab=integrations`, { replace: true });
}}
className={`px-4 py-2 font-medium border-b-2 transition-colors ${
className={`px-4 py-2 font-medium border-b-2 rounded-none transition-colors ${
activeTab === 'integrations'
? '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'
@@ -634,14 +639,14 @@ export default function SiteSettings() {
>
<PlugInIcon className="w-4 h-4 inline mr-2" />
Integrations
</button>
<button
type="button"
</Button>
<Button
variant="ghost"
onClick={() => {
setActiveTab('publishing');
navigate(`/sites/${siteId}/settings?tab=publishing`, { replace: true });
}}
className={`px-4 py-2 font-medium border-b-2 transition-colors ${
className={`px-4 py-2 font-medium border-b-2 rounded-none transition-colors ${
activeTab === 'publishing'
? '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'
@@ -649,15 +654,15 @@ export default function SiteSettings() {
>
<PaperPlaneIcon className="w-4 h-4 inline mr-2" />
Publishing
</button>
</Button>
{(wordPressIntegration || site?.wp_url || site?.wp_api_key || site?.hosting_type === 'wordpress') && (
<button
type="button"
<Button
variant="ghost"
onClick={() => {
setActiveTab('content-types');
navigate(`/sites/${siteId}/settings?tab=content-types`, { replace: true });
}}
className={`px-4 py-2 font-medium border-b-2 transition-colors ${
className={`px-4 py-2 font-medium border-b-2 rounded-none transition-colors ${
activeTab === 'content-types'
? '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'
@@ -665,7 +670,7 @@ export default function SiteSettings() {
>
<FileIcon className="w-4 h-4 inline mr-2" />
Content Types
</button>
</Button>
)}
</div>
</div>
@@ -696,17 +701,15 @@ export default function SiteSettings() {
</p>
</div>
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
<Switch
label=""
checked={publishingSettings.auto_approval_enabled}
onChange={(e) => {
const newSettings = { ...publishingSettings, auto_approval_enabled: e.target.checked };
onChange={(checked) => {
const newSettings = { ...publishingSettings, auto_approval_enabled: checked };
setPublishingSettings(newSettings);
savePublishingSettings({ auto_approval_enabled: e.target.checked });
savePublishingSettings({ auto_approval_enabled: checked });
}}
className="sr-only peer"
/>
<div className="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-brand-300 dark:peer-focus:ring-brand-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-brand-600"></div>
</label>
</div>
</div>
@@ -721,17 +724,15 @@ export default function SiteSettings() {
</p>
</div>
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
<Switch
label=""
checked={publishingSettings.auto_publish_enabled}
onChange={(e) => {
const newSettings = { ...publishingSettings, auto_publish_enabled: e.target.checked };
onChange={(checked) => {
const newSettings = { ...publishingSettings, auto_publish_enabled: checked };
setPublishingSettings(newSettings);
savePublishingSettings({ auto_publish_enabled: e.target.checked });
savePublishingSettings({ auto_publish_enabled: checked });
}}
className="sr-only peer"
/>
<div className="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-brand-300 dark:peer-focus:ring-brand-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-brand-600"></div>
</label>
</div>
</div>
@@ -745,7 +746,7 @@ export default function SiteSettings() {
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<Label>Daily Limit</Label>
<input
<InputField
type="number"
min="1"
max="50"
@@ -754,14 +755,12 @@ export default function SiteSettings() {
const value = Math.max(1, Math.min(50, parseInt(e.target.value) || 1));
setPublishingSettings({ ...publishingSettings, daily_publish_limit: value });
}}
onBlur={() => savePublishingSettings({ daily_publish_limit: publishingSettings.daily_publish_limit })}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-brand-500 focus:border-brand-500"
/>
<p className="text-xs text-gray-500 mt-1">Articles per day</p>
</div>
<div>
<Label>Weekly Limit</Label>
<input
<InputField
type="number"
min="1"
max="200"
@@ -770,14 +769,12 @@ export default function SiteSettings() {
const value = Math.max(1, Math.min(200, parseInt(e.target.value) || 1));
setPublishingSettings({ ...publishingSettings, weekly_publish_limit: value });
}}
onBlur={() => savePublishingSettings({ weekly_publish_limit: publishingSettings.weekly_publish_limit })}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-brand-500 focus:border-brand-500"
/>
<p className="text-xs text-gray-500 mt-1">Articles per week</p>
</div>
<div>
<Label>Monthly Limit</Label>
<input
<InputField
type="number"
min="1"
max="500"
@@ -786,8 +783,6 @@ export default function SiteSettings() {
const value = Math.max(1, Math.min(500, parseInt(e.target.value) || 1));
setPublishingSettings({ ...publishingSettings, monthly_publish_limit: value });
}}
onBlur={() => savePublishingSettings({ monthly_publish_limit: publishingSettings.monthly_publish_limit })}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-brand-500 focus:border-brand-500"
/>
<p className="text-xs text-gray-500 mt-1">Articles per month</p>
</div>
@@ -810,9 +805,11 @@ export default function SiteSettings() {
{ value: 'sat', label: 'Sat' },
{ value: 'sun', label: 'Sun' },
].map((day) => (
<button
<Button
key={day.value}
type="button"
variant={(publishingSettings.publish_days || []).includes(day.value) ? 'primary' : 'outline'}
tone="brand"
size="sm"
onClick={() => {
const currentDays = publishingSettings.publish_days || [];
const newDays = currentDays.includes(day.value)
@@ -821,14 +818,9 @@ export default function SiteSettings() {
setPublishingSettings({ ...publishingSettings, publish_days: newDays });
savePublishingSettings({ publish_days: newDays });
}}
className={`px-4 py-2 rounded-md text-sm font-medium transition-colors ${
(publishingSettings.publish_days || []).includes(day.value)
? 'bg-brand-600 text-white'
: 'bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600'
}`}
>
{day.label}
</button>
</Button>
))}
</div>
</div>
@@ -842,7 +834,7 @@ export default function SiteSettings() {
<div className="space-y-3">
{(publishingSettings.publish_time_slots || ['09:00', '14:00', '18:00']).map((time: string, index: number) => (
<div key={index} className="flex items-center gap-2">
<input
<InputField
type="time"
value={time}
onChange={(e) => {
@@ -850,40 +842,36 @@ export default function SiteSettings() {
newSlots[index] = e.target.value;
setPublishingSettings({ ...publishingSettings, publish_time_slots: newSlots });
}}
onBlur={() => savePublishingSettings({ publish_time_slots: publishingSettings.publish_time_slots })}
className="px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-brand-500 focus:border-brand-500"
/>
{(publishingSettings.publish_time_slots || []).length > 1 && (
<button
type="button"
<IconButton
icon={<CloseIcon className="w-5 h-5" />}
variant="ghost"
tone="danger"
size="sm"
title="Remove time slot"
onClick={() => {
const newSlots = (publishingSettings.publish_time_slots || []).filter((_: string, i: number) => i !== index);
setPublishingSettings({ ...publishingSettings, publish_time_slots: newSlots });
savePublishingSettings({ publish_time_slots: newSlots });
}}
className="p-2 text-gray-400 hover:text-error-500 transition-colors"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
/>
)}
</div>
))}
<button
type="button"
<Button
variant="ghost"
tone="brand"
size="sm"
startIcon={<PlusIcon className="w-4 h-4" />}
onClick={() => {
const newSlots = [...(publishingSettings.publish_time_slots || []), '12:00'];
setPublishingSettings({ ...publishingSettings, publish_time_slots: newSlots });
savePublishingSettings({ publish_time_slots: newSlots });
}}
className="text-sm text-brand-600 hover:text-brand-700 font-medium flex items-center gap-1"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
</svg>
Add Time Slot
</button>
</Button>
</div>
</div>
@@ -1084,32 +1072,29 @@ export default function SiteSettings() {
<div className="space-y-4">
<div>
<Label>Site Name</Label>
<input
<InputField
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
<InputField
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
<InputField
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>
@@ -1134,7 +1119,7 @@ export default function SiteSettings() {
<div>
<Checkbox
checked={formData.is_active}
onChange={(e) => setFormData({ ...formData, is_active: e.target.checked })}
onChange={(checked) => setFormData({ ...formData, is_active: checked })}
label="Active"
/>
</div>
@@ -1150,13 +1135,12 @@ export default function SiteSettings() {
<div className="space-y-4">
<div>
<Label>Meta Title</Label>
<input
<InputField
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"
max="60"
/>
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
{formData.meta_title.length}/60 characters
@@ -1180,12 +1164,11 @@ export default function SiteSettings() {
<div>
<Label>Meta Keywords (comma-separated)</Label>
<input
<InputField
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
@@ -1203,12 +1186,11 @@ export default function SiteSettings() {
<div className="space-y-4">
<div>
<Label>OG Title</Label>
<input
<InputField
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>
@@ -1225,12 +1207,11 @@ export default function SiteSettings() {
<div>
<Label>OG Image URL</Label>
<input
<InputField
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
@@ -1253,12 +1234,11 @@ export default function SiteSettings() {
<div>
<Label>OG Site Name</Label>
<input
<InputField
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>
@@ -1288,12 +1268,11 @@ export default function SiteSettings() {
<div>
<Label>Schema Name</Label>
<input
<InputField
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>
@@ -1310,34 +1289,31 @@ export default function SiteSettings() {
<div>
<Label>Schema URL</Label>
<input
<InputField
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
<InputField
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
<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"
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
@@ -1359,21 +1335,18 @@ export default function SiteSettings() {
<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);
<Select
options={industries.map((industry) => ({
value: industry.slug,
label: industry.name,
}))}
placeholder="Select an industry..."
defaultValue={selectedIndustry}
onChange={(value) => {
setSelectedIndustry(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}
@@ -1388,15 +1361,14 @@ export default function SiteSettings() {
</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
<div
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"
className="flex items-start space-x-3 p-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800"
>
<input
type="checkbox"
<Checkbox
checked={selectedSectors.includes(sector.slug)}
onChange={(e) => {
if (e.target.checked) {
onChange={(checked) => {
if (checked) {
if (selectedSectors.length >= 5) {
toast.error('Maximum 5 sectors allowed per site');
return;
@@ -1406,7 +1378,6 @@ export default function SiteSettings() {
setSelectedSectors(selectedSectors.filter(s => s !== sector.slug));
}
}}
className="mt-1 h-4 w-4 rounded border-gray-300 text-brand-600 focus:ring-brand-500"
/>
<div className="flex-1">
<div className="font-medium text-sm text-gray-900 dark:text-white">
@@ -1416,7 +1387,7 @@ export default function SiteSettings() {
{sector.description}
</div>
</div>
</label>
</div>
))}
</div>
<p className="mt-2 text-xs text-gray-500 dark:text-gray-400">
@@ -1447,13 +1418,12 @@ export default function SiteSettings() {
<div className="space-y-4">
<div>
<Label>Meta Title</Label>
<input
<InputField
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"
max="60"
/>
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
{formData.meta_title.length}/60 characters
@@ -1477,12 +1447,11 @@ export default function SiteSettings() {
<div>
<Label>Meta Keywords (comma-separated)</Label>
<input
<InputField
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
@@ -1498,12 +1467,11 @@ export default function SiteSettings() {
<div className="space-y-4">
<div>
<Label>OG Title</Label>
<input
<InputField
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>
@@ -1520,12 +1488,11 @@ export default function SiteSettings() {
<div>
<Label>OG Image URL</Label>
<input
<InputField
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
@@ -1542,18 +1509,17 @@ export default function SiteSettings() {
{ value: 'product', label: 'Product' },
]}
value={formData.og_type}
onChange={(e) => setFormData({ ...formData, og_type: e.target.value })}
onChange={(value) => setFormData({ ...formData, og_type: value })}
/>
</div>
<div>
<Label>OG Site Name</Label>
<input
<InputField
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>
@@ -1575,18 +1541,17 @@ export default function SiteSettings() {
{ value: 'NGO', label: 'NGO' },
]}
value={formData.schema_type}
onChange={(e) => setFormData({ ...formData, schema_type: e.target.value })}
onChange={(value) => setFormData({ ...formData, schema_type: value })}
/>
</div>
<div>
<Label>Schema Name</Label>
<input
<InputField
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>
@@ -1603,34 +1568,31 @@ export default function SiteSettings() {
<div>
<Label>Schema URL</Label>
<input
<InputField
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
<InputField
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
<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"
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

View File

@@ -287,9 +287,10 @@ export default function SyncDashboard() {
{/* Mismatches Section */}
{totalMismatches > 0 && (
<Card className="p-6 mb-6">
<button
<Button
onClick={() => setShowMismatches(!showMismatches)}
className="w-full flex items-center justify-between mb-4"
variant="ghost"
className="w-full flex items-center justify-between mb-4 px-0 hover:bg-transparent"
>
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">
Mismatches ({totalMismatches})
@@ -299,7 +300,7 @@ export default function SyncDashboard() {
) : (
<ChevronDownIcon className="w-5 h-5 text-gray-400" />
)}
</button>
</Button>
{showMismatches && mismatches && (
<div className="space-y-4">
@@ -412,9 +413,10 @@ export default function SyncDashboard() {
{/* Sync Logs */}
<Card className="p-6">
<button
<Button
onClick={() => setShowLogs(!showLogs)}
className="w-full flex items-center justify-between mb-4"
variant="ghost"
className="w-full flex items-center justify-between mb-4 px-0 hover:bg-transparent"
>
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">
Sync History ({logs.length})
@@ -424,7 +426,7 @@ export default function SyncDashboard() {
) : (
<ChevronDownIcon className="w-5 h-5 text-gray-400" />
)}
</button>
</Button>
{showLogs && (
<div className="space-y-2">