componenets standardization 1
This commit is contained in:
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
))}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user