COmpoeentes standardization 2

This commit is contained in:
IGNY8 VPS (Salman)
2026-01-02 00:27:27 +00:00
parent a4691ad2da
commit f28f641fd5
50 changed files with 622 additions and 490 deletions

View File

@@ -49,6 +49,8 @@ export default tseslint.config(
'igny8-design-system/no-raw-input': 'warn',
'igny8-design-system/no-raw-select': 'warn',
'igny8-design-system/no-raw-textarea': 'warn',
// Button icon positioning - icons as children cause vertical stacking, use startIcon/endIcon props
'igny8-design-system/no-icon-children': 'warn',
},
},
)

View File

@@ -15,6 +15,7 @@
* 2. no-raw-input - Use <InputField> from components/form/input/InputField
* 3. no-raw-select - Use <Select> from components/form/Select
* 4. no-raw-textarea - Use <TextArea> from components/form/input/TextArea
* 5. no-icon-children - Use startIcon/endIcon props instead of icon children in Button
*
* USAGE: Import in eslint.config.js and enable rules
*/
@@ -226,5 +227,144 @@ module.exports = {
};
},
},
/**
* Disallow icon components as direct children of Button, ButtonGroupItem
* Icons must be passed via startIcon or endIcon props to ensure proper horizontal layout.
* When icons are children, they get wrapped in spans causing vertical stacking.
*
* WRONG: <Button><PlusIcon />Add Item</Button> -- icon appears ABOVE text
* RIGHT: <Button startIcon={<PlusIcon />}>Add Item</Button> -- icon appears LEFT of text
*/
'no-icon-children': {
meta: {
type: 'problem',
docs: {
description: 'Disallow icon components as children of Button. Use startIcon/endIcon props instead.',
recommended: true,
},
messages: {
useIconProps: 'Icon "{{iconName}}" should be passed via startIcon or endIcon prop, not as a child. Icons as children cause vertical stacking. Use: <Button startIcon={<{{iconName}} />}>Text</Button>',
useIconPropsGeneric: 'Icons should be passed via startIcon or endIcon prop, not as children. Icons as children cause vertical stacking.',
},
schema: [],
},
create(context) {
// Components that support startIcon/endIcon props
const buttonComponents = ['Button', 'ButtonGroupItem'];
// Pattern to detect icon component names (ends with Icon or is a known icon)
const iconPatterns = [
/Icon$/, // Any component ending in "Icon" (PlusIcon, RefreshCwIcon, etc.)
/^Icon[A-Z]/, // Icon prefix pattern
];
function isIconComponent(name) {
return iconPatterns.some(pattern => pattern.test(name));
}
return {
JSXElement(node) {
const openingElement = node.openingElement;
// Check if this is a Button or ButtonGroupItem
if (openingElement.name.type !== 'JSXIdentifier') {
return;
}
const componentName = openingElement.name.name;
if (!buttonComponents.includes(componentName)) {
return;
}
// Check if startIcon or endIcon props are already provided
const hasIconProp = openingElement.attributes.some(attr => {
return attr.type === 'JSXAttribute' &&
attr.name &&
(attr.name.name === 'startIcon' || attr.name.name === 'endIcon' || attr.name.name === 'icon');
});
// If icon props are present, likely the component is correctly configured
// Still check children for additional icons
// Check children for icon components
const children = node.children || [];
for (const child of children) {
// Direct JSX element child
if (child.type === 'JSXElement') {
const childName = child.openingElement.name;
if (childName.type === 'JSXIdentifier' && isIconComponent(childName.name)) {
context.report({
node: child,
messageId: 'useIconProps',
data: { iconName: childName.name },
});
}
}
// JSX expression containing icon (e.g., {loading ? <Spinner /> : <PlusIcon />})
if (child.type === 'JSXExpressionContainer') {
const expression = child.expression;
// Direct icon in expression: {<PlusIcon />}
if (expression.type === 'JSXElement') {
const exprName = expression.openingElement.name;
if (exprName.type === 'JSXIdentifier' && isIconComponent(exprName.name)) {
context.report({
node: expression,
messageId: 'useIconProps',
data: { iconName: exprName.name },
});
}
}
// Conditional expression: {condition ? <IconA /> : <IconB />}
if (expression.type === 'ConditionalExpression') {
const consequent = expression.consequent;
const alternate = expression.alternate;
if (consequent.type === 'JSXElement') {
const consName = consequent.openingElement.name;
if (consName.type === 'JSXIdentifier' && isIconComponent(consName.name)) {
context.report({
node: consequent,
messageId: 'useIconProps',
data: { iconName: consName.name },
});
}
}
if (alternate.type === 'JSXElement') {
const altName = alternate.openingElement.name;
if (altName.type === 'JSXIdentifier' && isIconComponent(altName.name)) {
context.report({
node: alternate,
messageId: 'useIconProps',
data: { iconName: altName.name },
});
}
}
}
// Logical expression: {showIcon && <PlusIcon />}
if (expression.type === 'LogicalExpression') {
const right = expression.right;
if (right.type === 'JSXElement') {
const rightName = right.openingElement.name;
if (rightName.type === 'JSXIdentifier' && isIconComponent(rightName.name)) {
context.report({
node: right,
messageId: 'useIconProps',
data: { iconName: rightName.name },
});
}
}
}
}
}
},
};
},
},
},
};

View File

@@ -97,8 +97,7 @@ export default function PaymentHistory() {
<h2 className="text-2xl font-semibold text-gray-900 dark:text-white">
Payment History
</h2>
<Button onClick={loadPayments} variant="outline" size="sm">
<RefreshCwIcon className="w-4 h-4 mr-2" />
<Button onClick={loadPayments} variant="outline" size="sm" startIcon={<RefreshCwIcon className="w-4 h-4" />}>
Refresh
</Button>
</div>

View File

@@ -3,7 +3,9 @@ import Switch from '../form/switch/Switch';
import Button from '../ui/button/Button';
import Badge from '../ui/badge/Badge';
import SiteSetupChecklist from '../sites/SiteSetupChecklist';
import SiteTypeBadge from '../sites/SiteTypeBadge';
import { Site } from '../../services/api';
import { BoxCubeIcon as SettingsIcon, EyeIcon, FileIcon } from '../../icons';
interface SiteCardProps {
site: Site;
@@ -68,16 +70,17 @@ export default function SiteCard({
</p>
)}
<div className="flex items-center gap-2 mb-2 flex-wrap">
<SiteTypeBadge hostingType={site.hosting_type} />
{site.industry_name && (
<Badge variant="light" color="info" className="text-xs">
<Badge variant="soft" color="warning" size="sm">
{site.industry_name}
</Badge>
)}
<Badge variant="light" color="info" className="text-xs">
<Badge variant="soft" color="neutral" size="sm">
{site.active_sectors_count} / 5 Sectors
</Badge>
{site.status && (
<Badge variant="light" color={site.status === 'active' ? 'success' : 'dark'} className="text-xs">
<Badge variant={site.is_active ? 'solid' : 'soft'} color={site.status === 'active' ? 'success' : 'neutral'} size="sm">
{site.status}
</Badge>
)}
@@ -105,46 +108,34 @@ export default function SiteCard({
/>
</div>
</div>
<div className="flex items-center justify-between border-t border-gray-200 p-5 dark:border-gray-800">
<div className="flex gap-3">
<div className="flex items-center justify-between border-t border-gray-200 p-4 dark:border-gray-800">
<div className="flex gap-2 flex-1">
<Button
variant="outline"
size="md"
onClick={() => onSettings(site)}
title="Configure Site - Update connection details and publishing settings"
className="shadow-theme-xs inline-flex h-11 w-11 items-center justify-center rounded-lg border border-gray-300 text-gray-700 dark:border-gray-700 dark:text-gray-400"
variant="primary"
tone="brand"
size="sm"
onClick={() => onDetails(site)}
startIcon={<EyeIcon className="w-4 h-4" />}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
>
<path
d="M5.64615 4.59906C5.05459 4.25752 4.29808 4.46015 3.95654 5.05171L2.69321 7.23986C2.35175 7.83128 2.5544 8.58754 3.14582 8.92899C3.97016 9.40493 3.97017 10.5948 3.14583 11.0707C2.55441 11.4122 2.35178 12.1684 2.69323 12.7598L3.95657 14.948C4.2981 15.5395 5.05461 15.7422 5.64617 15.4006C6.4706 14.9247 7.50129 15.5196 7.50129 16.4715C7.50129 17.1545 8.05496 17.7082 8.73794 17.7082H11.2649C11.9478 17.7082 12.5013 17.1545 12.5013 16.4717C12.5013 15.5201 13.5315 14.9251 14.3556 15.401C14.9469 15.7423 15.7029 15.5397 16.0443 14.9485L17.3079 12.7598C17.6494 12.1684 17.4467 11.4121 16.8553 11.0707C16.031 10.5948 16.031 9.40494 16.8554 8.92902C17.4468 8.58757 17.6494 7.83133 17.3079 7.23992L16.0443 5.05123C15.7029 4.45996 14.9469 4.25737 14.3556 4.59874C13.5315 5.07456 12.5013 4.47961 12.5013 3.52798C12.5013 2.84515 11.9477 2.2915 11.2649 2.2915L8.73795 2.2915C8.05496 2.2915 7.50129 2.84518 7.50129 3.52816C7.50129 4.48015 6.47059 5.07505 5.64615 4.59906Z"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M12.5714 9.99977C12.5714 11.4196 11.4204 12.5706 10.0005 12.5706C8.58069 12.5706 7.42969 11.4196 7.42969 9.99977C7.42969 8.57994 8.58069 7.42894 10.0005 7.42894C11.4204 7.42894 12.5714 8.57994 12.5714 9.99977Z"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
Dashboard
</Button>
<Button
variant="secondary"
tone="neutral"
size="sm"
onClick={() => onDetails(site)}
startIcon={<FileIcon className="w-4 h-4" />}
>
Content
</Button>
<Button
variant="outline"
size="md"
onClick={() => onDetails(site)}
title="View Site Details - See all information about this website"
className="shadow-theme-xs inline-flex h-11 items-center justify-center rounded-lg border border-gray-300 px-4 py-3 text-sm font-medium text-gray-700 dark:border-gray-700 dark:text-gray-400"
tone="neutral"
size="sm"
onClick={() => onSettings(site)}
startIcon={<SettingsIcon className="w-4 h-4" />}
>
View Details
Settings
</Button>
</div>
<Switch

View File

@@ -6,13 +6,6 @@
import { useNavigate } from 'react-router-dom';
import Button from '../ui/button/Button';
import {
ListIcon,
GroupIcon,
BoltIcon,
FileTextIcon,
FileIcon,
CheckCircleIcon,
PaperPlaneIcon,
HelpCircleIcon,
} from '../../icons';
@@ -34,75 +27,75 @@ interface QuickActionsWidgetProps {
const workflowSteps = [
{
num: 1,
icon: ListIcon,
title: 'Add Keywords',
description: 'Import your target keywords manually or from CSV',
href: '/planner/keyword-opportunities',
actionLabel: 'Add',
gradient: 'from-brand-500 to-brand-600',
buttonTone: 'brand' as const,
},
{
num: 2,
icon: GroupIcon,
title: 'Auto Cluster',
description: 'AI groups related keywords into content clusters',
href: '/planner/clusters',
href: '/planner/keyword-opportunities', // Clustering runs from keywords page
actionLabel: 'Cluster',
gradient: 'from-purple-500 to-purple-600',
buttonTone: 'brand' as const,
},
{
num: 3,
icon: BoltIcon,
title: 'Generate Ideas',
description: 'Create content ideas from your keyword clusters',
href: '/planner/ideas',
actionLabel: 'Ideas',
gradient: 'from-warning-500 to-warning-600',
buttonTone: 'warning' as const,
},
{
num: 4,
icon: CheckCircleIcon,
title: 'Create Tasks',
description: 'Convert approved ideas into content tasks',
href: '/writer/tasks',
actionLabel: 'Tasks',
gradient: 'from-brand-500 to-brand-600',
buttonTone: 'brand' as const,
},
{
num: 5,
icon: FileTextIcon,
title: 'Generate Content',
description: 'AI writes SEO-optimized articles from tasks',
href: '/writer/content',
actionLabel: 'Write',
gradient: 'from-success-500 to-success-600',
buttonTone: 'success' as const,
},
{
num: 6,
icon: FileIcon,
title: 'Generate Images',
description: 'Create featured images and media for articles',
href: '/writer/images',
actionLabel: 'Images',
gradient: 'from-purple-500 to-purple-600',
buttonTone: 'brand' as const,
},
{
num: 7,
icon: CheckCircleIcon,
title: 'Review & Approve',
description: 'Quality check and approve generated content',
href: '/writer/review',
actionLabel: 'Review',
gradient: 'from-warning-500 to-warning-600',
buttonTone: 'warning' as const,
},
{
num: 8,
icon: PaperPlaneIcon,
title: 'Publish to WP',
description: 'Push approved content to your WordPress site',
href: '/writer/published',
actionLabel: 'Publish',
gradient: 'from-success-500 to-success-600',
buttonTone: 'success' as const,
},
];
@@ -132,7 +125,6 @@ export default function QuickActionsWidget({ onAddKeywords }: QuickActionsWidget
{/* Column 1: Steps 1-3 */}
<div className="space-y-2.5">
{workflowSteps.slice(0, 3).map((step) => {
const Icon = step.icon;
return (
<div
key={step.num}
@@ -143,11 +135,6 @@ export default function QuickActionsWidget({ onAddKeywords }: QuickActionsWidget
{step.num}
</span>
{/* Icon with solid gradient background */}
<div className={`flex-shrink-0 p-1.5 rounded-lg bg-gradient-to-br ${step.gradient} shadow-sm`}>
<Icon className="w-4 h-4 text-white" />
</div>
{/* Text Content */}
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-gray-800 dark:text-gray-200">
@@ -158,13 +145,13 @@ export default function QuickActionsWidget({ onAddKeywords }: QuickActionsWidget
</p>
</div>
{/* Action Button */}
{/* Action Button - matches step number color */}
<Button
size="xs"
variant="outline"
tone="brand"
variant="primary"
tone={step.buttonTone}
onClick={() => navigate(step.href)}
className="flex-shrink-0 opacity-70 group-hover:opacity-100 transition-opacity"
className="flex-shrink-0"
>
{step.actionLabel}
</Button>
@@ -176,7 +163,6 @@ export default function QuickActionsWidget({ onAddKeywords }: QuickActionsWidget
{/* Column 2: Steps 4-6 */}
<div className="space-y-2.5">
{workflowSteps.slice(3, 6).map((step) => {
const Icon = step.icon;
return (
<div
key={step.num}
@@ -187,11 +173,6 @@ export default function QuickActionsWidget({ onAddKeywords }: QuickActionsWidget
{step.num}
</span>
{/* Icon with solid gradient background */}
<div className={`flex-shrink-0 p-1.5 rounded-lg bg-gradient-to-br ${step.gradient} shadow-sm`}>
<Icon className="w-4 h-4 text-white" />
</div>
{/* Text Content */}
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-gray-800 dark:text-gray-200">
@@ -202,13 +183,13 @@ export default function QuickActionsWidget({ onAddKeywords }: QuickActionsWidget
</p>
</div>
{/* Action Button */}
{/* Action Button - matches step number color */}
<Button
size="xs"
variant="outline"
tone="brand"
variant="primary"
tone={step.buttonTone}
onClick={() => navigate(step.href)}
className="flex-shrink-0 opacity-70 group-hover:opacity-100 transition-opacity"
className="flex-shrink-0"
>
{step.actionLabel}
</Button>
@@ -220,7 +201,6 @@ export default function QuickActionsWidget({ onAddKeywords }: QuickActionsWidget
{/* Column 3: Steps 7-8 */}
<div className="space-y-2.5">
{workflowSteps.slice(6, 8).map((step) => {
const Icon = step.icon;
return (
<div
key={step.num}
@@ -231,11 +211,6 @@ export default function QuickActionsWidget({ onAddKeywords }: QuickActionsWidget
{step.num}
</span>
{/* Icon with solid gradient background */}
<div className={`flex-shrink-0 p-1.5 rounded-lg bg-gradient-to-br ${step.gradient} shadow-sm`}>
<Icon className="w-4 h-4 text-white" />
</div>
{/* Text Content */}
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-gray-800 dark:text-gray-200">
@@ -246,13 +221,13 @@ export default function QuickActionsWidget({ onAddKeywords }: QuickActionsWidget
</p>
</div>
{/* Action Button */}
{/* Action Button - matches step number color */}
<Button
size="xs"
variant="outline"
tone="brand"
variant="primary"
tone={step.buttonTone}
onClick={() => navigate(step.href)}
className="flex-shrink-0 opacity-70 group-hover:opacity-100 transition-opacity"
className="flex-shrink-0"
>
{step.actionLabel}
</Button>

View File

@@ -27,9 +27,7 @@ export default function DemographicCard() {
</p>
</div>
<div className="relative inline-block">
<IconButton variant="ghost" size="sm" onClick={toggleDropdown} aria-label="More options">
<MoreDotIcon className="text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 size-6" />
</IconButton>
<IconButton variant="ghost" size="sm" onClick={toggleDropdown} aria-label="More options" icon={<MoreDotIcon className="text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 size-6" />} />
<Dropdown
isOpen={isOpen}
onClose={closeDropdown}

View File

@@ -108,9 +108,7 @@ export default function MonthlySalesChart() {
Monthly Sales
</h3>
<div className="relative inline-block">
<IconButton variant="ghost" size="sm" onClick={toggleDropdown} aria-label="More options">
<MoreDotIcon className="text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 size-6" />
</IconButton>
<IconButton variant="ghost" size="sm" onClick={toggleDropdown} aria-label="More options" icon={<MoreDotIcon className="text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 size-6" />} />
<Dropdown
isOpen={isOpen}
onClose={closeDropdown}

View File

@@ -77,9 +77,7 @@ export default function MonthlyTarget() {
</p>
</div>
<div className="relative inline-block">
<IconButton variant="ghost" size="sm" onClick={toggleDropdown} aria-label="More options">
<MoreDotIcon className="text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 size-6" />
</IconButton>
<IconButton variant="ghost" size="sm" onClick={toggleDropdown} aria-label="More options" icon={<MoreDotIcon className="text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 size-6" />} />
<Dropdown
isOpen={isOpen}
onClose={closeDropdown}

View File

@@ -19,15 +19,15 @@ const Checkbox: React.FC<CheckboxProps> = ({
}) => {
return (
<label
className={`flex items-center space-x-3 group cursor-pointer ${
className={`flex items-center gap-2.5 group cursor-pointer ${
disabled ? "cursor-not-allowed opacity-60" : ""
}`}
>
<div className="relative w-4 h-4">
<div className="relative flex items-center justify-center w-5 h-5">
<input
id={id}
type="checkbox"
className={`w-4 h-4 appearance-none cursor-pointer dark:border-gray-700 border border-gray-300 checked:border-transparent rounded-md checked:bg-brand-500 disabled:opacity-60
className={`w-5 h-5 appearance-none cursor-pointer dark:border-gray-700 border border-gray-300 checked:border-transparent rounded-md checked:bg-brand-500 disabled:opacity-60
${className}`}
checked={checked}
onChange={(e) => onChange(e.target.checked)}
@@ -35,35 +35,31 @@ const Checkbox: React.FC<CheckboxProps> = ({
/>
{checked && (
<svg
className="absolute transform -translate-x-1/2 -translate-y-1/2 pointer-events-none top-1/2 left-1/2"
className="absolute w-3 h-3 pointer-events-none text-white"
xmlns="http://www.w3.org/2000/svg"
width="12"
height="12"
viewBox="0 0 12 12"
fill="none"
>
<path
d="M11.6666 3.5L5.24992 9.91667L2.33325 7"
stroke="white"
strokeWidth="1.94437"
d="M10 3L4.5 8.5L2 6"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)}
{disabled && (
{disabled && !checked && (
<svg
className="absolute transform -translate-x-1/2 -translate-y-1/2 pointer-events-none top-1/2 left-1/2"
className="absolute w-3 h-3 pointer-events-none text-gray-300"
xmlns="http://www.w3.org/2000/svg"
width="12"
height="12"
viewBox="0 0 12 12"
fill="none"
>
<path
d="M11.6666 3.5L5.24992 9.91667L2.33325 7"
stroke="#E4E7EC"
strokeWidth="2.33333"
d="M10 3L4.5 8.5L2 6"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>

View File

@@ -128,13 +128,30 @@ export default function NotificationDropdown() {
return (
<div className="relative">
<IconButton
ref={buttonRef as React.RefObject<HTMLButtonElement>}
variant="outline"
className="relative dropdown-toggle h-11 w-11"
onClick={handleClick}
aria-label={`Notifications ${unreadCount > 0 ? `(${unreadCount} unread)` : ''}`}
>
<div className="relative">
<IconButton
ref={buttonRef as React.RefObject<HTMLButtonElement>}
variant="outline"
tone="neutral"
size="md"
icon={
<svg
width="20"
height="20"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M10.75 2.29248C10.75 1.87827 10.4143 1.54248 10 1.54248C9.58583 1.54248 9.25004 1.87827 9.25004 2.29248V2.83613C6.08266 3.20733 3.62504 5.9004 3.62504 9.16748V14.4591H3.33337C2.91916 14.4591 2.58337 14.7949 2.58337 15.2091C2.58337 15.6234 2.91916 15.9591 3.33337 15.9591H4.37504H15.625H16.6667C17.0809 15.9591 17.4167 15.6234 17.4167 15.2091C17.4167 14.7949 17.0809 14.4591 16.6667 14.4591H16.375V9.16748C16.375 5.9004 13.9174 3.20733 10.75 2.83613V2.29248ZM14.875 14.4591V9.16748C14.875 6.47509 12.6924 4.29248 10 4.29248C7.30765 4.29248 5.12504 6.47509 5.12504 9.16748V14.4591H14.875ZM8.00004 17.7085C8.00004 18.1228 8.33583 18.4585 8.75004 18.4585H11.25C11.6643 18.4585 12 18.1228 12 17.7085C12 17.2943 11.6643 16.9585 11.25 16.9585H8.75004C8.33583 16.9585 8.00004 17.2943 8.00004 17.7085Z"
/>
</svg>
}
onClick={handleClick}
aria-label={`Notifications ${unreadCount > 0 ? `(${unreadCount} unread)` : ''}`}
/>
{/* Notification badge */}
{unreadCount > 0 && (
<span className="absolute -right-0.5 -top-0.5 z-10 flex h-5 w-5 items-center justify-center rounded-full bg-warning-500 text-[10px] font-semibold text-white">
@@ -142,21 +159,7 @@ export default function NotificationDropdown() {
<span className="absolute inline-flex w-full h-full bg-warning-400 rounded-full opacity-75 animate-ping"></span>
</span>
)}
<svg
className="fill-current"
width="20"
height="20"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M10.75 2.29248C10.75 1.87827 10.4143 1.54248 10 1.54248C9.58583 1.54248 9.25004 1.87827 9.25004 2.29248V2.83613C6.08266 3.20733 3.62504 5.9004 3.62504 9.16748V14.4591H3.33337C2.91916 14.4591 2.58337 14.7949 2.58337 15.2091C2.58337 15.6234 2.91916 15.9591 3.33337 15.9591H4.37504H15.625H16.6667C17.0809 15.9591 17.4167 15.6234 17.4167 15.2091C17.4167 14.7949 17.0809 14.4591 16.6667 14.4591H16.375V9.16748C16.375 5.9004 13.9174 3.20733 10.75 2.83613V2.29248ZM14.875 14.4591V9.16748C14.875 6.47509 12.6924 4.29248 10 4.29248C7.30765 4.29248 5.12504 6.47509 5.12504 9.16748V14.4591H14.875ZM8.00004 17.7085C8.00004 18.1228 8.33583 18.4585 8.75004 18.4585H11.25C11.6643 18.4585 12 18.1228 12 17.7085C12 17.2943 11.6643 16.9585 11.25 16.9585H8.75004C8.33583 16.9585 8.00004 17.2943 8.00004 17.7085Z"
fill="currentColor"
/>
</svg>
</IconButton>
</div>
<Dropdown
isOpen={isOpen}

View File

@@ -2,7 +2,6 @@ import { useState, useRef } from "react";
import { useNavigate } from "react-router-dom";
import { DropdownItem } from "../ui/dropdown/DropdownItem";
import { Dropdown } from "../ui/dropdown/Dropdown";
import { Link } from "react-router-dom";
import { useAuthStore } from "../../store/authStore";
import Button from "../ui/button/Button";
@@ -26,33 +25,28 @@ export default function UserDropdown() {
closeDropdown();
};
return (
<div className="relative">
<Button
<div className="relative flex-shrink-0">
<button
ref={buttonRef}
onClick={toggleDropdown}
variant="ghost"
tone="neutral"
className="flex items-center text-gray-700 dropdown-toggle dark:text-gray-400"
className="flex items-center gap-2 px-2 py-1.5 rounded-lg text-gray-700 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
>
<span className="mr-3 overflow-hidden rounded-full h-11 w-11 bg-brand-500 flex items-center justify-center">
<span className="overflow-hidden rounded-full h-9 w-9 bg-brand-500 flex items-center justify-center flex-shrink-0">
{user?.email ? (
<span className="text-white font-semibold text-sm">
{user.email.charAt(0).toUpperCase()}
</span>
) : (
<img src="/images/user/owner.jpg" alt="User" />
<img src="/images/user/owner.jpg" alt="User" className="h-full w-full object-cover" />
)}
</span>
<span className="block mr-1 font-medium text-theme-sm">
<span className="font-medium text-sm whitespace-nowrap">
{user?.username || user?.email?.split("@")[0] || "User"}
</span>
<svg
className={`stroke-gray-500 dark:stroke-gray-400 transition-transform duration-200 ${
className={`w-4 h-4 text-gray-500 dark:text-gray-400 transition-transform duration-200 flex-shrink-0 ${
isOpen ? "rotate-180" : ""
}`}
width="18"
height="20"
viewBox="0 0 18 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
@@ -65,7 +59,7 @@ export default function UserDropdown() {
strokeLinejoin="round"
/>
</svg>
</Button>
</button>
<Dropdown
isOpen={isOpen}

View File

@@ -301,8 +301,8 @@ export default function SiteIntegrationsSection({ siteId }: SiteIntegrationsSect
size="sm"
onClick={() => handleSync(integration)}
className="flex-1"
startIcon={<RefreshCwIcon className="w-4 h-4" />}
>
<RefreshCwIcon className="w-4 h-4 mr-1" />
Sync
</Button>
)}

View File

@@ -7,6 +7,7 @@ import React, { useState, useCallback, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { Card } from '../ui/card';
import Button from '../ui/button/Button';
import IconButton from '../ui/button/IconButton';
import {
ArrowRightIcon,
ArrowLeftIcon,
@@ -209,14 +210,13 @@ export default function OnboardingWizard({ onComplete, onSkip }: OnboardingWizar
</p>
</div>
</div>
<Button
<IconButton
variant="ghost"
size="sm"
onClick={handleSkipAll}
className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
>
<CloseIcon className="w-5 h-5" />
</Button>
icon={<CloseIcon className="w-5 h-5" />}
/>
</div>
{/* Progress Bar */}

View File

@@ -14,7 +14,7 @@ import {
CopyIcon,
CheckCircleIcon,
TimeIcon,
ArrowUpIcon,
DownloadIcon,
} from '../../../icons';
import { integrationApi } from '../../../services/integration.api';
import { useToast } from '../../ui/toast/ToastContainer';
@@ -115,8 +115,12 @@ export default function Step3ConnectIntegration({
title: 'Download the Plugin',
description: 'Get the IGNY8 Bridge plugin from our dashboard',
action: (
<Button variant="outline" size="sm" className="gap-1">
<ArrowUpIcon className="w-3 h-3" />
<Button
variant="outline"
tone="brand"
size="sm"
startIcon={<DownloadIcon className="w-3 h-3" />}
>
Download Plugin
</Button>
),
@@ -174,9 +178,9 @@ export default function Step3ConnectIntegration({
variant="outline"
size="sm"
onClick={handleCopyApiKey}
className="gap-1 flex-shrink-0"
className="flex-shrink-0"
startIcon={<CopyIcon className="w-4 h-4" />}
>
<CopyIcon className="w-4 h-4" />
Copy
</Button>
</div>

View File

@@ -167,13 +167,12 @@ export default function Step4AddKeywords({
className="pl-10"
/>
</div>
<Button
<IconButton
variant="outline"
onClick={handleAddKeyword}
disabled={!inputValue.trim()}
>
<PlusIcon className="w-4 h-4" />
</Button>
icon={<PlusIcon className="w-4 h-4" />}
/>
</div>
<p className="text-xs text-gray-500 mt-1">
Tip: Paste a comma-separated list or one keyword per line

View File

@@ -118,9 +118,8 @@ export default function PublishingRules({ rules, onChange }: PublishingRulesProp
Example: Publish blog posts to WordPress but guides to your main site
</p>
</div>
<Button onClick={handleAddRule} variant="primary" size="sm">
<PlusIcon className="w-4 h-4 mr-2" />
+ Add a Publishing Rule
<Button onClick={handleAddRule} variant="primary" size="sm" startIcon={<PlusIcon className="w-4 h-4" />}>
Add a Publishing Rule
</Button>
</div>

View File

@@ -7,6 +7,7 @@ import React, { useState } from 'react';
import { EyeIcon, XIcon, Maximize2Icon } from '../../icons';
import { Card } from '../../ui/card';
import Button from '../../ui/button/Button';
import IconButton from '../../ui/button/IconButton';
export interface LayoutPreviewProps {
layoutId: string;
@@ -145,15 +146,12 @@ export default function LayoutPreview({ layoutId, layoutName, onClose, onSelect
{isFullscreen ? 'Exit' : 'Fullscreen'}
</Button>
{onSelect && (
<Button variant="primary" size="sm" onClick={onSelect}>
<EyeIcon className="w-4 h-4 mr-2" />
<Button variant="primary" size="sm" onClick={onSelect} startIcon={<EyeIcon className="w-4 h-4" />}>
Select Layout
</Button>
)}
{onClose && (
<Button variant="ghost" size="sm" onClick={onClose}>
<XIcon className="w-4 h-4" />
</Button>
<IconButton variant="ghost" size="sm" onClick={onClose} icon={<XIcon className="w-4 h-4" />} />
)}
</div>
</div>

View File

@@ -9,9 +9,10 @@ import Badge from '../ui/badge/Badge';
interface SiteTypeBadgeProps {
hostingType: string;
className?: string;
size?: 'sm' | 'md' | 'lg';
}
export default function SiteTypeBadge({ hostingType, className = '' }: SiteTypeBadgeProps) {
export default function SiteTypeBadge({ hostingType, className = '', size = 'sm' }: SiteTypeBadgeProps) {
const getTypeInfo = () => {
switch (hostingType) {
case 'igny8_sites':
@@ -41,11 +42,11 @@ export default function SiteTypeBadge({ hostingType, className = '' }: SiteTypeB
<Badge
variant="soft"
color={typeInfo.color}
size={size}
startIcon={typeInfo.icon}
className={className}
>
{typeInfo.label}
</Badge>
);
}
}

View File

@@ -90,14 +90,12 @@ export default function StyleEditor({ styleSettings, onChange, onSave, onReset }
</div>
<div className="flex gap-2">
{onReset && (
<Button variant="outline" onClick={onReset}>
<RefreshCwIcon className="w-4 h-4 mr-2" />
<Button variant="outline" onClick={onReset} startIcon={<RefreshCwIcon className="w-4 h-4" />}>
Reset
</Button>
)}
{onSave && (
<Button variant="primary" onClick={onSave}>
<SaveIcon className="w-4 h-4 mr-2" />
<Button variant="primary" onClick={onSave} startIcon={<SaveIcon className="w-4 h-4" />}>
Save Styles
</Button>
)}
@@ -112,8 +110,8 @@ export default function StyleEditor({ styleSettings, onChange, onSave, onReset }
tone="brand"
size="sm"
onClick={() => setActiveTab('css')}
startIcon={<CodeIcon className="w-4 h-4" />}
>
<CodeIcon className="w-4 h-4 mr-2" />
Custom CSS
</Button>
<Button
@@ -121,8 +119,8 @@ export default function StyleEditor({ styleSettings, onChange, onSave, onReset }
tone="brand"
size="sm"
onClick={() => setActiveTab('colors')}
startIcon={<PaletteIcon className="w-4 h-4" />}
>
<PaletteIcon className="w-4 h-4 mr-2" />
Colors
</Button>
<Button
@@ -130,8 +128,8 @@ export default function StyleEditor({ styleSettings, onChange, onSave, onReset }
tone="brand"
size="sm"
onClick={() => setActiveTab('typography')}
startIcon={<TypeIcon className="w-4 h-4" />}
>
<TypeIcon className="w-4 h-4 mr-2" />
Typography
</Button>
<Button

View File

@@ -74,8 +74,8 @@ export default function TemplateCustomizer({
tone="brand"
size="sm"
onClick={() => setActiveTab('layout')}
startIcon={<LayoutIcon className="w-4 h-4" />}
>
<LayoutIcon className="w-4 h-4 mr-2" />
Layout
</Button>
<Button
@@ -83,8 +83,8 @@ export default function TemplateCustomizer({
tone="brand"
size="sm"
onClick={() => setActiveTab('colors')}
startIcon={<PaletteIcon className="w-4 h-4" />}
>
<PaletteIcon className="w-4 h-4 mr-2" />
Colors
</Button>
<Button
@@ -92,8 +92,8 @@ export default function TemplateCustomizer({
tone="brand"
size="sm"
onClick={() => setActiveTab('typography')}
startIcon={<TypeIcon className="w-4 h-4" />}
>
<TypeIcon className="w-4 h-4 mr-2" />
Typography
</Button>
<Button
@@ -101,8 +101,8 @@ export default function TemplateCustomizer({
tone="brand"
size="sm"
onClick={() => setActiveTab('spacing')}
startIcon={<SettingsIcon className="w-4 h-4" />}
>
<SettingsIcon className="w-4 h-4 mr-2" />
Spacing
</Button>
</div>

View File

@@ -8,6 +8,7 @@ import { useNavigate } from 'react-router-dom';
import { GlobeIcon, CheckCircleIcon, XCircleIcon, SettingsIcon, RefreshCwIcon, AlertCircleIcon, ExternalLinkIcon } from '../../icons';
import { Card } from '../ui/card';
import Button from '../ui/button/Button';
import IconButton from '../ui/button/IconButton';
import Badge from '../ui/badge/Badge';
interface WordPressIntegration {
@@ -175,8 +176,8 @@ export default function WordPressIntegrationCard({
variant="outline"
size="sm"
className="flex-1"
startIcon={<SettingsIcon className="w-4 h-4" />}
>
<SettingsIcon className="w-4 h-4 mr-2" />
Manage
</Button>
{siteId && (
@@ -190,14 +191,13 @@ export default function WordPressIntegrationCard({
</Button>
)}
{onSync && (
<Button
<IconButton
onClick={onSync}
variant="outline"
size="sm"
disabled={loading}
>
<RefreshCwIcon className={`w-4 h-4 ${loading ? 'animate-spin' : ''}`} />
</Button>
icon={<RefreshCwIcon className={`w-4 h-4 ${loading ? 'animate-spin' : ''}`} />}
/>
)}
</div>
</div>

View File

@@ -286,16 +286,14 @@ export default function WordPressIntegrationForm({
type={apiKeyVisible ? 'text' : 'password'}
value={apiKeyVisible ? apiKey : maskApiKey(apiKey)}
/>
<Button
<IconButton
onClick={handleCopyApiKey}
variant="outline"
tone="neutral"
size="sm"
className="absolute top-1/2 right-0 -translate-y-1/2 rounded-l-none"
>
<CopyIcon className="w-4 h-4" />
Copy
</Button>
icon={<CopyIcon className="w-4 h-4" />}
/>
</div>
<div className="group relative inline-block">
<IconButton
@@ -303,9 +301,8 @@ export default function WordPressIntegrationForm({
disabled={generatingKey}
variant="outline"
title="Regenerate"
>
<RefreshCwIcon className={`w-5 h-5 ${generatingKey ? 'animate-spin' : ''}`} />
</IconButton>
icon={<RefreshCwIcon className={`w-5 h-5 ${generatingKey ? 'animate-spin' : ''}`} />}
/>
<div className="invisible absolute bottom-full left-1/2 z-50 mb-2.5 -translate-x-1/2 opacity-0 transition-opacity duration-300 group-hover:visible group-hover:opacity-100">
<div className="relative">
<div className="rounded-lg bg-white px-3 py-2 text-xs font-medium whitespace-nowrap text-gray-700 shadow-xs dark:bg-gray-800 dark:text-white">
@@ -348,18 +345,16 @@ export default function WordPressIntegrationForm({
disabled={generatingKey}
variant="ghost"
title="Regenerate API key"
>
<RefreshCwIcon className={`w-5 h-5 ${generatingKey ? 'animate-spin' : ''}`} />
</IconButton>
icon={<RefreshCwIcon className={`w-5 h-5 ${generatingKey ? 'animate-spin' : ''}`} />}
/>
<IconButton
onClick={handleRevokeApiKey}
disabled={generatingKey}
variant="ghost"
tone="danger"
title="Revoke API key"
>
<TrashBinIcon className="w-5 h-5" />
</IconButton>
icon={<TrashBinIcon className="w-5 h-5" />}
/>
</div>
</td>
</tr>
@@ -389,8 +384,8 @@ export default function WordPressIntegrationForm({
<Button
onClick={handleDownloadPlugin}
variant="solid"
startIcon={<DownloadIcon className="w-4 h-4" />}
>
<DownloadIcon className="w-4 h-4 mr-2" />
Download Plugin
</Button>
</div>

View File

@@ -24,6 +24,7 @@ interface ButtonGroupItemProps {
isActive?: boolean;
className?: string;
disabled?: boolean;
startIcon?: ReactNode;
}
export const ButtonGroupItem: React.FC<ButtonGroupItemProps> = ({
@@ -32,18 +33,20 @@ export const ButtonGroupItem: React.FC<ButtonGroupItemProps> = ({
isActive = false,
className = "",
disabled = false,
startIcon,
}) => {
return (
<button
onClick={onClick}
disabled={disabled}
className={`px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 hover:text-gray-900 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-white disabled:opacity-50 disabled:cursor-not-allowed ${
className={`inline-flex items-center gap-1.5 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 hover:text-gray-900 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-white disabled:opacity-50 disabled:cursor-not-allowed ${
isActive
? "bg-gray-100 text-gray-900 dark:bg-white/10 dark:text-white"
: ""
} ${className}`}
type="button"
>
{startIcon}
{children}
</button>
);

View File

@@ -51,7 +51,7 @@ export const Modal: React.FC<ModalProps> = ({
const contentClasses = isFullscreen
? "w-full h-full"
: "relative w-full rounded-3xl bg-white dark:bg-gray-900";
: "relative w-full max-w-lg mx-4 rounded-3xl bg-white dark:bg-gray-900 shadow-xl";
return (
<div className="fixed inset-0 flex items-center justify-center overflow-y-auto modal z-99999">

View File

@@ -1,15 +1,14 @@
<svg
class="fill-current"
width="12"
height="12"
viewBox="0 0 12 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M5.31462 10.3761C5.45194 10.5293 5.65136 10.6257 5.87329 10.6257C5.8736 10.6257 5.8739 10.6257 5.87421 10.6257C6.0663 10.6259 6.25845 10.5527 6.40505 10.4062L9.40514 7.4082C9.69814 7.11541 9.69831 6.64054 9.40552 6.34754C9.11273 6.05454 8.63785 6.05438 8.34486 6.34717L6.62329 8.06753L6.62329 1.875C6.62329 1.46079 6.28751 1.125 5.87329 1.125C5.45908 1.125 5.12329 1.46079 5.12329 1.875L5.12329 8.06422L3.40516 6.34719C3.11218 6.05439 2.6373 6.05454 2.3445 6.34752C2.0517 6.64051 2.05185 7.11538 2.34484 7.40818L5.31462 10.3761Z"
fill=""
/>
</svg>
width="12"
height="12"
viewBox="0 0 12 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M5.31462 10.3761C5.45194 10.5293 5.65136 10.6257 5.87329 10.6257C5.8736 10.6257 5.8739 10.6257 5.87421 10.6257C6.0663 10.6259 6.25845 10.5527 6.40505 10.4062L9.40514 7.4082C9.69814 7.11541 9.69831 6.64054 9.40552 6.34754C9.11273 6.05454 8.63785 6.05438 8.34486 6.34717L6.62329 8.06753L6.62329 1.875C6.62329 1.46079 6.28751 1.125 5.87329 1.125C5.45908 1.125 5.12329 1.46079 5.12329 1.875L5.12329 8.06422L3.40516 6.34719C3.11218 6.05439 2.6373 6.05454 2.3445 6.34752C2.0517 6.64051 2.05185 7.11538 2.34484 7.40818L5.31462 10.3761Z"
fill="currentColor"
/>
</svg>

Before

Width:  |  Height:  |  Size: 858 B

After

Width:  |  Height:  |  Size: 735 B

View File

@@ -1,15 +1,14 @@
<svg
className="fill-current"
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M17.4175 9.9986C17.4178 10.1909 17.3446 10.3832 17.198 10.53L12.2013 15.5301C11.9085 15.8231 11.4337 15.8233 11.1407 15.5305C10.8477 15.2377 10.8475 14.7629 11.1403 14.4699L14.8604 10.7472L3.33301 10.7472C2.91879 10.7472 2.58301 10.4114 2.58301 9.99715C2.58301 9.58294 2.91879 9.24715 3.33301 9.24715L14.8549 9.24715L11.1403 5.53016C10.8475 5.23717 10.8477 4.7623 11.1407 4.4695C11.4336 4.1767 11.9085 4.17685 12.2013 4.46984L17.1588 9.43049C17.3173 9.568 17.4175 9.77087 17.4175 9.99715C17.4175 9.99763 17.4175 9.99812 17.4175 9.9986Z"
fill=""
/>
</svg>
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M17.4175 9.9986C17.4178 10.1909 17.3446 10.3832 17.198 10.53L12.2013 15.5301C11.9085 15.8231 11.4337 15.8233 11.1407 15.5305C10.8477 15.2377 10.8475 14.7629 11.1403 14.4699L14.8604 10.7472L3.33301 10.7472C2.91879 10.7472 2.58301 10.4114 2.58301 9.99715C2.58301 9.58294 2.91879 9.24715 3.33301 9.24715L14.8549 9.24715L11.1403 5.53016C10.8475 5.23717 10.8477 4.7623 11.1407 4.4695C11.4336 4.1767 11.9085 4.17685 12.2013 4.46984L17.1588 9.43049C17.3173 9.568 17.4175 9.77087 17.4175 9.99715C17.4175 9.99763 17.4175 9.99812 17.4175 9.9986Z"
fill="currentColor"
/>
</svg>

Before

Width:  |  Height:  |  Size: 897 B

After

Width:  |  Height:  |  Size: 742 B

View File

@@ -1,16 +1,14 @@
<svg
className="fill-current"
width="13"
height="12"
viewBox="0 0 13 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M6.06462 1.62393C6.20193 1.47072 6.40135 1.37432 6.62329 1.37432C6.6236 1.37432 6.62391 1.37432 6.62422 1.37432C6.81631 1.37415 7.00845 1.44731 7.15505 1.5938L10.1551 4.5918C10.4481 4.88459 10.4483 5.35946 10.1555 5.65246C9.86273 5.94546 9.38785 5.94562 9.09486 5.65283L7.37329 3.93247L7.37329 10.125C7.37329 10.5392 7.03751 10.875 6.62329 10.875C6.20908 10.875 5.87329 10.5392 5.87329 10.125L5.87329 3.93578L4.15516 5.65281C3.86218 5.94561 3.3873 5.94546 3.0945 5.65248C2.8017 5.35949 2.80185 4.88462 3.09484 4.59182L6.06462 1.62393Z"
fill=""
/>
</svg>
<svg
width="13"
height="12"
viewBox="0 0 13 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M6.06462 1.62393C6.20193 1.47072 6.40135 1.37432 6.62329 1.37432C6.6236 1.37432 6.62391 1.37432 6.62422 1.37432C6.81631 1.37415 7.00845 1.44731 7.15505 1.5938L10.1551 4.5918C10.4481 4.88459 10.4483 5.35946 10.1555 5.65246C9.86273 5.94546 9.38785 5.94562 9.09486 5.65283L7.37329 3.93247L7.37329 10.125C7.37329 10.5392 7.03751 10.875 6.62329 10.875C6.20908 10.875 5.87329 10.5392 5.87329 10.125L5.87329 3.93578L4.15516 5.65281C3.86218 5.94561 3.3873 5.94546 3.0945 5.65248C2.8017 5.35949 2.80185 4.88462 3.09484 4.59182L6.06462 1.62393Z"
fill="currentColor"
/>
</svg>

Before

Width:  |  Height:  |  Size: 926 B

After

Width:  |  Height:  |  Size: 741 B

View File

@@ -1,14 +1,14 @@
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M10.0002 13.8619C7.23361 13.8619 4.86803 12.1372 3.92328 9.70241C4.86804 7.26761 7.23361 5.54297 10.0002 5.54297C12.7667 5.54297 15.1323 7.26762 16.0771 9.70243C15.1323 12.1372 12.7667 13.8619 10.0002 13.8619ZM10.0002 4.04297C6.48191 4.04297 3.49489 6.30917 2.4155 9.4593C2.3615 9.61687 2.3615 9.78794 2.41549 9.94552C3.49488 13.0957 6.48191 15.3619 10.0002 15.3619C13.5184 15.3619 16.5055 13.0957 17.5849 9.94555C17.6389 9.78797 17.6389 9.6169 17.5849 9.45932C16.5055 6.30919 13.5184 4.04297 10.0002 4.04297ZM9.99151 7.84413C8.96527 7.84413 8.13333 8.67606 8.13333 9.70231C8.13333 10.7286 8.96527 11.5605 9.99151 11.5605H10.0064C11.0326 11.5605 11.8646 10.7286 11.8646 9.70231C11.8646 8.67606 11.0326 7.84413 10.0064 7.84413H9.99151Z"
/>
</svg>
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M10.0002 13.8619C7.23361 13.8619 4.86803 12.1372 3.92328 9.70241C4.86804 7.26761 7.23361 5.54297 10.0002 5.54297C12.7667 5.54297 15.1323 7.26762 16.0771 9.70243C15.1323 12.1372 12.7667 13.8619 10.0002 13.8619ZM10.0002 4.04297C6.48191 4.04297 3.49489 6.30917 2.4155 9.4593C2.3615 9.61687 2.3615 9.78794 2.41549 9.94552C3.49488 13.0957 6.48191 15.3619 10.0002 15.3619C13.5184 15.3619 16.5055 13.0957 17.5849 9.94555C17.6389 9.78797 17.6389 9.6169 17.5849 9.45932C16.5055 6.30919 13.5184 4.04297 10.0002 4.04297ZM9.99151 7.84413C8.96527 7.84413 8.13333 8.67606 8.13333 9.70231C8.13333 10.7286 8.96527 11.5605 9.99151 11.5605H10.0064C11.0326 11.5605 11.8646 10.7286 11.8646 9.70231C11.8646 8.67606 11.0326 7.84413 10.0064 7.84413H9.99151Z"
fill="currentColor"
/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 941 B

View File

@@ -55,6 +55,8 @@ import { ReactComponent as MoreDotIcon } from "./moredot.svg?react";
import { ReactComponent as AlertHexaIcon } from "./alert-hexa.svg?react";
import { ReactComponent as ErrorHexaIcon } from "./info-hexa.svg?react";
import { ReactComponent as CalendarIcon } from "./calendar.svg?react";
import { ReactComponent as SaveIcon } from "./save.svg?react";
import { ReactComponent as UserPlusIcon } from "./user-plus.svg?react";
export {
ErrorHexaIcon,
@@ -114,6 +116,8 @@ export {
AngleLeftIcon,
AngleRightIcon,
CalendarIcon,
SaveIcon,
UserPlusIcon,
};
// Aliases for commonly used icon names (lucide-react replacements)
@@ -153,7 +157,6 @@ export { FileIcon as FileArchiveIcon }; // File archive alias
export { PageIcon as ExternalLinkIcon }; // External link alias
export { PencilIcon as Wand2Icon }; // Wand alias
export { BoxCubeIcon as LayoutIcon }; // Layout alias
export { PencilIcon as SaveIcon }; // Save alias
export { BoxCubeIcon as CodeIcon }; // Code alias
export { GridIcon as SearchIcon }; // Search alias
export { BoxCubeIcon as PaletteIcon }; // Palette alias

View File

@@ -1,15 +1,14 @@
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M4.98481 2.44399C3.11333 1.57147 1.15325 3.46979 1.96543 5.36824L3.82086 9.70527C3.90146 9.89367 3.90146 10.1069 3.82086 10.2953L1.96543 14.6323C1.15326 16.5307 3.11332 18.4291 4.98481 17.5565L16.8184 12.0395C18.5508 11.2319 18.5508 8.76865 16.8184 7.961L4.98481 2.44399ZM3.34453 4.77824C3.0738 4.14543 3.72716 3.51266 4.35099 3.80349L16.1846 9.32051C16.762 9.58973 16.762 10.4108 16.1846 10.68L4.35098 16.197C3.72716 16.4879 3.0738 15.8551 3.34453 15.2223L5.19996 10.8853C5.21944 10.8397 5.23735 10.7937 5.2537 10.7473L9.11784 10.7473C9.53206 10.7473 9.86784 10.4115 9.86784 9.99726C9.86784 9.58304 9.53206 9.24726 9.11784 9.24726L5.25157 9.24726C5.2358 9.20287 5.2186 9.15885 5.19996 9.11528L3.34453 4.77824Z"
fill="currentColor"
/>
</svg>
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M4.98481 2.44399C3.11333 1.57147 1.15325 3.46979 1.96543 5.36824L3.82086 9.70527C3.90146 9.89367 3.90146 10.1069 3.82086 10.2953L1.96543 14.6323C1.15326 16.5307 3.11332 18.4291 4.98481 17.5565L16.8184 12.0395C18.5508 11.2319 18.5508 8.76865 16.8184 7.961L4.98481 2.44399ZM3.34453 4.77824C3.0738 4.14543 3.72716 3.51266 4.35099 3.80349L16.1846 9.32051C16.762 9.58973 16.762 10.4108 16.1846 10.68L4.35098 16.197C3.72716 16.4879 3.0738 15.8551 3.34453 15.2223L5.19996 10.8853C5.21944 10.8397 5.23735 10.7937 5.2537 10.7473L9.11784 10.7473C9.53206 10.7473 9.86784 10.4115 9.86784 9.99726C9.86784 9.58304 9.53206 9.24726 9.11784 9.24726L5.25157 9.24726C5.2358 9.20287 5.2186 9.15885 5.19996 9.11528L3.34453 4.77824Z"
fill="currentColor"
/>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 917 B

View File

@@ -1,15 +1,14 @@
<svg
class="fill-current"
width="12"
height="12"
viewBox="0 0 12 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M5.25012 3C5.25012 2.58579 5.58591 2.25 6.00012 2.25C6.41433 2.25 6.75012 2.58579 6.75012 3V5.25012L9.00034 5.25012C9.41455 5.25012 9.75034 5.58591 9.75034 6.00012C9.75034 6.41433 9.41455 6.75012 9.00034 6.75012H6.75012V9.00034C6.75012 9.41455 6.41433 9.75034 6.00012 9.75034C5.58591 9.75034 5.25012 9.41455 5.25012 9.00034L5.25012 6.75012H3C2.58579 6.75012 2.25 6.41433 2.25 6.00012C2.25 5.58591 2.58579 5.25012 3 5.25012H5.25012V3Z"
fill=""
/>
</svg>
<svg
width="12"
height="12"
viewBox="0 0 12 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M5.25012 3C5.25012 2.58579 5.58591 2.25 6.00012 2.25C6.41433 2.25 6.75012 2.58579 6.75012 3V5.25012L9.00034 5.25012C9.41455 5.25012 9.75034 5.58591 9.75034 6.00012C9.75034 6.41433 9.41455 6.75012 9.00034 6.75012H6.75012V9.00034C6.75012 9.41455 6.41433 9.75034 6.00012 9.75034C5.58591 9.75034 5.25012 9.41455 5.25012 9.00034L5.25012 6.75012H3C2.58579 6.75012 2.25 6.41433 2.25 6.00012C2.25 5.58591 2.58579 5.25012 3 5.25012H5.25012V3Z"
fill="currentColor"
/>
</svg>

Before

Width:  |  Height:  |  Size: 708 B

After

Width:  |  Height:  |  Size: 640 B

View File

@@ -0,0 +1,29 @@
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M19 21H5C4.46957 21 3.96086 20.7893 3.58579 20.4142C3.21071 20.0391 3 19.5304 3 19V5C3 4.46957 3.21071 3.96086 3.58579 3.58579C3.96086 3.21071 4.46957 3 5 3H16L21 8V19C21 19.5304 20.7893 20.0391 20.4142 20.4142C20.0391 20.7893 19.5304 21 19 21Z"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M17 21V13H7V21"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M7 3V8H15"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>

After

Width:  |  Height:  |  Size: 759 B

View File

@@ -1,15 +1,14 @@
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M3.04175 9.99984C3.04175 6.15686 6.1571 3.0415 10.0001 3.0415C13.8431 3.0415 16.9584 6.15686 16.9584 9.99984C16.9584 13.8428 13.8431 16.9582 10.0001 16.9582C6.1571 16.9582 3.04175 13.8428 3.04175 9.99984ZM10.0001 1.5415C5.32867 1.5415 1.54175 5.32843 1.54175 9.99984C1.54175 14.6712 5.32867 18.4582 10.0001 18.4582C14.6715 18.4582 18.4584 14.6712 18.4584 9.99984C18.4584 5.32843 14.6715 1.5415 10.0001 1.5415ZM9.99998 10.7498C9.58577 10.7498 9.24998 10.4141 9.24998 9.99984V5.4165C9.24998 5.00229 9.58577 4.6665 9.99998 4.6665C10.4142 4.6665 10.75 5.00229 10.75 5.4165V9.24984H13.3334C13.7476 9.24984 14.0834 9.58562 14.0834 9.99984C14.0834 10.4141 13.7476 10.7498 13.3334 10.7498H10.0001H9.99998Z"
fill="currentColor"
/>
</svg>
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M3.04175 9.99984C3.04175 6.15686 6.1571 3.0415 10.0001 3.0415C13.8431 3.0415 16.9584 6.15686 16.9584 9.99984C16.9584 13.8428 13.8431 16.9582 10.0001 16.9582C6.1571 16.9582 3.04175 13.8428 3.04175 9.99984ZM10.0001 1.5415C5.32867 1.5415 1.54175 5.32843 1.54175 9.99984C1.54175 14.6712 5.32867 18.4582 10.0001 18.4582C14.6715 18.4582 18.4584 14.6712 18.4584 9.99984C18.4584 5.32843 14.6715 1.5415 10.0001 1.5415ZM9.99998 10.7498C9.58577 10.7498 9.24998 10.4141 9.24998 9.99984V5.4165C9.24998 5.00229 9.58577 4.6665 9.99998 4.6665C10.4142 4.6665 10.75 5.00229 10.75 5.4165V9.24984H13.3334C13.7476 9.24984 14.0834 9.58562 14.0834 9.99984C14.0834 10.4141 13.7476 10.7498 13.3334 10.7498H10.0001H9.99998Z"
fill="currentColor"
/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 904 B

View File

@@ -0,0 +1,36 @@
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M16 21V19C16 17.9391 15.5786 16.9217 14.8284 16.1716C14.0783 15.4214 13.0609 15 12 15H5C3.93913 15 2.92172 15.4214 2.17157 16.1716C1.42143 16.9217 1 17.9391 1 19V21"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M8.5 11C10.7091 11 12.5 9.20914 12.5 7C12.5 4.79086 10.7091 3 8.5 3C6.29086 3 4.5 4.79086 4.5 7C4.5 9.20914 6.29086 11 8.5 11Z"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M20 8V14"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M23 11H17"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>

After

Width:  |  Height:  |  Size: 925 B

View File

@@ -107,14 +107,17 @@ const AppHeader: React.FC = () => {
{/* Mobile Menu Toggle */}
<IconButton
variant="ghost"
tone="neutral"
size="md"
onClick={toggleApplicationMenu}
className="w-10 h-10 z-99999 lg:hidden"
className="z-99999 lg:hidden"
aria-label="Toggle menu"
>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fillRule="evenodd" clipRule="evenodd" d="M5.99902 10.4951C6.82745 10.4951 7.49902 11.1667 7.49902 11.9951V12.0051C7.49902 12.8335 6.82745 13.5051 5.99902 13.5051C5.1706 13.5051 4.49902 12.8335 4.49902 12.0051V11.9951C4.49902 11.1667 5.1706 10.4951 5.99902 10.4951ZM17.999 10.4951C18.8275 10.4951 19.499 11.1667 19.499 11.9951V12.0051C19.499 12.8335 18.8275 13.5051 17.999 13.5051C17.1706 13.5051 16.499 12.8335 16.499 12.0051V11.9951C16.499 11.1667 17.1706 10.4951 17.999 10.4951ZM13.499 11.9951C13.499 11.1667 12.8275 10.4951 11.999 10.4951C11.1706 10.4951 10.499 11.1667 10.499 11.9951V12.0051C10.499 12.8335 11.1706 13.5051 11.999 13.5051C12.8275 13.5051 13.499 12.8335 13.499 12.0051V11.9951Z" fill="currentColor" />
</svg>
</IconButton>
icon={
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fillRule="evenodd" clipRule="evenodd" d="M5.99902 10.4951C6.82745 10.4951 7.49902 11.1667 7.49902 11.9951V12.0051C7.49902 12.8335 6.82745 13.5051 5.99902 13.5051C5.1706 13.5051 4.49902 12.8335 4.49902 12.0051V11.9951C4.49902 11.1667 5.1706 10.4951 5.99902 10.4951ZM17.999 10.4951C18.8275 10.4951 19.499 11.1667 19.499 11.9951V12.0051C19.499 12.8335 18.8275 13.5051 17.999 13.5051C17.1706 13.5051 16.499 12.8335 16.499 12.0051V11.9951C16.499 11.1667 17.1706 10.4951 17.999 10.4951ZM13.499 11.9951C13.499 11.1667 12.8275 10.4951 11.999 10.4951C11.1706 10.4951 10.499 11.1667 10.499 11.9951V12.0051C10.499 12.8335 11.1706 13.5051 11.999 13.5051C12.8275 13.5051 13.499 12.8335 13.499 12.0051V11.9951Z" fill="currentColor" />
</svg>
}
/>
{/* Page Title with Badge - Desktop */}
{pageInfo && (
@@ -122,20 +125,22 @@ const AppHeader: React.FC = () => {
{/* Sidebar Toggle Button - Always visible on desktop */}
<IconButton
variant="outline"
size="xs"
tone="neutral"
size="sm"
shape="circle"
onClick={toggleSidebar}
className="w-6 h-6 rounded-full"
aria-label={isExpanded ? "Collapse Sidebar" : "Expand Sidebar"}
>
<svg
className={`w-3.5 h-3.5 text-gray-500 dark:text-gray-400 transition-transform duration-300 ${isExpanded ? 'rotate-180' : ''}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
</IconButton>
icon={
<svg
className={`w-3.5 h-3.5 transition-transform duration-300 ${isExpanded ? 'rotate-180' : ''}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
}
/>
{pageInfo.badge && (
<div className={`flex items-center justify-center w-8 h-8 rounded-lg ${badgeColors[pageInfo.badge.color]?.bg || 'bg-gray-600'} flex-shrink-0`}>
@@ -188,15 +193,17 @@ const AppHeader: React.FC = () => {
{/* Search Icon */}
<IconButton
variant="ghost"
tone="neutral"
size="md"
onClick={() => setIsSearchOpen(true)}
className="w-10 h-10"
title="Search (⌘K)"
aria-label="Search"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</IconButton>
icon={
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
}
/>
{/* Dark Mode Toggler */}
<ThemeToggleButton />

View File

@@ -176,18 +176,9 @@ export default function LinkerContentList() {
disabled={isProcessing || processing === -1}
variant="primary"
size="sm"
startIcon={isProcessing ? <div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" /> : <PlugInIcon className="w-4 h-4" />}
>
{isProcessing ? (
<>
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" />
Processing...
</>
) : (
<>
<PlugInIcon className="w-4 h-4" />
Add Links
</>
)}
{isProcessing ? 'Processing...' : 'Add Links'}
</Button>
</td>
</tr>

View File

@@ -171,18 +171,9 @@ export default function OptimizerContentSelector() {
variant="primary"
onClick={handleBatchOptimize}
disabled={selectedIds.length === 0 || processing.length > 0}
startIcon={processing.length > 0 ? <div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" /> : <BoltIcon className="w-4 h-4" />}
>
{processing.length > 0 ? (
<>
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" />
Optimizing...
</>
) : (
<>
<BoltIcon className="w-4 h-4" />
Optimize Selected ({selectedIds.length})
</>
)}
{processing.length > 0 ? 'Optimizing...' : `Optimize Selected (${selectedIds.length})`}
</Button>
</div>
</div>
@@ -270,18 +261,9 @@ export default function OptimizerContentSelector() {
size="sm"
onClick={() => handleOptimize(item.id)}
disabled={isProcessing || processing.length > 0}
startIcon={isProcessing ? <div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" /> : <BoltIcon className="w-4 h-4" />}
>
{isProcessing ? (
<>
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" />
Optimizing...
</>
) : (
<>
<BoltIcon className="w-4 h-4" />
Optimize
</>
)}
{isProcessing ? 'Optimizing...' : 'Optimize'}
</Button>
</td>
</tr>

View File

@@ -157,9 +157,8 @@ export default function ClusterDetail() {
variant="ghost"
size="sm"
onClick={() => navigate('/planner/clusters')}
className="flex items-center gap-2"
startIcon={<ChevronLeftIcon className="w-4 h-4" />}
>
<ChevronLeftIcon className="w-4 h-4" />
Back to Clusters
</Button>
</div>
@@ -241,8 +240,8 @@ export default function ClusterDetail() {
? 'border-brand-500 text-brand-600 dark:text-brand-400'
: 'border-transparent text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300'
}`}
startIcon={<FileIcon className="w-4 h-4" />}
>
<FileIcon className="w-4 h-4 inline mr-2" />
Articles
</Button>
<Button
@@ -253,8 +252,8 @@ export default function ClusterDetail() {
? 'border-brand-500 text-brand-600 dark:text-brand-400'
: 'border-transparent text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300'
}`}
startIcon={<PageIcon className="w-4 h-4" />}
>
<PageIcon className="w-4 h-4 inline mr-2" />
Pages
</Button>
<Button
@@ -265,8 +264,8 @@ export default function ClusterDetail() {
? 'border-brand-500 text-brand-600 dark:text-brand-400'
: 'border-transparent text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300'
}`}
startIcon={<GridIcon className="w-4 h-4" />}
>
<GridIcon className="w-4 h-4 inline mr-2" />
Products
</Button>
<Button
@@ -277,8 +276,8 @@ export default function ClusterDetail() {
? 'border-brand-500 text-brand-600 dark:text-brand-400'
: 'border-transparent text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300'
}`}
startIcon={<TagIcon className="w-4 h-4" />}
>
<TagIcon className="w-4 h-4 inline mr-2" />
Taxonomy
</Button>
</div>

View File

@@ -28,7 +28,9 @@ import {
PageIcon,
TableIcon,
ChevronDownIcon,
ChevronUpIcon
ChevronUpIcon,
FilterIcon,
BoxCubeIcon as SettingsIcon
} from '../../icons';
import {
fetchSites,
@@ -67,6 +69,7 @@ export default function SiteList() {
const [loading, setLoading] = useState(true);
const [viewType, setViewType] = useState<ViewType>('grid');
const [showWelcomeGuide, setShowWelcomeGuide] = useState(false);
const [showFilters, setShowFilters] = useState(false);
// Site Management Modals
const [selectedSite, setSelectedSite] = useState<Site | null>(null);
@@ -410,19 +413,16 @@ export default function SiteList() {
{site.domain}
</p>
)}
<div className="flex items-center gap-2 mb-2 flex-wrap">
<SiteTypeBadge hostingType={site.hosting_type} />
{/* Centered badges with uniform size */}
<div className="flex items-center justify-center gap-2 mb-2 flex-wrap">
<SiteTypeBadge hostingType={site.hosting_type} size="sm" />
{site.industry_name && (
<Badge variant="light" color="info" className="text-xs">
<Badge variant="soft" color="warning" size="sm">
{site.industry_name}
</Badge>
)}
{site.integration_count && site.integration_count > 0 && (
<Badge variant="soft" color="success" className="text-[10px] px-1.5 py-0.5">
{site.integration_count} integration{site.integration_count > 1 ? 's' : ''}
</Badge>
)}
</div>
{/* Status badge and toggle in top right */}
<div className="absolute top-4 right-4 flex items-center gap-3">
<Switch
checked={site.is_active}
@@ -430,19 +430,21 @@ export default function SiteList() {
disabled={togglingSiteId === site.id}
/>
<Badge
variant={site.is_active ? "soft" : "light"}
color={site.is_active ? "success" : "gray"}
size="sm"
variant="solid"
color={site.is_active ? "success" : "error"}
size="xs"
>
{site.is_active ? 'Active' : 'Inactive'}
</Badge>
</div>
</div>
{/* Centered button row */}
<div className="border-t border-gray-200 p-3 dark:border-gray-800">
<div className="grid grid-cols-2 gap-2">
<div className="flex justify-center gap-2">
<Button
onClick={() => navigate(`/sites/${site.id}`)}
variant="primary"
tone="brand"
size="sm"
startIcon={<EyeIcon className="w-4 h-4" />}
>
@@ -451,6 +453,7 @@ export default function SiteList() {
<Button
onClick={() => navigate(`/sites/${site.id}/content`)}
variant="secondary"
tone="neutral"
size="sm"
startIcon={<FileIcon className="w-4 h-4" />}
>
@@ -459,9 +462,9 @@ export default function SiteList() {
<Button
onClick={() => navigate(`/sites/${site.id}/settings`)}
variant="outline"
tone="neutral"
size="sm"
startIcon={<PlugInIcon className="w-4 h-4" />}
className="col-span-2"
startIcon={<SettingsIcon className="w-4 h-4" />}
>
Settings
</Button>
@@ -497,33 +500,46 @@ export default function SiteList() {
{/* Custom Header Actions - Add Site button and view toggle */}
<div className="flex items-center justify-between mb-6">
<div className="flex-1">
<div className="flex items-center gap-3">
<Button
onClick={() => setShowWelcomeGuide(!showWelcomeGuide)}
variant="success"
variant="primary"
tone="brand"
size="md"
startIcon={<PlusIcon className="w-5 h-5" />}
>
Add New Website
</Button>
{viewType === 'grid' && (
<Button
variant="secondary"
size="md"
onClick={() => setShowFilters(!showFilters)}
startIcon={<FilterIcon className="w-4 h-4" />}
>
{showFilters ? 'Hide Filters' : 'Show Filters'}
</Button>
)}
</div>
<div className="flex items-center gap-3">
<div className="flex items-center gap-2">
<div className="flex items-center gap-1 bg-gray-100 dark:bg-gray-800 rounded-lg p-1">
<Button
onClick={() => setViewType('table')}
variant={viewType === 'table' ? 'secondary' : 'ghost'}
variant={viewType === 'table' ? 'primary' : 'ghost'}
tone="brand"
size="sm"
startIcon={<TableIcon className="w-4 h-4" />}
>
<span className="hidden sm:inline">Table</span>
Table
</Button>
<Button
onClick={() => setViewType('grid')}
variant={viewType === 'grid' ? 'secondary' : 'ghost'}
variant={viewType === 'grid' ? 'primary' : 'ghost'}
tone="brand"
size="sm"
startIcon={<GridIcon className="w-4 h-4" />}
>
<span className="hidden sm:inline">Grid</span>
Grid
</Button>
</div>
</div>
@@ -600,59 +616,61 @@ export default function SiteList() {
/>
) : (
<>
{/* Standard Filters Bar for Grid View - Matches Table View */}
<div className="flex justify-center mb-4">
<div
className="w-[75%] igny8-filter-bar p-3 rounded-lg bg-transparent shadow-theme-md"
>
<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">
<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)}
/>
{/* Standard Filters Bar for Grid View - Collapsible like table view */}
{showFilters && (
<div className="flex justify-center mb-4">
<div
className="w-full igny8-filter-bar p-3 rounded-lg bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 shadow-sm"
>
<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">
<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
variant="secondary"
size="sm"
onClick={clearFilters}
className="flex-shrink-0"
>
Clear Filters
</Button>
)}
</div>
{hasActiveFilters && (
<Button
variant="secondary"
size="sm"
onClick={clearFilters}
className="flex-shrink-0"
>
Clear Filters
</Button>
)}
</div>
</div>
</div>
)}
{/* Grid View */}
{filteredSites.length === 0 ? (
@@ -665,7 +683,7 @@ export default function SiteList() {
Clear Filters
</Button>
) : (
<Button onClick={() => setShowWelcomeGuide(true)} variant="success" startIcon={<PlusIcon className="w-5 h-5" />}>
<Button onClick={() => setShowWelcomeGuide(true)} variant="primary" tone="success" startIcon={<PlusIcon className="w-5 h-5" />}>
Add Your First Site
</Button>
)}

View File

@@ -11,6 +11,7 @@ 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 { useToast } from '../../components/ui/toast/ToastContainer';
import { fetchAPI } from '../../services/api';
import {
@@ -94,13 +95,10 @@ const DraggablePageItem: React.FC<{
</div>
</div>
<div className="flex gap-2">
<Button variant="outline" size="sm" onClick={() => onEdit(page.id)}>
<PencilIcon className="w-4 h-4 mr-1" />
<Button variant="outline" size="sm" onClick={() => onEdit(page.id)} startIcon={<PencilIcon className="w-4 h-4" />}>
Edit
</Button>
<Button variant="ghost" size="sm" onClick={() => onDelete(page.id)}>
<TrashBinIcon className="w-4 h-4" />
</Button>
<IconButton variant="ghost" size="sm" onClick={() => onDelete(page.id)} icon={<TrashBinIcon className="w-4 h-4" />} />
</div>
</div>
);
@@ -330,8 +328,8 @@ export default function PageManager() {
size="sm"
onClick={handleBulkDelete}
className="text-error-600 hover:text-error-700"
startIcon={<TrashBinIcon className="w-4 h-4" />}
>
<TrashBinIcon className="w-4 h-4 mr-1" />
Delete Selected
</Button>
<Button variant="ghost" size="sm" onClick={() => setSelectedPages(new Set())}>

View File

@@ -271,16 +271,16 @@ export default function PostEditor() {
variant={activeTab === 'content' ? 'primary' : 'ghost'}
size="sm"
onClick={() => setActiveTab('content')}
startIcon={<FileTextIcon className="w-4 h-4" />}
>
<FileTextIcon className="w-4 h-4" />
Content
</Button>
<Button
variant={activeTab === 'taxonomy' ? 'primary' : 'ghost'}
size="sm"
onClick={() => setActiveTab('taxonomy')}
startIcon={<TagIcon className="w-4 h-4" />}
>
<TagIcon className="w-4 h-4" />
Taxonomy & Cluster
</Button>
{content.id && (
@@ -291,8 +291,8 @@ export default function PostEditor() {
setActiveTab('validation');
loadValidation();
}}
startIcon={<CheckCircleIcon className="w-4 h-4" />}
>
<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">

View File

@@ -283,15 +283,15 @@ export default function PublishingQueue() {
<ButtonGroupItem
isActive={viewMode === 'list'}
onClick={() => setViewMode('list')}
startIcon={<ListIcon className="w-4 h-4" />}
>
<ListIcon className="w-4 h-4 mr-1.5" />
List
</ButtonGroupItem>
<ButtonGroupItem
isActive={viewMode === 'calendar'}
onClick={() => setViewMode('calendar')}
startIcon={<CalendarIcon className="w-4 h-4" />}
>
<CalendarIcon className="w-4 h-4 mr-1.5" />
Calendar
</ButtonGroupItem>
</ButtonGroup>

View File

@@ -27,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, CloseIcon, PlusIcon } from '../../icons';
import { GridIcon, PlugInIcon, PaperPlaneIcon, DocsIcon, BoltIcon, FileIcon, ChevronDownIcon, CloseIcon, PlusIcon, RefreshCwIcon } from '../../icons';
import Badge from '../../components/ui/badge/Badge';
import { Dropdown } from '../../components/ui/dropdown/Dropdown';
import { DropdownItem } from '../../components/ui/dropdown/DropdownItem';
@@ -621,8 +621,8 @@ export default function SiteSettings() {
? 'border-brand-500 text-brand-600 dark:text-brand-400'
: 'border-transparent text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300'
}`}
startIcon={<GridIcon className="w-4 h-4" />}
>
<GridIcon className="w-4 h-4 inline mr-2" />
General
</Button>
<Button
@@ -636,8 +636,8 @@ export default function SiteSettings() {
? 'border-brand-500 text-brand-600 dark:text-brand-400'
: 'border-transparent text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300'
}`}
startIcon={<PlugInIcon className="w-4 h-4" />}
>
<PlugInIcon className="w-4 h-4 inline mr-2" />
Integrations
</Button>
<Button
@@ -651,8 +651,8 @@ export default function SiteSettings() {
? 'border-brand-500 text-brand-600 dark:text-brand-400'
: 'border-transparent text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300'
}`}
startIcon={<PaperPlaneIcon className="w-4 h-4" />}
>
<PaperPlaneIcon className="w-4 h-4 inline mr-2" />
Publishing
</Button>
{(wordPressIntegration || site?.wp_url || site?.wp_api_key || site?.hosting_type === 'wordpress') && (
@@ -667,8 +667,8 @@ export default function SiteSettings() {
? 'border-brand-500 text-brand-600 dark:text-brand-400'
: 'border-transparent text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300'
}`}
startIcon={<FileIcon className="w-4 h-4" />}
>
<FileIcon className="w-4 h-4 inline mr-2" />
Content Types
</Button>
)}
@@ -951,21 +951,9 @@ export default function SiteSettings() {
variant="outline"
disabled={syncLoading || !(wordPressIntegration || site?.wp_url || site?.wp_api_key || site?.hosting_type === 'wordpress')}
onClick={handleManualSync}
className="flex items-center gap-2"
startIcon={syncLoading ? <RefreshCwIcon className="w-4 h-4 animate-spin" /> : <RefreshCwIcon className="w-4 h-4" />}
>
{syncLoading ? (
<>
<div className="inline-block animate-spin rounded-full h-4 w-4 border-b-2 border-current"></div>
<span>Syncing...</span>
</>
) : (
<>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
<span>Sync Structure</span>
</>
)}
{syncLoading ? 'Syncing...' : 'Sync Structure'}
</Button>
</div>

View File

@@ -252,8 +252,8 @@ export default function SyncDashboard() {
size="sm"
onClick={() => handleSync('to_external')}
disabled={syncing || !integration.sync_enabled}
startIcon={<ArrowRightIcon className="w-4 h-4" />}
>
<ArrowRightIcon className="w-4 h-4 mr-1" />
Sync to WordPress
</Button>
<Button
@@ -261,8 +261,8 @@ export default function SyncDashboard() {
size="sm"
onClick={() => handleSync('from_external')}
disabled={syncing || !integration.sync_enabled}
startIcon={<ArrowRightIcon className="w-4 h-4 rotate-180" />}
>
<ArrowRightIcon className="w-4 h-4 mr-1 rotate-180" />
Sync from WordPress
</Button>
</div>

View File

@@ -107,8 +107,7 @@ export default function AuthorProfiles() {
breadcrumb="Thinker / Author Profiles"
/>
<div className="mb-6 flex justify-between items-center">
<Button onClick={handleCreate} variant="primary">
<PlusIcon className="w-4 h-4 mr-2" />
<Button onClick={handleCreate} variant="primary" startIcon={<PlusIcon className="w-4 h-4" />}>
Create Profile
</Button>
</div>

View File

@@ -7,7 +7,7 @@
import { useState, useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import {
SaveIcon, Loader2Icon, SettingsIcon, UserIcon, UsersIcon, UserIcon as UserPlusIcon, LockIcon, LockIcon as ShieldIcon, XIcon
SaveIcon, Loader2Icon, SettingsIcon, UserIcon, UsersIcon, UserPlusIcon, LockIcon, LockIcon as ShieldIcon, XIcon
} from '../../icons';
import { Card } from '../../components/ui/card';
import Button from '../../components/ui/button/Button';
@@ -605,7 +605,7 @@ export default function AccountSettingsPage() {
<Button
variant="primary"
tone="brand"
startIcon={<UserPlus className="w-4 h-4" />}
startIcon={<UserPlusIcon className="w-4 h-4" />}
onClick={() => setShowInviteModal(true)}
>
Invite Someone

View File

@@ -7,7 +7,7 @@
import { useState, useEffect, useCallback } from 'react';
import { useLocation } from 'react-router-dom';
import {
SaveIcon, Loader2Icon, ImageIcon, FileTextIcon, PaperPlaneIcon as SendIcon, SettingsIcon
SaveIcon, Loader2Icon, ImageIcon, FileTextIcon, PaperPlaneIcon, SettingsIcon
} from '../../icons';
import { Card } from '../../components/ui/card';
import Button from '../../components/ui/button/Button';

View File

@@ -209,9 +209,8 @@ export default function NotificationsPage() {
variant="outline"
size="sm"
onClick={() => setShowFilters(!showFilters)}
className="flex items-center gap-2"
startIcon={<FilterIcon className="w-4 h-4" />}
>
<FilterIcon className="w-4 h-4" />
Filters
</Button>
@@ -220,9 +219,8 @@ export default function NotificationsPage() {
variant="outline"
size="sm"
onClick={handleMarkAllRead}
className="flex items-center gap-2"
startIcon={<CheckCheckIcon className="w-4 h-4" />}
>
<CheckCheckIcon className="w-4 h-4" />
Mark All Read
</Button>
)}

View File

@@ -9,7 +9,7 @@
import { useState, useEffect, useRef } from 'react';
import { Link, useLocation } from 'react-router-dom';
import {
CreditCardIcon, BoxIcon as PackageIcon, TrendingUpIcon, FileTextIcon, WalletIcon, ArrowUpIcon as ArrowUpCircleIcon,
CreditCardIcon, BoxIcon as PackageIcon, TrendingUpIcon, FileTextIcon, WalletIcon, ArrowUpIcon,
Loader2Icon, AlertCircleIcon, CheckCircleIcon, DownloadIcon, ZapIcon, GlobeIcon, UsersIcon, XIcon
} from '../../icons';
import { Card } from '../../components/ui/card';

View File

@@ -763,8 +763,8 @@ export default function ContentViewTemplate({ content, loading, onBack }: Conten
<Button
variant="primary"
onClick={onBack}
startIcon={<ArrowLeftIcon className="w-4 h-4" />}
>
<ArrowLeftIcon className="w-4 h-4" />
Back to Content List
</Button>
)}
@@ -829,8 +829,8 @@ export default function ContentViewTemplate({ content, loading, onBack }: Conten
variant="ghost"
onClick={onBack}
className="mb-6"
startIcon={<ArrowLeftIcon className="w-4 h-4" />}
>
<ArrowLeftIcon className="w-4 h-4" />
Back to Content List
</Button>
)}
@@ -1030,16 +1030,16 @@ export default function ContentViewTemplate({ content, loading, onBack }: Conten
<Button
variant="primary"
onClick={() => navigate(`/sites/${content.site_id}/posts/${content.id}/edit`)}
startIcon={<PencilIcon className="w-4 h-4" />}
>
<PencilIcon className="w-4 h-4" />
Edit Content
</Button>
<Button
variant="primary"
tone="brand"
onClick={() => navigate(`/writer/images?contentId=${content.id}`)}
startIcon={<ImageIcon className="w-4 h-4" />}
>
<ImageIcon className="w-4 h-4" />
Generate Images
</Button>
</>
@@ -1051,16 +1051,16 @@ export default function ContentViewTemplate({ content, loading, onBack }: Conten
<Button
variant="primary"
onClick={() => navigate(`/sites/${content.site_id}/posts/${content.id}/edit`)}
startIcon={<PencilIcon className="w-4 h-4" />}
>
<PencilIcon className="w-4 h-4" />
Edit Content
</Button>
<Button
variant="primary"
tone="brand"
onClick={() => navigate(`/writer/published?contentId=${content.id}&action=publish`)}
startIcon={<BoltIcon className="w-4 h-4" />}
>
<BoltIcon className="w-4 h-4" />
Publish
</Button>
</>