handleFiles(files)}
+ accept=".csv,.json"
+ multiple={false}
+/>
+```
+
+---
+
+## 3. DISPLAY COMPONENTS
+
+### Badge
+
+```tsx
+import Badge from '../../components/ui/badge/Badge';
+
+}
+ endIcon={}
+>
+ Label
+
+```
+
+**Status Badge Patterns:**
+```tsx
+Active
+Pending
+Failed
+Draft
+Archived
+```
+
+### Card
+
+```tsx
+import { Card, CardTitle, CardContent, CardDescription, CardAction, CardIcon } from '../../components/ui/card/Card';
+
+
+
+ Title
+ Description text
+ Main content
+ Action
+
+```
+
+### Alert
+
+```tsx
+import Alert from '../../components/ui/alert/Alert';
+
+
+```
+
+### Modal
+
+```tsx
+import { Modal } from '../../components/ui/modal';
+
+ setIsOpen(false)}
+ showCloseButton={true}
+ isFullscreen={false}
+>
+
+ Modal content
+
+
+```
+
+### Spinner
+
+```tsx
+import { Spinner } from '../../components/ui/spinner/Spinner';
+
+
+```
+
+### Tooltip
+
+```tsx
+import { Tooltip } from '../../components/ui/tooltip/Tooltip';
+
+
+ Hover target
+
+```
+
+### Toast (Notifications)
+
+```tsx
+import { useToast } from '../../components/ui/toast/ToastContainer';
+
+const toast = useToast();
+
+// Show notifications
+toast.success('Success', 'Operation completed');
+toast.error('Error', 'Something went wrong');
+toast.warning('Warning', 'Please review');
+toast.info('Info', 'Here is some information');
+```
+
+---
+
+## 4. ICONS
+
+### Importing Icons
+
+```tsx
+// Always import from central icons folder
+import { PlusIcon, CloseIcon, CheckCircleIcon } from '../../icons';
+
+// Use consistent sizing
+ // Small (in buttons, badges)
+ // Medium (standalone)
+ // Large (headers, features)
+```
+
+### Available Icons
+
+**Core Icons:**
+- `PlusIcon`, `CloseIcon`, `CheckCircleIcon`, `AlertIcon`, `InfoIcon`, `ErrorIcon`
+- `BoltIcon`, `ArrowUpIcon`, `ArrowDownIcon`, `ArrowRightIcon`, `ArrowLeftIcon`
+- `PencilIcon`, `TrashBinIcon`, `DownloadIcon`, `CopyIcon`
+- `EyeIcon`, `EyeCloseIcon`, `LockIcon`, `UserIcon`
+- `FolderIcon`, `FileIcon`, `GridIcon`, `ListIcon`
+- `ChevronDownIcon`, `ChevronUpIcon`, `ChevronLeftIcon`, `ChevronRightIcon`
+- `AngleDownIcon`, `AngleUpIcon`, `AngleLeftIcon`, `AngleRightIcon`
+
+**Module Icons:**
+- `TaskIcon`, `PageIcon`, `TableIcon`, `CalendarIcon`
+- `PlugInIcon`, `DocsIcon`, `MailIcon`, `ChatIcon`
+- `PieChartIcon`, `BoxCubeIcon`, `GroupIcon`
+- `ShootingStarIcon`, `DollarLineIcon`
+
+**Aliases (for compatibility):**
+- `TrashIcon` → `TrashBinIcon`
+- `XIcon` / `XMarkIcon` → `CloseIcon`
+- `SearchIcon` → `GridIcon`
+- `SettingsIcon` → `BoxCubeIcon`
+- `FilterIcon` → `ListIcon`
+
+### Adding New Icons
+
+1. Add SVG file to `src/icons/`
+2. Export in `src/icons/index.ts`:
+```ts
+import { ReactComponent as NewIcon } from "./new-icon.svg?react";
+export { NewIcon };
+```
+
+---
+
+## 5. FOLDER STRUCTURE
+
+```
+src/
+├── components/
+│ ├── ui/ # UI Components (display/interaction)
+│ │ ├── accordion/
+│ │ ├── alert/
+│ │ │ ├── Alert.tsx
+│ │ │ └── AlertModal.tsx
+│ │ ├── avatar/
+│ │ ├── badge/
+│ │ │ └── Badge.tsx
+│ │ ├── breadcrumb/
+│ │ ├── button/
+│ │ │ ├── Button.tsx # ← Standard button
+│ │ │ ├── IconButton.tsx # ← Icon-only button
+│ │ │ └── ButtonWithTooltip.tsx
+│ │ ├── button-group/
+│ │ ├── card/
+│ │ ├── dataview/
+│ │ ├── dropdown/
+│ │ ├── list/
+│ │ ├── modal/
+│ │ ├── pagination/
+│ │ ├── progress/
+│ │ ├── ribbon/
+│ │ ├── spinner/
+│ │ ├── table/
+│ │ ├── tabs/
+│ │ ├── toast/
+│ │ ├── tooltip/
+│ │ └── videos/
+│ │
+│ └── form/ # Form Components (inputs)
+│ ├── input/
+│ │ ├── InputField.tsx # ← Text/number/email inputs
+│ │ ├── Checkbox.tsx # ← Checkbox
+│ │ ├── Radio.tsx # ← Radio button
+│ │ ├── RadioSm.tsx # ← Small radio button
+│ │ ├── FileInput.tsx # ← File upload
+│ │ └── TextArea.tsx # ← Multi-line text
+│ ├── switch/
+│ │ └── Switch.tsx # ← Toggle switch
+│ ├── Select.tsx # ← Dropdown select
+│ ├── SelectDropdown.tsx # ← Searchable dropdown
+│ ├── MultiSelect.tsx # ← Multi-select dropdown
+│ ├── Label.tsx # ← Form labels
+│ ├── Form.tsx # ← Form wrapper
+│ └── date-picker.tsx # ← Date picker
+│
+├── icons/ # All SVG icons
+│ ├── index.ts # ← Export all icons from here
+│ ├── plus.svg
+│ ├── close.svg
+│ └── ... (50+ icons)
+│
+└── styles/
+ └── design-system.css # ← Global design tokens
+```
+
+---
+
+## 6. ESLINT ENFORCEMENT
+
+### Rules File Location
+`eslint-plugin-igny8-design-system.cjs` (project root)
+
+### Active Rules
+
+| Rule | Severity | Description |
+|------|----------|-------------|
+| `no-raw-button` | warn | Use `Button` or `IconButton` instead of `
)}
{/* Delete Action */}
{onDelete && (
<>
-
+ Delete
+
>
)}
diff --git a/frontend/src/components/auth/SignInForm.tsx b/frontend/src/components/auth/SignInForm.tsx
index 67ccce1c..985340c9 100644
--- a/frontend/src/components/auth/SignInForm.tsx
+++ b/frontend/src/components/auth/SignInForm.tsx
@@ -114,7 +114,7 @@ export default function SignInForm() {
-
@@ -246,21 +246,27 @@ export default function SignInForm() {
You're trying to login as:
{sessionConflict.requestedUser.email}
-
{loading ? 'Logging out...' : 'Logout Previous & Continue'}
-
-
+ setSessionConflict(null)}
- className="flex-1 px-4 py-2 text-sm font-medium text-warning-700 bg-warning-100 rounded-lg hover:bg-warning-200 dark:bg-warning-900/40 dark:text-warning-300 dark:hover:bg-warning-900/60 transition-colors"
+ className="flex-1"
>
Cancel
-
+
)}
diff --git a/frontend/src/components/auth/SignUpForm.tsx b/frontend/src/components/auth/SignUpForm.tsx
index 1af95425..25a21839 100644
--- a/frontend/src/components/auth/SignUpForm.tsx
+++ b/frontend/src/components/auth/SignUpForm.tsx
@@ -4,6 +4,7 @@ import { ChevronLeftIcon, EyeCloseIcon, EyeIcon } from "../../icons";
import Label from "../form/Label";
import Input from "../form/input/InputField";
import Checkbox from "../form/input/Checkbox";
+import Button from "../ui/button/Button";
import { useAuthStore } from "../../store/authStore";
export default function SignUpForm({ planDetails: planDetailsProp, planLoading: planLoadingProp }: { planDetails?: any; planLoading?: boolean }) {
@@ -134,7 +135,7 @@ export default function SignUpForm({ planDetails: planDetailsProp, planLoading:
-
+
Sign up with Google
-
-
+
+
Sign up with X
-
+
@@ -294,13 +295,16 @@ export default function SignUpForm({ planDetails: planDetailsProp, planLoading:
{/* Submit Button */}
-
{loading ? "Creating your account..." : "Start Free Trial"}
-
+
diff --git a/frontend/src/components/auth/SignUpFormSimplified.tsx b/frontend/src/components/auth/SignUpFormSimplified.tsx
index 33d5e60c..6eb4ea54 100644
--- a/frontend/src/components/auth/SignUpFormSimplified.tsx
+++ b/frontend/src/components/auth/SignUpFormSimplified.tsx
@@ -11,6 +11,7 @@ import Label from '../form/Label';
import Input from '../form/input/InputField';
import Checkbox from '../form/input/Checkbox';
import Button from '../ui/button/Button';
+import SelectDropdown from '../form/SelectDropdown';
import { useAuthStore } from '../../store/authStore';
interface PaymentMethodConfig {
@@ -321,21 +322,21 @@ export default function SignUpFormSimplified({ planDetails: planDetailsProp, pla
-
+ onChange={(val) => setFormData({ ...formData, billingCountry: val })}
+ />
Payment methods will be filtered by your country
diff --git a/frontend/src/components/auth/SignUpFormUnified.tsx b/frontend/src/components/auth/SignUpFormUnified.tsx
index 532c23f7..85e8feed 100644
--- a/frontend/src/components/auth/SignUpFormUnified.tsx
+++ b/frontend/src/components/auth/SignUpFormUnified.tsx
@@ -284,24 +284,28 @@ export default function SignUpFormUnified({
billingPeriod === 'monthly' ? 'translate-x-0' : 'translate-x-28'
}`}
>
-
setBillingPeriod('monthly')}
className={`relative flex h-9 w-28 items-center justify-center text-sm font-semibold transition-all duration-200 rounded-md ${
billingPeriod === 'monthly' ? 'text-white' : 'text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200'
}`}
>
Monthly
-
-
+ setBillingPeriod('annually')}
className={`relative flex h-9 w-28 items-center justify-center text-sm font-semibold transition-all duration-200 rounded-md ${
billingPeriod === 'annually' ? 'text-white' : 'text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200'
}`}
>
Annually
-
+
onPlanSelect(plan)}
className={`p-3 rounded-lg border-2 text-left transition-all ${
isSelected
@@ -366,7 +373,7 @@ export default function SignUpFormUnified({
{billingPeriod === 'annually' && !isFree ? '/year' : '/month'}
-
+
);
})}
@@ -550,24 +557,28 @@ export default function SignUpFormUnified({
billingPeriod === 'monthly' ? 'translate-x-0' : 'translate-x-32'
}`}
>
-
setBillingPeriod('monthly')}
className={`relative flex h-11 w-32 items-center justify-center text-base font-semibold transition-all duration-200 rounded-lg ${
billingPeriod === 'monthly' ? 'text-white' : 'text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200'
}`}
>
Monthly
-
-
+ setBillingPeriod('annually')}
className={`relative flex h-11 w-32 items-center justify-center text-base font-semibold transition-all duration-200 rounded-lg ${
billingPeriod === 'annually' ? 'text-white' : 'text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200'
}`}
>
Annually
-
+
-
diff --git a/frontend/src/components/common/ChartTab.tsx b/frontend/src/components/common/ChartTab.tsx
index f62e2db2..9b9c4c01 100644
--- a/frontend/src/components/common/ChartTab.tsx
+++ b/frontend/src/components/common/ChartTab.tsx
@@ -1,4 +1,5 @@
import { useState } from "react";
+import Button from "../ui/button/Button";
const ChartTab: React.FC = () => {
const [selected, setSelected] = useState<
@@ -12,32 +13,35 @@ const ChartTab: React.FC = () => {
return (
- setSelected("optionOne")}
className={`px-3 py-2 font-medium w-full rounded-md text-theme-sm hover:text-gray-900 dark:hover:text-white ${getButtonClass(
"optionOne"
)}`}
>
Monthly
-
+
- setSelected("optionTwo")}
className={`px-3 py-2 font-medium w-full rounded-md text-theme-sm hover:text-gray-900 dark:hover:text-white ${getButtonClass(
"optionTwo"
)}`}
>
Quarterly
-
+
- setSelected("optionThree")}
className={`px-3 py-2 font-medium w-full rounded-md text-theme-sm hover:text-gray-900 dark:hover:text-white ${getButtonClass(
"optionThree"
)}`}
>
Annually
-
+
);
};
diff --git a/frontend/src/components/common/ContentImageCell.tsx b/frontend/src/components/common/ContentImageCell.tsx
index 29b98027..dcf30e70 100644
--- a/frontend/src/components/common/ContentImageCell.tsx
+++ b/frontend/src/components/common/ContentImageCell.tsx
@@ -4,6 +4,7 @@
*/
import React, { useState } from 'react';
import Badge from '../ui/badge/Badge';
+import Button from '../ui/button/Button';
export interface ContentImageData {
id?: number;
@@ -73,12 +74,15 @@ export default function ContentImageCell({ image, maxPromptLength = 100, showPro
{displayPrompt}
{shouldTruncate && (
- setShowFullPrompt(!showFullPrompt)}
- className="ml-1 text-brand-500 hover:text-brand-600 text-xs"
+ className="ml-1 p-0 h-auto text-xs"
>
{showFullPrompt ? 'Show less' : 'Show more'}
-
+
)}
diff --git a/frontend/src/components/common/ContentViewerModal.tsx b/frontend/src/components/common/ContentViewerModal.tsx
index 348f6159..14d10504 100644
--- a/frontend/src/components/common/ContentViewerModal.tsx
+++ b/frontend/src/components/common/ContentViewerModal.tsx
@@ -4,6 +4,8 @@
import { Modal } from '../ui/modal';
import { CloseIcon } from '../../icons';
+import Button from '../ui/button/Button';
+import IconButton from '../ui/button/IconButton';
interface ContentViewerModalProps {
isOpen: boolean;
@@ -30,12 +32,14 @@ export default function ContentViewerModal({
{title}
- }
+ variant="ghost"
+ tone="neutral"
+ size="sm"
+ title="Close"
onClick={onClose}
- className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 transition-colors"
- >
-
-
+ />
{/* Content */}
@@ -48,12 +52,14 @@ export default function ContentViewerModal({
{/* Footer */}
-
Close
-
+
diff --git a/frontend/src/components/common/FormModal.tsx b/frontend/src/components/common/FormModal.tsx
index f42b5647..b2326feb 100644
--- a/frontend/src/components/common/FormModal.tsx
+++ b/frontend/src/components/common/FormModal.tsx
@@ -3,6 +3,8 @@ import { Modal } from '../ui/modal';
import Button from '../ui/button/Button';
import SelectDropdown from '../form/SelectDropdown';
import Label from '../form/Label';
+import InputField from '../form/input/InputField';
+import TextArea from '../form/input/TextArea';
export interface FormField {
key: string;
@@ -69,17 +71,15 @@ export default function FormModal({
<>
{fields.find(f => f.key === 'keyword') && (
-
)}
@@ -87,20 +87,18 @@ export default function FormModal({
@@ -161,36 +155,30 @@ export default function FormModal({
if (field.type === 'textarea') {
return (
-
+
{field.label}
{field.required && *}
-
-
+ field.onChange(e.target.value)}
+ onChange={(val) => field.onChange(val)}
placeholder={field.placeholder}
- required={field.required}
/>
);
}
return (
-
+
{field.label}
{field.required && *}
-
-
+ field.onChange(e.target.value)}
placeholder={field.placeholder}
- required={field.required}
- min={field.min}
- max={field.max}
/>
);
diff --git a/frontend/src/components/common/GlobalErrorDisplay.tsx b/frontend/src/components/common/GlobalErrorDisplay.tsx
index 8bb1b152..7b2bd479 100644
--- a/frontend/src/components/common/GlobalErrorDisplay.tsx
+++ b/frontend/src/components/common/GlobalErrorDisplay.tsx
@@ -1,5 +1,7 @@
import { useEffect, useState } from 'react';
import { useErrorHandler } from '../../hooks/useErrorHandler';
+import IconButton from '../ui/button/IconButton';
+import Button from '../ui/button/Button';
export default function GlobalErrorDisplay() {
const { errors, clearError, clearAllErrors } = useErrorHandler('GlobalErrorDisplay');
@@ -42,23 +44,27 @@ export default function GlobalErrorDisplay() {
)}
- ×}
onClick={() => clearError(index)}
- className="text-error-600 dark:text-error-400 hover:text-error-800 dark:hover:text-error-200 text-xl leading-none"
+ variant="ghost"
+ tone="danger"
+ size="sm"
aria-label="Dismiss error"
- >
- ×
-
+ />
))}
{errors.length > 1 && (
-
Clear All Errors
-
+
)}
);
diff --git a/frontend/src/components/common/ImageGenerationCard.tsx b/frontend/src/components/common/ImageGenerationCard.tsx
index 40cb91d1..d07f7b2b 100644
--- a/frontend/src/components/common/ImageGenerationCard.tsx
+++ b/frontend/src/components/common/ImageGenerationCard.tsx
@@ -1,5 +1,8 @@
import { ReactNode, useState, useEffect } from 'react';
import Button from '../ui/button/Button';
+import TextArea from '../form/input/TextArea';
+import Select from '../form/Select';
+import Label from '../form/Label';
import { useToast } from '../ui/toast/ToastContainer';
import { fetchAPI } from '../../services/api';
@@ -292,14 +295,13 @@ export default function ImageGenerationCard({
{/* Prompt Description - Full Width */}
-
+
Prompt Description *
-
-
+ setPrompt(e.target.value)}
+ onChange={(val) => setPrompt(val)}
rows={6}
- className="w-full rounded-lg border border-gray-300 px-4 py-3 text-sm focus:border-brand-500 focus:outline-none focus:ring-2 focus:ring-brand-500/20 dark:border-gray-700 dark:bg-gray-800 dark:text-white"
placeholder="Describe the visual elements, style, mood, and composition you want in the image..."
/>
@@ -309,14 +311,13 @@ export default function ImageGenerationCard({
{/* Negative Prompt - Small */}
-
+
Negative Prompt
-
-
+ setNegativePrompt(e.target.value)}
+ onChange={(val) => setNegativePrompt(val)}
rows={2}
- className="w-full rounded-lg border border-gray-300 px-4 py-3 text-sm focus:border-brand-500 focus:outline-none focus:ring-2 focus:ring-brand-500/20 dark:border-gray-700 dark:bg-gray-800 dark:text-white"
placeholder="Describe what you DON'T want in the image..."
/>
@@ -328,56 +329,38 @@ export default function ImageGenerationCard({
{/* Image Type */}
-
+
Image Type
-
-
+
+
{/* Image Size */}
-
+
Image Size
-
-
+
+
{/* Image Format */}
-
+
Image Format
-
-
+
+
diff --git a/frontend/src/components/common/ImageQueueModal.tsx b/frontend/src/components/common/ImageQueueModal.tsx
index d15e3c60..299af8fc 100644
--- a/frontend/src/components/common/ImageQueueModal.tsx
+++ b/frontend/src/components/common/ImageQueueModal.tsx
@@ -8,6 +8,7 @@ import React, { useEffect, useState, useRef } from 'react';
import { Modal } from '../ui/modal';
import { FileIcon, TimeIcon, CheckCircleIcon, ErrorIcon } from '../../icons';
import { fetchAPI } from '../../services/api';
+import Button from '../ui/button/Button';
export interface ImageQueueItem {
imageId: number | null;
@@ -593,12 +594,14 @@ export default function ImageQueueModal({
{completedCount} completed{failedCount > 0 ? `, ${failedCount} failed` : ''} of {totalImages} total
{allDone && (
-
Close
-
+
)}
diff --git a/frontend/src/components/common/ImageResultCard.tsx b/frontend/src/components/common/ImageResultCard.tsx
index 6729604c..8634d0ac 100644
--- a/frontend/src/components/common/ImageResultCard.tsx
+++ b/frontend/src/components/common/ImageResultCard.tsx
@@ -1,4 +1,5 @@
import { ReactNode, useEffect, useState } from 'react';
+import Button from '../ui/button/Button';
interface ImageResultCardProps {
title: string;
@@ -195,11 +196,13 @@ export default function ImageResultCard({
View Original
- {
navigator.clipboard.writeText(imageData.url);
}}
- className="inline-flex items-center gap-2 rounded-lg border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 transition-colors hover:bg-gray-50 dark:border-gray-700 dark:text-gray-300 dark:hover:bg-gray-800"
>
Copy URL
-
+
) : (
diff --git a/frontend/src/components/common/SearchModal.tsx b/frontend/src/components/common/SearchModal.tsx
index b2312b28..63ee450c 100644
--- a/frontend/src/components/common/SearchModal.tsx
+++ b/frontend/src/components/common/SearchModal.tsx
@@ -4,6 +4,7 @@
import { useState, useEffect, useRef } from 'react';
import { useNavigate } from 'react-router-dom';
import { Modal } from '../ui/modal';
+import Button from '../ui/button/Button';
interface SearchModalProps {
isOpen: boolean;
@@ -87,11 +88,12 @@ export default function SearchModal({ isOpen, onClose }: SearchModalProps) {
@@ -113,10 +115,12 @@ export default function SearchModal({ isOpen, onClose }: SearchModalProps) {
) : (
filteredResults.map((result, index) => (
- handleSelect(result)}
- className={`w-full px-4 py-3 flex items-center gap-3 text-left transition-colors ${
+ className={`w-full px-4 py-3 flex items-center gap-3 text-left justify-start rounded-none ${
index === selectedIndex
? 'bg-brand-50 dark:bg-brand-900/20 text-brand-600 dark:text-brand-400'
: 'hover:bg-gray-50 dark:hover:bg-gray-800 text-gray-700 dark:text-gray-300'
@@ -126,7 +130,7 @@ export default function SearchModal({ isOpen, onClose }: SearchModalProps) {
{result.title}
-
+
))
)}
diff --git a/frontend/src/components/common/ViewToggle.tsx b/frontend/src/components/common/ViewToggle.tsx
index e8691337..4724d8e8 100644
--- a/frontend/src/components/common/ViewToggle.tsx
+++ b/frontend/src/components/common/ViewToggle.tsx
@@ -1,5 +1,6 @@
import React from "react";
import { GridIcon, ListIcon, TableIcon } from "../../icons";
+import Button from "../ui/button/Button";
export type ViewType = "table" | "kanban" | "list";
@@ -23,19 +24,18 @@ const ViewToggle: React.FC = ({
return (
{views.map((view) => (
- onViewChange(view.type)}
- className={`inline-flex items-center gap-2 px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${
- currentView === view.type
- ? "bg-white text-gray-900 dark:bg-gray-800 dark:text-white shadow-sm"
- : "text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white"
- }`}
+ variant={currentView === view.type ? "secondary" : "ghost"}
+ tone="neutral"
+ size="sm"
+ className={currentView === view.type ? "shadow-sm" : ""}
title={view.label}
+ startIcon={view.icon}
>
- {view.icon}
{view.label}
-
+
))}
);
diff --git a/frontend/src/components/content/ContentFilter.tsx b/frontend/src/components/content/ContentFilter.tsx
index 06ced271..7af7711d 100644
--- a/frontend/src/components/content/ContentFilter.tsx
+++ b/frontend/src/components/content/ContentFilter.tsx
@@ -1,5 +1,7 @@
import React, { useState, useEffect } from 'react';
import { SourceBadge, ContentSource } from './SourceBadge';
+import InputField from '../form/input/InputField';
+import Button from '../ui/button/Button';
interface ContentFilterProps {
onFilterChange: (filters: FilterState) => void;
@@ -34,12 +36,11 @@ export const ContentFilter: React.FC = ({ onFilterChange, cl
{/* Search */}
-
@@ -47,28 +48,24 @@ export const ContentFilter: React.FC
= ({ onFilterChange, cl
Source
- handleSourceChange('all')}
- className={`px-3 py-1 rounded-full text-sm font-medium transition-colors ${
- filters.source === 'all'
- ? 'bg-brand-500 text-white'
- : 'bg-gray-100 text-gray-700 hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600'
- }`}
>
All
-
+
{(['igny8', 'wordpress', 'shopify', 'custom'] as ContentSource[]).map((source) => (
- handleSourceChange(source)}
- className={`px-3 py-1 rounded-full text-sm font-medium transition-colors ${
- filters.source === source
- ? 'bg-brand-500 text-white'
- : 'bg-gray-100 text-gray-700 hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600'
- }`}
>
-
+
))}
diff --git a/frontend/src/components/dashboard/AIOperationsWidget.tsx b/frontend/src/components/dashboard/AIOperationsWidget.tsx
index 1ae126c4..c4a1611c 100644
--- a/frontend/src/components/dashboard/AIOperationsWidget.tsx
+++ b/frontend/src/components/dashboard/AIOperationsWidget.tsx
@@ -11,6 +11,7 @@ import {
FileIcon,
ChevronDownIcon,
} from '../../icons';
+import Button from '../ui/button/Button';
export interface AIOperation {
type: 'clustering' | 'ideas' | 'content' | 'images';
@@ -73,31 +74,33 @@ export default function AIOperationsWidget({ data, onPeriodChange, loading }: AI
{/* Period Dropdown */}
-
setIsDropdownOpen(!isDropdownOpen)}
- className="flex items-center gap-1 px-2 py-1 text-xs font-medium text-gray-600 dark:text-gray-400 bg-gray-100 dark:bg-gray-800 rounded-md hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
+ variant="ghost"
+ tone="neutral"
+ size="xs"
+ endIcon={}
>
{currentPeriod.label}
-
-
+
{isDropdownOpen && (
{periods.map((period) => (
- {
onPeriodChange?.(period.value);
setIsDropdownOpen(false);
}}
- className={`w-full px-3 py-1.5 text-xs text-left hover:bg-gray-100 dark:hover:bg-gray-700 ${
- data.period === period.value
- ? 'text-brand-600 dark:text-brand-400 font-medium'
- : 'text-gray-600 dark:text-gray-400'
- }`}
+ variant="ghost"
+ tone={data.period === period.value ? 'brand' : 'neutral'}
+ size="xs"
+ fullWidth
+ className="justify-start"
>
{period.label}
-
+
))}
)}
diff --git a/frontend/src/components/dashboard/NeedsAttentionBar.tsx b/frontend/src/components/dashboard/NeedsAttentionBar.tsx
index ed78d848..5789f42b 100644
--- a/frontend/src/components/dashboard/NeedsAttentionBar.tsx
+++ b/frontend/src/components/dashboard/NeedsAttentionBar.tsx
@@ -12,6 +12,8 @@ import {
CheckCircleIcon,
CloseIcon,
} from '../../icons';
+import Button from '../ui/button/Button';
+import IconButton from '../ui/button/IconButton';
export interface AttentionItem {
id: string;
@@ -80,9 +82,17 @@ export default function NeedsAttentionBar({ items, onDismiss }: NeedsAttentionBa
return (
{/* Header */}
-
setIsCollapsed(!isCollapsed)}
- className="w-full flex items-center justify-between px-5 py-3 bg-warning-50 dark:bg-warning-900/20 border border-warning-200 dark:border-warning-800 rounded-t-xl hover:bg-warning-100 dark:hover:bg-warning-900/30 transition-colors"
+ variant="ghost"
+ tone="warning"
+ fullWidth
+ className="flex items-center justify-between px-5 py-3 bg-warning-50 dark:bg-warning-900/20 border border-warning-200 dark:border-warning-800 rounded-t-xl hover:bg-warning-100 dark:hover:bg-warning-900/30"
+ endIcon={isCollapsed ? (
+
+ ) : (
+
+ )}
>
@@ -90,12 +100,7 @@ export default function NeedsAttentionBar({ items, onDismiss }: NeedsAttentionBa
Needs Attention ({items.length})
- {isCollapsed ? (
-
- ) : (
-
- )}
-
+
{/* Content */}
{!isCollapsed && (
@@ -127,12 +132,14 @@ export default function NeedsAttentionBar({ items, onDismiss }: NeedsAttentionBa
{item.actionLabel} →
) : item.onAction ? (
-
{item.actionLabel}
-
+
) : null}
{item.secondaryActionHref && (
{onDismiss && (
-
}
onClick={() => onDismiss(item.id)}
- className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
- >
-
-
+ variant="ghost"
+ tone="neutral"
+ size="sm"
+ aria-label="Dismiss"
+ />
)}
);
diff --git a/frontend/src/components/dashboard/UsageChartWidget.tsx b/frontend/src/components/dashboard/UsageChartWidget.tsx
index ec04ded9..9ee6ea19 100644
--- a/frontend/src/components/dashboard/UsageChartWidget.tsx
+++ b/frontend/src/components/dashboard/UsageChartWidget.tsx
@@ -1,6 +1,7 @@
import { useEffect, useState } from 'react';
import { useBillingStore } from '../../store/billingStore';
import ComponentCard from '../common/ComponentCard';
+import Select from '../form/Select';
export default function UsageChartWidget() {
const { usageSummary, loading, loadUsageSummary } = useBillingStore();
@@ -40,15 +41,15 @@ export default function UsageChartWidget() {
>
-
+
diff --git a/frontend/src/components/dashboard/WorkflowCompletionWidget.tsx b/frontend/src/components/dashboard/WorkflowCompletionWidget.tsx
index 152435c2..dc7729d8 100644
--- a/frontend/src/components/dashboard/WorkflowCompletionWidget.tsx
+++ b/frontend/src/components/dashboard/WorkflowCompletionWidget.tsx
@@ -16,6 +16,7 @@
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import { Card } from '../ui/card/Card';
+import Button from '../ui/button/Button';
import { ChevronRightIcon } from '@heroicons/react/24/solid';
import { useWorkflowStats, TimeFilter } from '../../hooks/useWorkflowStats';
import { WORKFLOW_COLORS } from '../../config/colors.config';
@@ -55,17 +56,15 @@ function TimeFilterButtons({ value, onChange }: TimeFilterProps) {
return (
{options.map((option) => (
- onChange(option.value)}
- className={`px-2 py-1 text-xs font-medium rounded-md transition-all ${
- value === option.value
- ? 'bg-brand-500 text-white shadow-sm'
- : 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white'
- }`}
+ variant={value === option.value ? 'primary' : 'ghost'}
+ tone={value === option.value ? 'brand' : 'neutral'}
+ size="xs"
>
{option.label}
-
+
))}
);
diff --git a/frontend/src/components/ecommerce/DemographicCard.tsx b/frontend/src/components/ecommerce/DemographicCard.tsx
index 5f2fc304..d12f8742 100644
--- a/frontend/src/components/ecommerce/DemographicCard.tsx
+++ b/frontend/src/components/ecommerce/DemographicCard.tsx
@@ -3,6 +3,7 @@ import { Dropdown } from "../ui/dropdown/Dropdown";
import { DropdownItem } from "../ui/dropdown/DropdownItem";
import { MoreDotIcon } from "../../icons";
import CountryMap from "./CountryMap";
+import IconButton from "../ui/button/IconButton";
export default function DemographicCard() {
const [isOpen, setIsOpen] = useState(false);
@@ -26,9 +27,9 @@ export default function DemographicCard() {
-
+
-
+
-
+
-
+
-
+
-
+
-
+
Filter
-
-
+
+
See all
-
+
diff --git a/frontend/src/components/onboarding/steps/Step2AddSite.tsx b/frontend/src/components/onboarding/steps/Step2AddSite.tsx
index 212a6897..70587721 100644
--- a/frontend/src/components/onboarding/steps/Step2AddSite.tsx
+++ b/frontend/src/components/onboarding/steps/Step2AddSite.tsx
@@ -278,20 +278,19 @@ export default function Step2AddSite({
}
>
-
Back
: undefined}
>
{isCreating ? 'Creating...' : 'Create Site'}
-
diff --git a/frontend/src/components/onboarding/steps/Step3ConnectIntegration.tsx b/frontend/src/components/onboarding/steps/Step3ConnectIntegration.tsx
index 7e178ae9..98ea382f 100644
--- a/frontend/src/components/onboarding/steps/Step3ConnectIntegration.tsx
+++ b/frontend/src/components/onboarding/steps/Step3ConnectIntegration.tsx
@@ -245,24 +245,13 @@ export default function Step3ConnectIntegration({
size="sm"
onClick={handleTestConnection}
disabled={isTesting}
- className="gap-1"
+ startIcon={
+ isTesting ?
:
+ testResult === 'success' ?
:
+
+ }
>
- {isTesting ? (
- <>
-
- Testing...
- >
- ) : testResult === 'success' ? (
- <>
-
- Connected
- >
- ) : (
- <>
-
- Test Connection
- >
- )}
+ {isTesting ? 'Testing...' : testResult === 'success' ? 'Connected' : 'Test Connection'}
@@ -278,15 +267,16 @@ export default function Step3ConnectIntegration({
}
>
-
Back
Skip for now
@@ -294,10 +284,9 @@ export default function Step3ConnectIntegration({
}
>
Continue
-
diff --git a/frontend/src/components/onboarding/steps/Step4AddKeywords.tsx b/frontend/src/components/onboarding/steps/Step4AddKeywords.tsx
index 9735333c..c105adee 100644
--- a/frontend/src/components/onboarding/steps/Step4AddKeywords.tsx
+++ b/frontend/src/components/onboarding/steps/Step4AddKeywords.tsx
@@ -4,6 +4,8 @@
*/
import React, { useState } from 'react';
import Button from '../../ui/button/Button';
+import IconButton from '../../ui/button/IconButton';
+import InputField from '../../form/input/InputField';
import { Card } from '../../ui/card';
import Badge from '../../ui/badge/Badge';
import Alert from '../../ui/alert/Alert';
@@ -158,14 +160,11 @@ export default function Step4AddKeywords({
@@ -214,12 +215,14 @@ export default function Step4AddKeywords({
{keywords.length} keyword{keywords.length !== 1 ? 's' : ''} added
{keywords.length > 0 && (
- setKeywords([])}
className="text-error-500 hover:text-error-600"
>
Clear all
-
+
)}
@@ -251,15 +254,16 @@ export default function Step4AddKeywords({
}
>
-
Back
Skip for now
@@ -268,10 +272,9 @@ export default function Step4AddKeywords({
variant="primary"
onClick={handleSubmitKeywords}
disabled={isAdding || keywords.length === 0}
- className="gap-2"
+ endIcon={!isAdding ? : undefined}
>
{isAdding ? 'Adding...' : `Add ${keywords.length} Keyword${keywords.length !== 1 ? 's' : ''}`}
-
diff --git a/frontend/src/components/onboarding/steps/Step5Complete.tsx b/frontend/src/components/onboarding/steps/Step5Complete.tsx
index cad2b516..954f6265 100644
--- a/frontend/src/components/onboarding/steps/Step5Complete.tsx
+++ b/frontend/src/components/onboarding/steps/Step5Complete.tsx
@@ -157,10 +157,10 @@ export default function Step5Complete({
size="lg"
onClick={onComplete}
disabled={isLoading}
- className="gap-2 w-full"
+ fullWidth
+ endIcon={!isLoading ? : undefined}
>
{isLoading ? 'Loading...' : 'Go to Dashboard'}
-
);
diff --git a/frontend/src/components/shared/blocks/CTABlock.tsx b/frontend/src/components/shared/blocks/CTABlock.tsx
index 8a0cf05b..14f98cc6 100644
--- a/frontend/src/components/shared/blocks/CTABlock.tsx
+++ b/frontend/src/components/shared/blocks/CTABlock.tsx
@@ -1,5 +1,6 @@
import type { ReactNode } from 'react';
import './blocks.css';
+import Button from '../../ui/button/Button';
export interface CTABlockProps {
title: string;
@@ -44,13 +45,12 @@ export function CTABlock({
{primaryCtaLabel}
) : (
-
{primaryCtaLabel}
-
+
)
)}
{secondaryCtaLabel && (
@@ -59,13 +59,12 @@ export function CTABlock({
{secondaryCtaLabel}
) : (
-
{secondaryCtaLabel}
-
+
)
)}
diff --git a/frontend/src/components/shared/blocks/ContactFormBlock.tsx b/frontend/src/components/shared/blocks/ContactFormBlock.tsx
index 5e46f472..cb546454 100644
--- a/frontend/src/components/shared/blocks/ContactFormBlock.tsx
+++ b/frontend/src/components/shared/blocks/ContactFormBlock.tsx
@@ -1,5 +1,8 @@
import { useState } from 'react';
import './blocks.css';
+import InputField from '../../form/input/InputField';
+import TextArea from '../../form/input/TextArea';
+import Button from '../../ui/button/Button';
export interface ContactFormBlockProps {
title?: string;
@@ -49,33 +52,25 @@ export function ContactFormBlock({
{field.required && *}
{field.type === 'textarea' ? (
- handleChange(field.name, e.target.value)}
- className="shared-contact-form__input"
+ onChange={(val) => handleChange(field.name, val)}
rows={4}
/>
) : (
- handleChange(field.name, e.target.value)}
- className="shared-contact-form__input"
/>
)}
))}
-
+
{submitLabel}
-
+
);
diff --git a/frontend/src/components/shared/blocks/HeroBlock.tsx b/frontend/src/components/shared/blocks/HeroBlock.tsx
index 68e148ec..b095797d 100644
--- a/frontend/src/components/shared/blocks/HeroBlock.tsx
+++ b/frontend/src/components/shared/blocks/HeroBlock.tsx
@@ -1,5 +1,6 @@
import type { ReactNode } from 'react';
import './blocks.css';
+import Button from '../../ui/button/Button';
export interface HeroBlockProps {
eyebrow?: string;
@@ -18,9 +19,9 @@ export function HeroBlock({ eyebrow, title, subtitle, ctaLabel, onCtaClick, supp
{subtitle && {subtitle}
}
{supportingContent && {supportingContent}
}
{ctaLabel && (
-
+
{ctaLabel}
-
+
)}
);
diff --git a/frontend/src/components/shared/blocks/ProductsBlock.tsx b/frontend/src/components/shared/blocks/ProductsBlock.tsx
index 11b9ee3f..2bae8266 100644
--- a/frontend/src/components/shared/blocks/ProductsBlock.tsx
+++ b/frontend/src/components/shared/blocks/ProductsBlock.tsx
@@ -1,5 +1,6 @@
import type { ReactNode } from 'react';
import './blocks.css';
+import Button from '../../ui/button/Button';
export interface ProductItem {
name: string;
@@ -41,13 +42,12 @@ export function ProductsBlock({
)}
{product.price && {product.price}
}
{product.ctaLabel && (
-
{product.ctaLabel}
-
+
)}
))}
diff --git a/frontend/src/components/sites/SiteProgressWidget.tsx b/frontend/src/components/sites/SiteProgressWidget.tsx
index e263c2e8..f111f624 100644
--- a/frontend/src/components/sites/SiteProgressWidget.tsx
+++ b/frontend/src/components/sites/SiteProgressWidget.tsx
@@ -6,6 +6,7 @@ import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { Card } from '../ui/card';
import Badge from '../ui/badge/Badge';
+import Button from '../ui/button/Button';
// import { fetchSiteProgress, SiteProgress } from '../../services/api';
import { CheckCircleIcon, XCircleIcon, AlertCircleIcon, ArrowRightIcon } from '../../icons';
@@ -326,7 +327,7 @@ export default function SiteProgressWidget({ blueprintId, siteId }: SiteProgress
- Some data may be outdated. Refresh
+ Some data may be outdated. Refresh
diff --git a/frontend/src/components/sites/StyleEditor.tsx b/frontend/src/components/sites/StyleEditor.tsx
index 0f3dadbc..b56d63bf 100644
--- a/frontend/src/components/sites/StyleEditor.tsx
+++ b/frontend/src/components/sites/StyleEditor.tsx
@@ -9,6 +9,7 @@ import { Card } from '../../ui/card';
import Button from '../../ui/button/Button';
import Label from '../../form/Label';
import TextArea from '../../form/input/TextArea';
+import InputField from '../../form/input/InputField';
import { useToast } from '../../ui/toast/ToastContainer';
export interface StyleSettings {
@@ -106,53 +107,41 @@ export default function StyleEditor({ styleSettings, onChange, onSave, onReset }
{/* Tabs */}
-
setActiveTab('css')}
- className={`px-4 py-2 font-medium border-b-2 transition-colors ${
- activeTab === 'css'
- ? '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'
- }`}
>
-
+
Custom CSS
-
-
+ setActiveTab('colors')}
- className={`px-4 py-2 font-medium border-b-2 transition-colors ${
- activeTab === 'colors'
- ? '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'
- }`}
>
-
+
Colors
-
-
+ setActiveTab('typography')}
- className={`px-4 py-2 font-medium border-b-2 transition-colors ${
- activeTab === 'typography'
- ? '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'
- }`}
>
-
+
Typography
-
-
+ setActiveTab('spacing')}
- className={`px-4 py-2 font-medium border-b-2 transition-colors ${
- activeTab === 'spacing'
- ? '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'
- }`}
>
Spacing
-
+
@@ -185,18 +174,18 @@ export default function StyleEditor({ styleSettings, onChange, onSave, onReset }
@@ -211,45 +200,45 @@ export default function StyleEditor({ styleSettings, onChange, onSave, onReset }
@@ -262,12 +251,12 @@ export default function StyleEditor({ styleSettings, onChange, onSave, onReset }
Base Spacing Unit
-
handleSpacingChange('base', e.target.value)}
placeholder="8px"
- 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"
/>
Base unit for spacing calculations (e.g., 8px, 1rem)
@@ -276,12 +265,12 @@ export default function StyleEditor({ styleSettings, onChange, onSave, onReset }
Spacing Scale
-
handleSpacingChange('scale', e.target.value)}
placeholder="1.5"
- 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"
/>
Multiplier for spacing scale (e.g., 1.5 for 1.5x spacing)
diff --git a/frontend/src/components/sites/TemplateCustomizer.tsx b/frontend/src/components/sites/TemplateCustomizer.tsx
index d038353d..4889184e 100644
--- a/frontend/src/components/sites/TemplateCustomizer.tsx
+++ b/frontend/src/components/sites/TemplateCustomizer.tsx
@@ -9,6 +9,7 @@ import { Card } from '../../ui/card';
import Label from '../../form/Label';
import SelectDropdown from '../../form/SelectDropdown';
import Button from '../../ui/button/Button';
+import InputField from '../../form/input/InputField';
export interface TemplateCustomization {
layout: string;
@@ -68,54 +69,42 @@ export default function TemplateCustomizer({
{/* Tabs */}
-
setActiveTab('layout')}
- className={`px-4 py-2 font-medium border-b-2 transition-colors ${
- activeTab === 'layout'
- ? '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'
- }`}
>
-
+
Layout
-
-
+ setActiveTab('colors')}
- className={`px-4 py-2 font-medium border-b-2 transition-colors ${
- activeTab === 'colors'
- ? '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'
- }`}
>
-
+
Colors
-
-
+ setActiveTab('typography')}
- className={`px-4 py-2 font-medium border-b-2 transition-colors ${
- activeTab === 'typography'
- ? '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'
- }`}
>
-
+
Typography
-
-
+ setActiveTab('spacing')}
- className={`px-4 py-2 font-medium border-b-2 transition-colors ${
- activeTab === 'spacing'
- ? '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'
- }`}
>
-
+
Spacing
-
+
@@ -203,7 +192,7 @@ export default function TemplateCustomizer({
@@ -230,7 +219,7 @@ export default function TemplateCustomizer({
diff --git a/frontend/src/components/sites/TemplateLibrary.tsx b/frontend/src/components/sites/TemplateLibrary.tsx
index cab87677..7b6d69df 100644
--- a/frontend/src/components/sites/TemplateLibrary.tsx
+++ b/frontend/src/components/sites/TemplateLibrary.tsx
@@ -7,6 +7,7 @@ import React, { useState } from 'react';
import { SearchIcon, FilterIcon, CheckIcon } from '../../icons';
import { Card } from '../../ui/card';
import Button from '../../ui/button/Button';
+import InputField from '../../form/input/InputField';
export interface TemplateOption {
id: string;
@@ -106,47 +107,37 @@ export default function TemplateLibrary({
{/* Search and Filters */}
-
-
-
+ setSearchTerm(e.target.value)}
- className="w-full pl-10 pr-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md dark:bg-gray-800 dark:text-white"
+ startIcon={}
/>
- setShowFeaturedOnly(!showFeaturedOnly)}
- className={`px-4 py-2 rounded-md text-sm font-medium transition-colors ${
- showFeaturedOnly
- ? 'bg-brand-500 text-white'
- : 'bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700'
- }`}
+ startIcon={}
>
-
Featured Only
-
+
{/* Category Filter */}
{categories.map((category) => (
- setSelectedCategory(category)}
- className={`px-4 py-2 rounded-md text-sm font-medium transition-colors ${
- selectedCategory === category
- ? 'bg-brand-500 text-white'
- : 'bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700'
- }`}
>
{category.charAt(0).toUpperCase() + category.slice(1)}
-
+
))}
diff --git a/frontend/src/components/sites/WordPressIntegrationForm.tsx b/frontend/src/components/sites/WordPressIntegrationForm.tsx
index 8f6282ce..78b73015 100644
--- a/frontend/src/components/sites/WordPressIntegrationForm.tsx
+++ b/frontend/src/components/sites/WordPressIntegrationForm.tsx
@@ -5,9 +5,11 @@
import React, { useState, useEffect } from 'react';
import { Card } from '../ui/card';
import Button from '../ui/button/Button';
+import IconButton from '../ui/button/IconButton';
import Label from '../form/Label';
import Input from '../form/input/InputField';
import Checkbox from '../form/input/Checkbox';
+import Switch from '../form/switch/Switch';
import { useToast } from '../ui/toast/ToastContainer';
import { integrationApi, SiteIntegration } from '../../services/integration.api';
import { fetchAPI } from '../../services/api';
@@ -218,26 +220,11 @@ export default function WordPressIntegrationForm({
{/* Toggle Switch */}
{apiKey && (
-
-
- {integrationEnabled ? 'Sync Enabled' : 'Sync Disabled'}
-
- handleToggleIntegration(!integrationEnabled)}
- className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-brand-500 focus:ring-offset-2 ${
- integrationEnabled ? 'bg-brand-600' : 'bg-gray-300 dark:bg-gray-600'
- }`}
- role="switch"
- aria-checked={integrationEnabled}
- >
-
-
-
+
handleToggleIntegration(checked)}
+ />
)}
@@ -293,30 +280,32 @@ export default function WordPressIntegrationForm({
-
-
-
- Copy
-
+
+ Copy
+
-
-
+
@@ -326,9 +315,9 @@ export default function WordPressIntegrationForm({
-
setApiKeyVisible(!apiKeyVisible)}
- className="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 hover:bg-gray-50 dark:hover:bg-gray-700"
+ variant="outline"
>
{apiKeyVisible ? (
)}
-
+
@@ -354,22 +343,23 @@ export default function WordPressIntegrationForm({
-
-
-
+
-
+
|
diff --git a/frontend/src/components/tasks/KanbanBoard.tsx b/frontend/src/components/tasks/KanbanBoard.tsx
index c79c491f..4cf54da0 100644
--- a/frontend/src/components/tasks/KanbanBoard.tsx
+++ b/frontend/src/components/tasks/KanbanBoard.tsx
@@ -1,6 +1,8 @@
import React, { useState } from "react";
-import { PlusIcon, HorizontaLDots } from "../../icons";
+import { PlusIcon, HorizontaLDots, FilterIcon } from "../../icons";
import RelationshipMap from "./RelationshipMap";
+import Button from "../ui/button/Button";
+import IconButton from "../ui/button/IconButton";
export interface Task {
id: string;
@@ -103,13 +105,12 @@ const KanbanBoard: React.FC
= ({
- handleFilterChange("All")}
- className={`inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-md group hover:text-gray-900 dark:hover:text-white ${
- selectedFilter === "All"
- ? "text-gray-900 dark:text-white bg-white dark:bg-gray-800"
- : "text-gray-500 dark:text-gray-400"
- }`}
+ className={`group ${selectedFilter === "All" ? "bg-white dark:bg-gray-800 text-gray-900 dark:text-white" : ""}`}
>
All Tasks
= ({
>
{tasks.length}
-
+
- handleFilterChange("Todo")}
- className={`inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-md group hover:text-gray-900 dark:hover:text-white ${
- selectedFilter === "Todo"
- ? "text-gray-900 dark:text-white bg-white dark:bg-gray-800"
- : "text-gray-500 dark:text-gray-400"
- }`}
+ className={`group ${selectedFilter === "Todo" ? "bg-white dark:bg-gray-800 text-gray-900 dark:text-white" : ""}`}
>
To do
= ({
>
{todoTasks.length}
-
+
- handleFilterChange("InProgress")}
- className={`inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-md group hover:text-gray-900 dark:hover:text-white ${
- selectedFilter === "InProgress"
- ? "text-gray-900 dark:text-white bg-white dark:bg-gray-800"
- : "text-gray-500 dark:text-gray-400"
- }`}
+ className={`group ${selectedFilter === "InProgress" ? "bg-white dark:bg-gray-800 text-gray-900 dark:text-white" : ""}`}
>
In Progress
= ({
>
{inProgressTasks.length}
-
+
- handleFilterChange("Completed")}
- className={`inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-md group hover:text-gray-900 dark:hover:text-white ${
- selectedFilter === "Completed"
- ? "text-gray-900 dark:text-white bg-white dark:bg-gray-800"
- : "text-gray-500 dark:text-gray-400"
- }`}
+ className={`group ${selectedFilter === "Completed" ? "bg-white dark:bg-gray-800 text-gray-900 dark:text-white" : ""}`}
>
Completed
= ({
>
{completedTasks.length}
-
+
-
-
+ }
+ >
Filter & Sort
-
+
-
}
>
Add New Task
-
-
+
@@ -324,12 +326,14 @@ const KanbanColumn: React.FC = ({
-
}
+ variant="ghost"
+ tone="neutral"
+ size="sm"
+ title="More options"
onClick={() => onDropdownToggle(status)}
- className="text-gray-700 dark:text-gray-400"
- >
-
-
+ />
{openDropdown && (
<>
= ({
onClick={() => onDropdownClose(status)}
/>
-
+
Edit
-
-
+
+
Delete
-
-
+
+
Clear All
-
+
>
)}
diff --git a/frontend/src/components/tasks/RelationshipMap.tsx b/frontend/src/components/tasks/RelationshipMap.tsx
index fd3f0027..07afc6e2 100644
--- a/frontend/src/components/tasks/RelationshipMap.tsx
+++ b/frontend/src/components/tasks/RelationshipMap.tsx
@@ -1,6 +1,7 @@
import React from "react";
import { Link } from "react-router-dom";
import { ArrowRightIcon } from "../../icons";
+import Button from "../ui/button/Button";
export interface RelationshipData {
taskId: number;
@@ -43,13 +44,15 @@ const RelationshipMap: React.FC
= ({
<>
{task.keywordNames?.slice(0, 3).map((keyword, idx) => (
-
onNavigate?.("keyword", task.keywordIds![idx])}
- className="px-2 py-0.5 rounded bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
+ variant="ghost"
+ tone="neutral"
+ size="xs"
>
{keyword}
-
+
))}
{task.keywordIds.length > 3 && (
diff --git a/frontend/src/components/tasks/TaskList.tsx b/frontend/src/components/tasks/TaskList.tsx
index 198411c5..a1c17536 100644
--- a/frontend/src/components/tasks/TaskList.tsx
+++ b/frontend/src/components/tasks/TaskList.tsx
@@ -1,6 +1,9 @@
import React, { useState } from "react";
-import { PlusIcon, HorizontaLDots } from "../../icons";
+import { PlusIcon, HorizontaLDots, FilterIcon } from "../../icons";
import { Task } from "./KanbanBoard";
+import Button from "../ui/button/Button";
+import IconButton from "../ui/button/IconButton";
+import Checkbox from "../form/input/Checkbox";
interface TaskListProps {
tasks: Task[];
@@ -66,13 +69,12 @@ const TaskList: React.FC = ({
- handleFilterChange("All")}
- className={`inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-md group hover:text-gray-900 dark:hover:text-white ${
- selectedFilter === "All"
- ? "text-gray-900 dark:text-white bg-white dark:bg-gray-800"
- : "text-gray-500 dark:text-gray-400"
- }`}
+ className={`group ${selectedFilter === "All" ? "bg-white dark:bg-gray-800 text-gray-900 dark:text-white" : ""}`}
>
All Tasks
= ({
>
{tasks.length}
-
+
- handleFilterChange("Todo")}
- className={`inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-md group hover:text-gray-900 dark:hover:text-white ${
- selectedFilter === "Todo"
- ? "text-gray-900 dark:text-white bg-white dark:bg-gray-800"
- : "text-gray-500 dark:text-gray-400"
- }`}
+ className={`group ${selectedFilter === "Todo" ? "bg-white dark:bg-gray-800 text-gray-900 dark:text-white" : ""}`}
>
To do
= ({
>
{todoTasks.length}
-
+
- handleFilterChange("InProgress")}
- className={`inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-md group hover:text-gray-900 dark:hover:text-white ${
- selectedFilter === "InProgress"
- ? "text-gray-900 dark:text-white bg-white dark:bg-gray-800"
- : "text-gray-500 dark:text-gray-400"
- }`}
+ className={`group ${selectedFilter === "InProgress" ? "bg-white dark:bg-gray-800 text-gray-900 dark:text-white" : ""}`}
>
In Progress
= ({
>
{inProgressTasks.length}
-
+
- handleFilterChange("Completed")}
- className={`inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-md group hover:text-gray-900 dark:hover:text-white ${
- selectedFilter === "Completed"
- ? "text-gray-900 dark:text-white bg-white dark:bg-gray-800"
- : "text-gray-500 dark:text-gray-400"
- }`}
+ className={`group ${selectedFilter === "Completed" ? "bg-white dark:bg-gray-800 text-gray-900 dark:text-white" : ""}`}
>
Completed
= ({
>
{completedTasks.length}
-
+
-
-
+ }
+ >
Filter & Sort
-
+
-
}
>
Add New Task
-
-
+
@@ -257,12 +260,14 @@ const TaskListSection: React.FC = ({
-
}
+ variant="ghost"
+ tone="neutral"
+ size="sm"
+ title="More options"
onClick={() => onDropdownToggle(title.toLowerCase().replace(" ", "_"))}
- className="text-gray-700 dark:text-gray-400"
- >
-
-
+ />
{openDropdown && (
<>
= ({
onClick={() => onDropdownClose(title.toLowerCase().replace(" ", "_"))}
/>
-
+
Edit
-
-
+
+
Delete
-
-
+
+
Clear All
-
+
>
)}
@@ -334,27 +339,18 @@ const TaskListItem: React.FC
= ({ task, checked, onClick, onC
-
-
-
+
+
onCheckboxChange(e.target.checked)}
+ onChange={onCheckboxChange}
/>
-
-
- {task.title}
-
-
+
+ {task.title}
+
+
diff --git a/frontend/src/components/ui/button/IconButton.tsx b/frontend/src/components/ui/button/IconButton.tsx
new file mode 100644
index 00000000..0a484f5e
--- /dev/null
+++ b/frontend/src/components/ui/button/IconButton.tsx
@@ -0,0 +1,145 @@
+/**
+ * IconButton Component
+ *
+ * 🔒 STYLE LOCKED - See DESIGN_SYSTEM.md for available variants and sizes.
+ * Icon-only button component for actions that only need an icon.
+ */
+import { ReactNode, forwardRef } from "react";
+import clsx from "clsx";
+import { twMerge } from "tailwind-merge";
+
+type IconButtonSize = "xs" | "sm" | "md" | "lg";
+type IconButtonVariant = "solid" | "outline" | "ghost";
+type IconButtonTone = "brand" | "success" | "warning" | "danger" | "neutral";
+type IconButtonShape = "rounded" | "circle";
+
+interface IconButtonProps {
+ icon: ReactNode;
+ size?: IconButtonSize;
+ variant?: IconButtonVariant;
+ tone?: IconButtonTone;
+ shape?: IconButtonShape;
+ onClick?: () => void;
+ disabled?: boolean;
+ className?: string;
+ type?: "button" | "submit" | "reset";
+ title?: string;
+ "aria-label"?: string;
+}
+
+const toneMap: Record<
+ IconButtonTone,
+ {
+ solid: string;
+ outline: string;
+ ghost: string;
+ ring: string;
+ }
+> = {
+ brand: {
+ solid: "bg-brand-500 text-white hover:bg-brand-600",
+ outline: "text-brand-600 ring-1 ring-brand-200 hover:bg-brand-50 dark:ring-brand-500/40 dark:text-brand-300 dark:hover:bg-brand-500/10",
+ ghost: "text-brand-600 hover:bg-brand-50 dark:text-brand-300 dark:hover:bg-brand-500/10",
+ ring: "focus-visible:ring-brand-500",
+ },
+ success: {
+ solid: "bg-success-500 text-white hover:bg-success-600",
+ outline: "text-success-600 ring-1 ring-success-200 hover:bg-success-50 dark:ring-success-500/40 dark:text-success-300 dark:hover:bg-success-500/10",
+ ghost: "text-success-600 hover:bg-success-50 dark:text-success-300 dark:hover:bg-success-500/10",
+ ring: "focus-visible:ring-success-500",
+ },
+ warning: {
+ solid: "bg-warning-500 text-white hover:bg-warning-600",
+ outline: "text-warning-600 ring-1 ring-warning-200 hover:bg-warning-50 dark:ring-warning-500/40 dark:text-warning-300 dark:hover:bg-warning-500/10",
+ ghost: "text-warning-600 hover:bg-warning-50 dark:text-warning-300 dark:hover:bg-warning-500/10",
+ ring: "focus-visible:ring-warning-500",
+ },
+ danger: {
+ solid: "bg-error-500 text-white hover:bg-error-600",
+ outline: "text-error-600 ring-1 ring-error-200 hover:bg-error-50 dark:ring-error-500/40 dark:text-error-300 dark:hover:bg-error-500/10",
+ ghost: "text-error-600 hover:bg-error-50 dark:text-error-300 dark:hover:bg-error-500/10",
+ ring: "focus-visible:ring-error-500",
+ },
+ neutral: {
+ solid: "bg-gray-700 text-white hover:bg-gray-800 dark:bg-gray-600 dark:hover:bg-gray-500",
+ outline: "text-gray-700 ring-1 ring-gray-300 hover:bg-gray-100 dark:text-gray-300 dark:ring-gray-600 dark:hover:bg-gray-800",
+ ghost: "text-gray-600 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-800",
+ ring: "focus-visible:ring-gray-400",
+ },
+};
+
+const sizeClasses: Record
= {
+ xs: "h-6 w-6 text-xs",
+ sm: "h-8 w-8 text-sm",
+ md: "h-10 w-10 text-base",
+ lg: "h-12 w-12 text-lg",
+};
+
+const iconSizeClasses: Record = {
+ xs: "[&>svg]:w-3 [&>svg]:h-3",
+ sm: "[&>svg]:w-4 [&>svg]:h-4",
+ md: "[&>svg]:w-5 [&>svg]:h-5",
+ lg: "[&>svg]:w-6 [&>svg]:h-6",
+};
+
+const IconButton = forwardRef(
+ (
+ {
+ icon,
+ size = "md",
+ variant = "ghost",
+ tone = "neutral",
+ shape = "rounded",
+ onClick,
+ className = "",
+ disabled = false,
+ type = "button",
+ title,
+ "aria-label": ariaLabel,
+ },
+ ref,
+ ) => {
+ const toneStyles = toneMap[tone];
+
+ const variantClasses: Record = {
+ solid: toneStyles.solid,
+ outline: clsx("bg-transparent transition-colors", toneStyles.outline),
+ ghost: toneStyles.ghost,
+ };
+
+ const baseClasses =
+ "inline-flex items-center justify-center transition duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed";
+
+ const shapeClasses = shape === "circle" ? "rounded-full" : "rounded-lg";
+
+ const computedClass = twMerge(
+ clsx(
+ baseClasses,
+ sizeClasses[size],
+ iconSizeClasses[size],
+ shapeClasses,
+ variantClasses[variant],
+ toneStyles.ring,
+ className,
+ ),
+ );
+
+ return (
+
+ {icon}
+
+ );
+ },
+);
+
+IconButton.displayName = "IconButton";
+
+export default IconButton;
diff --git a/frontend/src/components/ui/list/List.tsx b/frontend/src/components/ui/list/List.tsx
index 300199cb..2f1731d8 100644
--- a/frontend/src/components/ui/list/List.tsx
+++ b/frontend/src/components/ui/list/List.tsx
@@ -1,4 +1,5 @@
import { ReactNode } from "react";
+import Checkbox from "../../form/input/Checkbox";
interface ListProps {
children: ReactNode;
@@ -134,23 +135,13 @@ export const ListCheckboxItem: React.FC = ({
}) => {
return (
-
-
-
- onChange?.(e.target.checked)}
- />
-
-
-
- {label}
-
-
+ onChange?.(val)}
+ />
);
};
diff --git a/frontend/src/components/ui/pagination/CompactPagination.tsx b/frontend/src/components/ui/pagination/CompactPagination.tsx
index 57f68019..d708123a 100644
--- a/frontend/src/components/ui/pagination/CompactPagination.tsx
+++ b/frontend/src/components/ui/pagination/CompactPagination.tsx
@@ -1,5 +1,6 @@
import React from "react";
import { AngleLeftIcon, AngleRightIcon } from "../../../icons";
+import Select from "../../form/Select";
interface CompactPaginationProps {
currentPage: number;
@@ -61,16 +62,15 @@ export const CompactPagination: React.FC = ({
Show:
-
+
{billingPeriod === 'annually' && (
@@ -230,7 +235,10 @@ export default function PricingTable({
);
})}
- handlePlanClick(plan)}
disabled={plan.disabled}
className={`flex w-full items-center justify-center rounded-lg p-3.5 text-sm font-medium text-white shadow-theme-xs transition-colors mt-auto ${
@@ -240,7 +248,7 @@ export default function PricingTable({
} ${plan.disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}`}
>
{plan.buttonText || (plan.price === 0 || plan.monthlyPrice === 0 ? 'Start Free' : 'Choose Plan')}
-
+
);
})}
@@ -304,7 +312,10 @@ export default function PricingTable({
);
})}
- handlePlanClick(plan)}
disabled={plan.disabled}
className={`flex w-full items-center justify-center rounded-lg p-3.5 text-sm font-medium text-white shadow-theme-xs transition-colors ${
@@ -314,7 +325,7 @@ export default function PricingTable({
} ${plan.disabled ? 'opacity-50 cursor-not-allowed' : ''}`}
>
{plan.buttonText || (isHighlighted ? 'Choose This Plan' : 'Choose Starter')}
-
+
);
})}
@@ -376,7 +387,10 @@ export default function PricingTable({
>
{plan.period || 'For a Lifetime'}
- handlePlanClick(plan)}
disabled={plan.disabled}
className={`flex h-11 w-full items-center justify-center rounded-lg p-3.5 text-sm font-medium shadow-theme-xs transition-colors ${
@@ -388,7 +402,7 @@ export default function PricingTable({
}`}
>
{plan.buttonText || (plan.disabled ? 'Current Plan' : 'Try for Free')}
-
+
@@ -220,12 +221,11 @@ const Calendar: React.FC = () => {
Enter Start Date
- setEventStartDate(e.target.value)}
- className="dark:bg-dark-900 h-11 w-full appearance-none rounded-lg border border-gray-300 bg-transparent bg-none px-4 py-2.5 pl-4 pr-11 text-sm text-gray-800 shadow-theme-xs 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"
/>
@@ -235,31 +235,32 @@ const Calendar: React.FC = () => {
Enter End Date
- setEventEndDate(e.target.value)}
- className="dark:bg-dark-900 h-11 w-full appearance-none rounded-lg border border-gray-300 bg-transparent bg-none px-4 py-2.5 pl-4 pr-11 text-sm text-gray-800 shadow-theme-xs 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"
/>
-
Close
-
-
+
{selectedEvent ? "Update Changes" : "Add Event"}
-
+
diff --git a/frontend/src/pages/Components.tsx b/frontend/src/pages/Components.tsx
index 7331b5e4..f972315f 100644
--- a/frontend/src/pages/Components.tsx
+++ b/frontend/src/pages/Components.tsx
@@ -8,6 +8,8 @@ import { Pagination } from '../components/ui/pagination/Pagination';
import { Card, CardImage, CardTitle, CardDescription, CardAction, CardIcon } from '../components/ui/card/Card';
import ChartTab from '../components/common/ChartTab';
import PageMeta from '../components/common/PageMeta';
+import InputField from '../components/form/input/InputField';
+import TextArea from '../components/form/input/TextArea';
export default function Components() {
// Alert modals state
@@ -257,9 +259,8 @@ export default function Components() {
First Name
-
@@ -267,9 +268,8 @@ export default function Components() {
Last Name
-
@@ -279,9 +279,8 @@ export default function Components() {
Email
-
@@ -289,9 +288,8 @@ export default function Components() {
Phone
-
@@ -300,9 +298,8 @@ export default function Components() {
Bio
-
@@ -449,34 +446,34 @@ function ButtonGroupsShowcase() {
{/* Default Button Group */}
-
+
Left
-
-
+
+
Center
-
-
+
+
Right
-
+
{/* Icon Button Group */}
diff --git a/frontend/src/pages/Linker/ContentList.tsx b/frontend/src/pages/Linker/ContentList.tsx
index dc4d4fbd..cd488c30 100644
--- a/frontend/src/pages/Linker/ContentList.tsx
+++ b/frontend/src/pages/Linker/ContentList.tsx
@@ -3,6 +3,7 @@ import { useNavigate } from 'react-router-dom';
import PageMeta from '../../components/common/PageMeta';
import PageHeader from '../../components/common/PageHeader';
import ModuleNavigationTabs from '../../components/navigation/ModuleNavigationTabs';
+import Button from '../../components/ui/button/Button';
import { linkerApi } from '../../api/linker.api';
import { fetchContent, Content as ContentType } from '../../services/api';
import { useToast } from '../../components/ui/toast/ToastContainer';
@@ -170,10 +171,11 @@ export default function LinkerContentList() {
{item.linker_version || 0}
- handleLink(item.id)}
disabled={isProcessing || processing === -1}
- className="inline-flex items-center gap-2 px-3 py-1.5 bg-brand-500 text-white rounded hover:bg-brand-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
+ variant="primary"
+ size="sm"
>
{isProcessing ? (
<>
@@ -186,7 +188,7 @@ export default function LinkerContentList() {
Add Links
>
)}
-
+
|
);
@@ -202,20 +204,22 @@ export default function LinkerContentList() {
Showing {((currentPage - 1) * pageSize) + 1} to {Math.min(currentPage * pageSize, totalCount)} of {totalCount} results
- setCurrentPage(prev => Math.max(1, prev - 1))}
disabled={currentPage === 1}
- className="px-3 py-1 border border-gray-300 dark:border-gray-600 rounded text-sm disabled:opacity-50"
+ variant="outline"
+ size="sm"
>
Previous
-
-
+ setCurrentPage(prev => prev + 1)}
disabled={currentPage * pageSize >= totalCount}
- className="px-3 py-1 border border-gray-300 dark:border-gray-600 rounded text-sm disabled:opacity-50"
+ variant="outline"
+ size="sm"
>
Next
-
+
)}
diff --git a/frontend/src/pages/Optimizer/ContentSelector.tsx b/frontend/src/pages/Optimizer/ContentSelector.tsx
index c5737fe3..5ec06250 100644
--- a/frontend/src/pages/Optimizer/ContentSelector.tsx
+++ b/frontend/src/pages/Optimizer/ContentSelector.tsx
@@ -12,6 +12,9 @@ import { OptimizationScores } from '../../components/optimizer/OptimizationScore
import { BoltIcon, CheckCircleIcon, FileIcon } from '../../icons';
import { useSectorStore } from '../../store/sectorStore';
import { usePageSizeStore } from '../../store/pageSizeStore';
+import Select from '../../components/form/Select';
+import Checkbox from '../../components/form/input/Checkbox';
+import Button from '../../components/ui/button/Button';
export default function OptimizerContentSelector() {
const navigate = useNavigate();
@@ -153,21 +156,21 @@ export default function OptimizerContentSelector() {
/>
-
- setEntryPoint(val as EntryPoint)}
+ />
+ 0}
- className="inline-flex items-center gap-2 px-4 py-2 bg-brand-500 text-white rounded-lg hover:bg-brand-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
{processing.length > 0 ? (
<>
@@ -180,7 +183,7 @@ export default function OptimizerContentSelector() {
Optimize Selected ({selectedIds.length})
>
)}
-
+
@@ -202,11 +205,9 @@ export default function OptimizerContentSelector() {
|
- 0}
onChange={toggleSelectAll}
- className="rounded border-gray-300 text-brand-600 focus:ring-brand-500"
/>
|
@@ -238,11 +239,9 @@ export default function OptimizerContentSelector() {
className={`hover:bg-gray-50 dark:hover:bg-gray-700 ${isSelected ? 'bg-brand-50 dark:bg-brand-900/20' : ''}`}
>
|
- toggleSelection(item.id)}
- className="rounded border-gray-300 text-brand-600 focus:ring-brand-500"
/>
|
@@ -266,10 +265,11 @@ export default function OptimizerContentSelector() {
{item.optimizer_version || 0}
|
- handleOptimize(item.id)}
disabled={isProcessing || processing.length > 0}
- className="inline-flex items-center gap-2 px-3 py-1.5 bg-brand-500 text-white rounded hover:bg-brand-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
{isProcessing ? (
<>
@@ -282,7 +282,7 @@ export default function OptimizerContentSelector() {
Optimize
>
)}
-
+
|
);
@@ -298,20 +298,22 @@ export default function OptimizerContentSelector() {
Showing {((currentPage - 1) * pageSize) + 1} to {Math.min(currentPage * pageSize, totalCount)} of {totalCount} results
- setCurrentPage(prev => Math.max(1, prev - 1))}
disabled={currentPage === 1}
- className="px-3 py-1 border border-gray-300 dark:border-gray-600 rounded text-sm disabled:opacity-50"
>
Previous
-
-
+ setCurrentPage(prev => prev + 1)}
disabled={currentPage * pageSize >= totalCount}
- className="px-3 py-1 border border-gray-300 dark:border-gray-600 rounded text-sm disabled:opacity-50"
>
Next
-
+
)}
diff --git a/frontend/src/pages/Payment.tsx b/frontend/src/pages/Payment.tsx
index 4626368c..f9ed6c59 100644
--- a/frontend/src/pages/Payment.tsx
+++ b/frontend/src/pages/Payment.tsx
@@ -1,6 +1,10 @@
import { useEffect, useMemo, useState } from "react";
import { useLocation, useNavigate, Link } from "react-router-dom";
import { useAuthStore } from "../store/authStore";
+import InputField from "../components/form/input/InputField";
+import TextArea from "../components/form/input/TextArea";
+import Label from "../components/form/Label";
+import Button from "../components/ui/button/Button";
const PLAN_COPY: Record = {
starter: { name: "Starter", price: "$49/mo", content: "50 content pieces/month" },
@@ -76,42 +80,39 @@ export default function Payment() {
)}
-
- Contact email
- setContactEmail(e.target.value)}
- placeholder="you@example.com"
- className="mt-1 w-full rounded-lg border border-gray-300 px-3 py-2 text-gray-900 focus:border-brand-500 focus:outline-none"
- />
-
-
- Notes (optional)
- setContactEmail(e.target.value)}
+ placeholder="you@example.com"
+ />
+
+ Notes (optional)
+ setNote(e.target.value)}
+ onChange={(value) => setNote(value)}
placeholder="Company name, billing contact, or questions"
- className="mt-1 w-full rounded-lg border border-gray-300 px-3 py-2 text-gray-900 focus:border-brand-500 focus:outline-none"
rows={3}
/>
-
+
{error && {error}
}
diff --git a/frontend/src/pages/Planner/ClusterDetail.tsx b/frontend/src/pages/Planner/ClusterDetail.tsx
index f400f37d..def90d4e 100644
--- a/frontend/src/pages/Planner/ClusterDetail.tsx
+++ b/frontend/src/pages/Planner/ClusterDetail.tsx
@@ -233,10 +233,10 @@ export default function ClusterDetail() {
{/* Tabs */}
-
handleTabChange('articles')}
- 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 === 'articles'
? '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'
@@ -244,11 +244,11 @@ export default function ClusterDetail() {
>
Articles
-
-
+ handleTabChange('pages')}
- 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 === 'pages'
? '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'
@@ -256,11 +256,11 @@ export default function ClusterDetail() {
>
Pages
-
-
+ handleTabChange('products')}
- 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 === 'products'
? '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'
@@ -268,11 +268,11 @@ export default function ClusterDetail() {
>
Products
-
-
+ handleTabChange('taxonomy')}
- 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 === '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'
@@ -280,7 +280,7 @@ export default function ClusterDetail() {
>
Taxonomy
-
+
diff --git a/frontend/src/pages/Settings/CreditsAndBilling.tsx b/frontend/src/pages/Settings/CreditsAndBilling.tsx
index 0f1a9297..87c423c8 100644
--- a/frontend/src/pages/Settings/CreditsAndBilling.tsx
+++ b/frontend/src/pages/Settings/CreditsAndBilling.tsx
@@ -127,26 +127,28 @@ const CreditsAndBilling: React.FC = () => {
{/* Tabs */}
diff --git a/frontend/src/pages/Settings/General.tsx b/frontend/src/pages/Settings/General.tsx
index fc37f9f1..3fadb771 100644
--- a/frontend/src/pages/Settings/General.tsx
+++ b/frontend/src/pages/Settings/General.tsx
@@ -5,6 +5,8 @@ import { useSettingsStore } from '../../store/settingsStore';
import { useToast } from '../../components/ui/toast/ToastContainer';
import Button from '../../components/ui/button/Button';
import Label from '../../components/form/Label';
+import InputField from '../../components/form/input/InputField';
+import Select from '../../components/form/Select';
export default function GeneralSettings() {
const toast = useToast();
@@ -49,13 +51,10 @@ export default function GeneralSettings() {
diff --git a/frontend/src/pages/Settings/Sites.tsx b/frontend/src/pages/Settings/Sites.tsx
index d1765e40..44a3742a 100644
--- a/frontend/src/pages/Settings/Sites.tsx
+++ b/frontend/src/pages/Settings/Sites.tsx
@@ -5,6 +5,8 @@ import FormModal, { FormField } from '../../components/common/FormModal';
import Button from '../../components/ui/button/Button';
import { useToast } from '../../components/ui/toast/ToastContainer';
import Alert from '../../components/ui/alert/Alert';
+import Select from '../../components/form/Select';
+import Checkbox from '../../components/form/input/Checkbox';
import {
fetchSites,
createSite,
@@ -475,21 +477,20 @@ export default function Sites() {
Select Industry
-
+ />
{selectedIndustry && (
{industries.find(i => i.slug === selectedIndustry)?.description}
@@ -504,15 +505,14 @@ export default function Sites() {
{getIndustrySectors().map((sector) => (
-
- {
- if (e.target.checked) {
+ onChange={(checked) => {
+ if (checked) {
if (selectedSectors.length >= 5) {
toast.error('Maximum 5 sectors allowed per site');
return;
@@ -522,17 +522,18 @@ export default function Sites() {
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"
+ label={
+
+
+ {sector.name}
+
+
+ {sector.description}
+
+
+ }
/>
-
-
- {sector.name}
-
-
- {sector.description}
-
-
-
+
))}
diff --git a/frontend/src/pages/Sites/Content.tsx b/frontend/src/pages/Sites/Content.tsx
index 358490b0..88c10b7d 100644
--- a/frontend/src/pages/Sites/Content.tsx
+++ b/frontend/src/pages/Sites/Content.tsx
@@ -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() {
diff --git a/frontend/src/pages/Sites/Dashboard.tsx b/frontend/src/pages/Sites/Dashboard.tsx
index c606d541..9e107bb4 100644
--- a/frontend/src/pages/Sites/Dashboard.tsx
+++ b/frontend/src/pages/Sites/Dashboard.tsx
@@ -263,9 +263,11 @@ export default function SiteDashboard() {
{/* Quick Actions */}
-
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"
>
@@ -275,11 +277,13 @@ export default function SiteDashboard() {
View and edit pages
-
+
-
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"
>
@@ -289,11 +293,13 @@ export default function SiteDashboard() {
View and edit content
-
+
-
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"
>
@@ -303,11 +309,13 @@ export default function SiteDashboard() {
Manage connections
-
+
-
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"
>
@@ -317,11 +325,13 @@ export default function SiteDashboard() {
View sync status
-
+
-
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"
>
@@ -331,11 +341,13 @@ export default function SiteDashboard() {
Deploy to production
-
+
-
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"
>
@@ -345,7 +357,7 @@ export default function SiteDashboard() {
View scheduled content
-
+
diff --git a/frontend/src/pages/Sites/List.tsx b/frontend/src/pages/Sites/List.tsx
index 14fd8ef2..cd4fa978 100644
--- a/frontend/src/pages/Sites/List.tsx
+++ b/frontend/src/pages/Sites/List.tsx
@@ -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() {
>
-
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"
- />
-
-
-
+
+ setSearchTerm(e.target.value)}
+ />
+
+
+
+
+
+
+
{hasActiveFilters && (
{isSelected ? (
@@ -365,8 +366,9 @@ export default function PageManager() {
-
@@ -376,7 +378,7 @@ export default function PageManager() {
)}
Select All
-
+
Drag and drop to reorder pages
diff --git a/frontend/src/pages/Sites/PostEditor.tsx b/frontend/src/pages/Sites/PostEditor.tsx
index 2d7d2ccc..e5f41788 100644
--- a/frontend/src/pages/Sites/PostEditor.tsx
+++ b/frontend/src/pages/Sites/PostEditor.tsx
@@ -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 */}
- 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'
- }`}
>
-
+
Content
-
-
+ 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'
- }`}
>
-
+
Taxonomy & Cluster
-
+
{content.id && (
- {
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'
- }`}
>
-
+
Validation
{validationResult && !validationResult.is_valid && (
{validationResult.validation_errors.length}
)}
-
+
)}
@@ -322,12 +311,12 @@ export default function PostEditor() {
Title *
- 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"
/>
diff --git a/frontend/src/pages/Sites/PublishingQueue.tsx b/frontend/src/pages/Sites/PublishingQueue.tsx
index 6d6078aa..b4ab2662 100644
--- a/frontend/src/pages/Sites/PublishingQueue.tsx
+++ b/frontend/src/pages/Sites/PublishingQueue.tsx
@@ -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() {
{queueItems.length} items in queue
-
-
+ 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'
- }`}
>
-
+
List
-
-
+ 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'
- }`}
>
-
+
Calendar
-
-
+
+
{/* Queue Content */}
@@ -361,27 +355,30 @@ export default function PublishingQueue() {
{/* Actions */}
-
}
+ 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"
- >
-
-
-
+
:
}
+ 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 ?
:
}
-
-
+
}
+ 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"
- >
-
-
+ />
))}
diff --git a/frontend/src/pages/Sites/Settings.tsx b/frontend/src/pages/Sites/Settings.tsx
index 445f9f52..2fc38191 100644
--- a/frontend/src/pages/Sites/Settings.tsx
+++ b/frontend/src/pages/Sites/Settings.tsx
@@ -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 && (
-
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"
>
@@ -565,7 +570,7 @@ export default function SiteSettings() {
{site?.name || 'Select Site'}
-
+
setIsSiteSelectorOpen(false)}
@@ -605,13 +610,13 @@ export default function SiteSettings() {
{/* Tabs */}
-
{
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() {
>
General
-
-
+ {
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() {
>
Integrations
-
-
+ {
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() {
>
Publishing
-
+
{(wordPressIntegration || site?.wp_url || site?.wp_api_key || site?.hosting_type === 'wordpress') && (
- {
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() {
>
Content Types
-
+
)}
@@ -696,17 +701,15 @@ export default function SiteSettings() {
- {
- 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"
/>
-
@@ -721,17 +724,15 @@ export default function SiteSettings() {
- {
- 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"
/>
-
@@ -745,7 +746,7 @@ export default function SiteSettings() {
Daily Limit
-
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"
/>
Articles per day
Weekly Limit
-
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"
/>
Articles per week
Monthly Limit
-
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"
/>
Articles per month
@@ -810,9 +805,11 @@ export default function SiteSettings() {
{ value: 'sat', label: 'Sat' },
{ value: 'sun', label: 'Sun' },
].map((day) => (
-
{
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}
-
+
))}
@@ -842,7 +834,7 @@ export default function SiteSettings() {
{(publishingSettings.publish_time_slots || ['09:00', '14:00', '18:00']).map((time: string, index: number) => (
-
{
@@ -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 && (
-
}
+ 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"
- >
-
-
+ />
)}
))}
-
}
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"
>
-
Add Time Slot
-
+
@@ -1084,32 +1072,29 @@ export default function SiteSettings() {
Site Name
- 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"
/>
Slug
- 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"
/>
Site URL
- 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"
/>
@@ -1134,7 +1119,7 @@ export default function SiteSettings() {
setFormData({ ...formData, is_active: e.target.checked })}
+ onChange={(checked) => setFormData({ ...formData, is_active: checked })}
label="Active"
/>
@@ -1150,13 +1135,12 @@ export default function SiteSettings() {
Meta Title
-
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"
/>
{formData.meta_title.length}/60 characters
@@ -1180,12 +1164,11 @@ export default function SiteSettings() {
Meta Keywords (comma-separated)
-
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"
/>
Separate keywords with commas
@@ -1203,12 +1186,11 @@ export default function SiteSettings() {
OG Title
- 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"
/>
@@ -1225,12 +1207,11 @@ export default function SiteSettings() {
@@ -1288,12 +1268,11 @@ export default function SiteSettings() {
Schema Name
- 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"
/>
@@ -1310,34 +1289,31 @@ export default function SiteSettings() {
Schema URL
- 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"
/>
Schema Logo URL
- 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"
/>
Same As URLs (comma-separated)
-
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"
/>
Social media profiles and other related URLs
@@ -1359,21 +1335,18 @@ export default function SiteSettings() {
Select Industry
-
@@ -1447,13 +1418,12 @@ export default function SiteSettings() {
Meta Title
-
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"
/>
{formData.meta_title.length}/60 characters
@@ -1477,12 +1447,11 @@ export default function SiteSettings() {
Meta Keywords (comma-separated)
-
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"
/>
Separate keywords with commas
@@ -1498,12 +1467,11 @@ export default function SiteSettings() {
OG Title
- 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"
/>
@@ -1520,12 +1488,11 @@ export default function SiteSettings() {
OG Image URL
-
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"
/>
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 })}
/>
OG Site Name
- 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"
/>
@@ -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 })}
/>
Schema Name
- 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"
/>
@@ -1603,34 +1568,31 @@ export default function SiteSettings() {
Schema URL
- 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"
/>
Schema Logo URL
- 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"
/>
Same As URLs (comma-separated)
-
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"
/>
Social media profiles and other related URLs
diff --git a/frontend/src/pages/Sites/SyncDashboard.tsx b/frontend/src/pages/Sites/SyncDashboard.tsx
index 71fa083d..09f2ed9a 100644
--- a/frontend/src/pages/Sites/SyncDashboard.tsx
+++ b/frontend/src/pages/Sites/SyncDashboard.tsx
@@ -287,9 +287,10 @@ export default function SyncDashboard() {
{/* Mismatches Section */}
{totalMismatches > 0 && (
- 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"
>
Mismatches ({totalMismatches})
@@ -299,7 +300,7 @@ export default function SyncDashboard() {
) : (
)}
-
+
{showMismatches && mismatches && (
@@ -412,9 +413,10 @@ export default function SyncDashboard() {
{/* Sync Logs */}
- 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"
>
Sync History ({logs.length})
@@ -424,7 +426,7 @@ export default function SyncDashboard() {
) : (
)}
-
+
{showLogs && (
diff --git a/frontend/src/pages/UIElements.tsx b/frontend/src/pages/UIElements.tsx
new file mode 100644
index 00000000..922f4255
--- /dev/null
+++ b/frontend/src/pages/UIElements.tsx
@@ -0,0 +1,737 @@
+/**
+ * UI Elements Showcase Page
+ *
+ * Single source of truth for all UI components from components/ui/
+ * This page is non-indexable and serves as documentation/reference
+ *
+ * Route: /ui-elements
+ */
+import React, { useState } from 'react';
+import PageMeta from '../components/common/PageMeta';
+
+// ============================================================================
+// UI COMPONENTS - All imports from components/ui/ (SINGLE SOURCE OF TRUTH)
+// ============================================================================
+
+// Accordion
+import { Accordion, AccordionItem } from '../components/ui/accordion/Accordion';
+
+// Alert
+import Alert from '../components/ui/alert/Alert';
+import AlertModal from '../components/ui/alert/AlertModal';
+
+// Avatar
+import Avatar from '../components/ui/avatar/Avatar';
+
+// Badge
+import Badge from '../components/ui/badge/Badge';
+
+// Breadcrumb
+import { Breadcrumb } from '../components/ui/breadcrumb/Breadcrumb';
+
+// Button
+import Button from '../components/ui/button/Button';
+import ButtonWithTooltip from '../components/ui/button/ButtonWithTooltip';
+import IconButton from '../components/ui/button/IconButton';
+
+// Button Group
+import { ButtonGroup, ButtonGroupItem } from '../components/ui/button-group/ButtonGroup';
+
+// Card
+import { Card, CardImage, CardTitle, CardContent, CardDescription, CardAction, CardIcon, HorizontalCard } from '../components/ui/card/Card';
+
+// DataView
+import { DataView, DataViewHeader, DataViewToolbar, DataViewEmptyState } from '../components/ui/dataview/DataView';
+
+// Dropdown
+import { Dropdown } from '../components/ui/dropdown/Dropdown';
+import { DropdownItem } from '../components/ui/dropdown/DropdownItem';
+
+// List
+import { List, ListItem, ListDot, ListIcon, ListCheckboxItem, ListRadioItem } from '../components/ui/list/List';
+
+// Modal
+import { Modal } from '../components/ui/modal';
+
+// Pagination
+import { Pagination } from '../components/ui/pagination/Pagination';
+import { CompactPagination } from '../components/ui/pagination/CompactPagination';
+
+// Progress
+import { ProgressBar } from '../components/ui/progress/ProgressBar';
+
+// Ribbon
+import { Ribbon } from '../components/ui/ribbon/Ribbon';
+
+// Spinner
+import { Spinner } from '../components/ui/spinner/Spinner';
+
+// Table
+import { Table, TableHeader, TableBody, TableRow, TableCell } from '../components/ui/table';
+
+// Tabs
+import { Tabs, TabList, Tab, TabPanel } from '../components/ui/tabs/Tabs';
+
+// Toast
+import { useToast } from '../components/ui/toast/ToastContainer';
+
+// Tooltip
+import { Tooltip } from '../components/ui/tooltip/Tooltip';
+import { EnhancedTooltip } from '../components/ui/tooltip/EnhancedTooltip';
+
+// Icons for demos
+import {
+ CheckCircleIcon,
+ CloseIcon,
+ PlusIcon,
+ ArrowRightIcon,
+ GridIcon,
+ FileIcon,
+ BoltIcon,
+} from '../icons';
+
+// Component categories for navigation
+const CATEGORIES = [
+ { id: 'buttons', label: 'Buttons' },
+ { id: 'badges', label: 'Badges' },
+ { id: 'cards', label: 'Cards' },
+ { id: 'alerts', label: 'Alerts' },
+ { id: 'modals', label: 'Modals' },
+ { id: 'tables', label: 'Tables' },
+ { id: 'tabs', label: 'Tabs' },
+ { id: 'accordion', label: 'Accordion' },
+ { id: 'dropdown', label: 'Dropdown' },
+ { id: 'pagination', label: 'Pagination' },
+ { id: 'progress', label: 'Progress' },
+ { id: 'spinner', label: 'Spinner' },
+ { id: 'avatar', label: 'Avatar' },
+ { id: 'breadcrumb', label: 'Breadcrumb' },
+ { id: 'list', label: 'List' },
+ { id: 'tooltip', label: 'Tooltip' },
+ { id: 'ribbon', label: 'Ribbon' },
+ { id: 'toast', label: 'Toast' },
+ { id: 'dataview', label: 'DataView' },
+];
+
+// Section wrapper component
+function Section({ id, title, children }: { id: string; title: string; children: React.ReactNode }) {
+ return (
+
+
+ {title}
+
+
+ {children}
+
+
+ );
+}
+
+// Demo box component
+function DemoBox({ label, children }: { label: string; children: React.ReactNode }) {
+ return (
+
+
{label}
+
+ {children}
+
+
+ );
+}
+
+export default function UIElements() {
+ const toast = useToast();
+
+ // Modal states
+ const [showModal, setShowModal] = useState(false);
+ const [showAlertModal, setShowAlertModal] = useState(false);
+
+ // Dropdown state
+ const [dropdownOpen, setDropdownOpen] = useState(false);
+ const dropdownRef = React.useRef
(null);
+
+ // Pagination state
+ const [currentPage, setCurrentPage] = useState(1);
+ const [pageSize, setPageSize] = useState(10);
+
+ // Tabs state
+ const [activeTab, setActiveTab] = useState('tab1');
+
+ // List state
+ const [checkboxChecked, setCheckboxChecked] = useState(false);
+ const [radioValue, setRadioValue] = useState('option1');
+
+ // Button group state
+ const [activeButton, setActiveButton] = useState(0);
+
+ return (
+ <>
+
+
+
+ {/* Sticky Navigation */}
+
+
+ {/* Main Content */}
+
+
+
+ UI Elements Library
+
+
+ Single source of truth for all UI components. All components are imported from components/ui/
+
+
+
+ {/* ================================================================ */}
+ {/* BUTTONS */}
+ {/* ================================================================ */}
+
+
+ {/* ================================================================ */}
+ {/* BADGES */}
+ {/* ================================================================ */}
+
+
+ Solid
+ Soft
+ Outline
+ Light
+
+
+
+ Brand
+ Success
+ Warning
+ Danger
+ Info
+ Neutral
+
+
+
+ XS
+ SM
+ MD
+
+
+
+ } tone="success">Completed
+ } tone="danger">Remove
+
+
+
+ {/* ================================================================ */}
+ {/* CARDS */}
+ {/* ================================================================ */}
+
+
+
+ Surface Card
+
+
+ Panel Card
+
+
+
+
+
+ Small Padding
+
+
+ Large Padding
+
+
+
+
+
+
+ Card Title
+ Card description text goes here
+ View More
+
+
+
+
+ {/* ================================================================ */}
+ {/* ALERTS */}
+ {/* ================================================================ */}
+
+
+
+
+
+
+ setShowAlertModal(true)}>Open Alert Modal
+ setShowAlertModal(false)}
+ variant="warning"
+ title="Confirm Action"
+ message="Are you sure you want to proceed?"
+ isConfirmation={true}
+ onConfirm={() => setShowAlertModal(false)}
+ />
+
+
+
+ {/* ================================================================ */}
+ {/* MODALS */}
+ {/* ================================================================ */}
+
+
+ setShowModal(true)}>Open Modal
+ setShowModal(false)}>
+
+
Modal Title
+
+ This is modal content. You can put any React components here.
+
+
+ setShowModal(false)}>Cancel
+ setShowModal(false)}>Confirm
+
+
+
+
+
+
+ {/* ================================================================ */}
+ {/* TABLES */}
+ {/* ================================================================ */}
+
+
+
+
+
+ Name
+ Status
+ Date
+
+
+
+
+ Item One
+ Active
+ Jan 1, 2024
+
+
+ Item Two
+ Pending
+ Jan 2, 2024
+
+
+
+
+
+
+ {/* ================================================================ */}
+ {/* TABS */}
+ {/* ================================================================ */}
+
+
+
+
+
+ Tab One
+ Tab Two
+ Tab Three
+
+
+ Content for Tab One
+
+
+ Content for Tab Two
+
+
+ Content for Tab Three
+
+
+
+
+
+
+ {/* ================================================================ */}
+ {/* ACCORDION */}
+ {/* ================================================================ */}
+
+
+
+
+
+ Content for section one goes here.
+
+
+ Content for section two goes here.
+
+
+ Content for section three goes here.
+
+
+
+
+
+
+ {/* ================================================================ */}
+ {/* DROPDOWN */}
+ {/* ================================================================ */}
+
+
+
+ setDropdownOpen(!dropdownOpen)}>
+ Open Dropdown
+
+ setDropdownOpen(false)}
+ anchorRef={dropdownRef}
+ >
+ setDropdownOpen(false)}>Option One
+ setDropdownOpen(false)}>Option Two
+ setDropdownOpen(false)}>Option Three
+
+
+
+
+
+ {/* ================================================================ */}
+ {/* PAGINATION */}
+ {/* ================================================================ */}
+
+
+ {/* ================================================================ */}
+ {/* PROGRESS */}
+ {/* ================================================================ */}
+
+
+ {/* ================================================================ */}
+ {/* SPINNER */}
+ {/* ================================================================ */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* ================================================================ */}
+ {/* AVATAR */}
+ {/* ================================================================ */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* ================================================================ */}
+ {/* BREADCRUMB */}
+ {/* ================================================================ */}
+
+
+ },
+ { label: 'Category', path: '/category' },
+ { label: 'Current Page' },
+ ]} />
+
+
+
+ {/* ================================================================ */}
+ {/* LIST */}
+ {/* ================================================================ */}
+
+
+
+ Item One
+ Item Two
+ Item Three
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* ================================================================ */}
+ {/* TOOLTIP */}
+ {/* ================================================================ */}
+
+ }
+ >
+
Hover Me
+
+
+
+
+ {/* ================================================================ */}
+ {/* RIBBON */}
+ {/* ================================================================ */}
+
+
+
+
+ Card with ribbon
+
+
+
+
+
+ {/* ================================================================ */}
+ {/* TOAST */}
+ {/* ================================================================ */}
+
+
+ toast.success('Success', 'Operation completed!')}>
+ Show Success
+
+ toast.error('Error', 'Something went wrong!')} tone="danger">
+ Show Error
+
+ toast.warning('Warning', 'Please review!')} tone="warning">
+ Show Warning
+
+ toast.info('Info', 'Here is some information')} variant="outline">
+ Show Info
+
+
+
+
+ {/* ================================================================ */}
+ {/* DATAVIEW */}
+ {/* ================================================================ */}
+
+
+
+ Action}
+ />
+
+ Filter
+ Sort
+
+ Data content goes here...
+
+
+
+
+ Add Item}
+ />
+
+
+
+
+
+ >
+ );
+}
diff --git a/frontend/src/pages/account/AccountSettingsPage.tsx b/frontend/src/pages/account/AccountSettingsPage.tsx
index d3bc042f..1e94565c 100644
--- a/frontend/src/pages/account/AccountSettingsPage.tsx
+++ b/frontend/src/pages/account/AccountSettingsPage.tsx
@@ -12,6 +12,9 @@ import {
import { Card } from '../../components/ui/card';
import Button from '../../components/ui/button/Button';
import Badge from '../../components/ui/badge/Badge';
+import InputField from '../../components/form/input/InputField';
+import Select from '../../components/form/Select';
+import Checkbox from '../../components/form/input/Checkbox';
import PageMeta from '../../components/common/PageMeta';
import PageHeader from '../../components/common/PageHeader';
import { useToast } from '../../components/ui/toast/ToastContainer';
@@ -323,40 +326,30 @@ export default function AccountSettingsPage() {
Account Information
-
- Billing Email
-
-
@@ -366,77 +359,59 @@ export default function AccountSettingsPage() {
Billing Address
Language
- setProfileForm({ ...profileForm, language: e.target.value })}
- className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-[var(--color-brand-500)] dark:bg-gray-800"
- >
-
-
-
-
+ setProfileForm({ ...profileForm, language: value })}
+ />
@@ -582,11 +542,9 @@ export default function AccountSettingsPage() {
Get notified about important changes to your account
-
setProfileForm({ ...profileForm, emailNotifications: e.target.checked })}
- className="w-5 h-5 text-[var(--color-brand-500)] rounded focus:ring-[var(--color-brand-500)]"
+ onChange={(checked) => setProfileForm({ ...profileForm, emailNotifications: checked })}
/>
@@ -596,11 +554,9 @@ export default function AccountSettingsPage() {
Hear about new features and content tips
-
setProfileForm({ ...profileForm, marketingEmails: e.target.checked })}
- className="w-5 h-5 text-[var(--color-brand-500)] rounded focus:ring-[var(--color-brand-500)]"
+ onChange={(checked) => setProfileForm({ ...profileForm, marketingEmails: checked })}
/>
@@ -769,39 +725,30 @@ export default function AccountSettingsPage() {
@@ -854,38 +801,29 @@ export default function AccountSettingsPage() {
-
- Current Password
-
- setPasswordForm(prev => ({ ...prev, currentPassword: e.target.value }))}
- 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"
placeholder="Enter current password"
/>
-
- New Password
-
- setPasswordForm(prev => ({ ...prev, newPassword: e.target.value }))}
- 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"
placeholder="Enter new password"
/>
-
- Confirm New Password
-
- setPasswordForm(prev => ({ ...prev, confirmPassword: e.target.value }))}
- 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"
placeholder="Confirm new password"
/>
diff --git a/frontend/src/pages/account/ContentSettingsPage.tsx b/frontend/src/pages/account/ContentSettingsPage.tsx
index b80ad487..876d5233 100644
--- a/frontend/src/pages/account/ContentSettingsPage.tsx
+++ b/frontend/src/pages/account/ContentSettingsPage.tsx
@@ -16,6 +16,7 @@ import { useToast } from '../../components/ui/toast/ToastContainer';
import SelectDropdown from '../../components/form/SelectDropdown';
import Label from '../../components/form/Label';
import Checkbox from '../../components/form/input/Checkbox';
+import TextArea from '../../components/form/input/TextArea';
import PageMeta from '../../components/common/PageMeta';
import PageHeader from '../../components/common/PageHeader';
import { BoxCubeIcon } from '../../icons';
@@ -357,11 +358,11 @@ export default function ContentSettingsPage() {
Append to Every Prompt
-
setContentSettings({ ...contentSettings, appendToPrompt: e.target.value })}
+ onChange={(value) => setContentSettings({ ...contentSettings, appendToPrompt: value })}
placeholder="Add custom instructions that will be included with every content generation request..."
- className="w-full px-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-[var(--color-brand-500)] dark:bg-gray-800 min-h-[120px] resize-y"
+ rows={5}
/>
This text will be appended to every AI prompt. Use it to enforce brand guidelines, tone, or specific requirements.
diff --git a/frontend/src/pages/account/NotificationsPage.tsx b/frontend/src/pages/account/NotificationsPage.tsx
index d2dfebaf..398cfe41 100644
--- a/frontend/src/pages/account/NotificationsPage.tsx
+++ b/frontend/src/pages/account/NotificationsPage.tsx
@@ -15,6 +15,7 @@ import {
} from '../../icons';
import { Card } from '../../components/ui/card';
import Button from '../../components/ui/button/Button';
+import Select from '../../components/form/Select';
import PageMeta from '../../components/common/PageMeta';
import PageHeader from '../../components/common/PageHeader';
import { useNotificationStore } from '../../store/notificationStore';
@@ -237,19 +238,19 @@ export default function NotificationsPage() {
Severity
-
- setFilters({ ...filters, severity: e.target.value })
+
+ setFilters({ ...filters, severity: value })
}
- className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white"
- >
-
-
-
-
-
-
+ />
{/* Type Filter */}
@@ -257,20 +258,19 @@ export default function NotificationsPage() {
Type
-
- setFilters({ ...filters, notification_type: e.target.value })
+ ({
+ value: type,
+ label: getTypeLabel(type),
+ })),
+ ]}
+ defaultValue={filters.notification_type}
+ onChange={(value) =>
+ setFilters({ ...filters, notification_type: value })
}
- className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white"
- >
-
- {notificationTypes.map((type) => (
-
- ))}
-
+ />
{/* Read Status Filter */}
@@ -278,17 +278,17 @@ export default function NotificationsPage() {
Status
-
- setFilters({ ...filters, is_read: e.target.value })
+
+ setFilters({ ...filters, is_read: value })
}
- className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white"
- >
-
-
-
-
+ />
diff --git a/frontend/src/pages/account/PurchaseCreditsPage.tsx b/frontend/src/pages/account/PurchaseCreditsPage.tsx
index 6220405f..71b5c942 100644
--- a/frontend/src/pages/account/PurchaseCreditsPage.tsx
+++ b/frontend/src/pages/account/PurchaseCreditsPage.tsx
@@ -6,6 +6,9 @@
import { useState, useEffect } from 'react';
import { AlertCircleIcon, CheckIcon, CreditCardIcon, Building2Icon, WalletIcon, Loader2Icon, ZapIcon } from '../../icons';
import Button from '../../components/ui/button/Button';
+import InputField from '../../components/form/input/InputField';
+import TextArea from '../../components/form/input/TextArea';
+import Label from '../../components/form/Label';
import PageMeta from '../../components/common/PageMeta';
import PageHeader from '../../components/common/PageHeader';
import {
@@ -245,33 +248,26 @@ export default function PurchaseCreditsPage() {
-
- Transaction Reference / ID *
-
-
setManualPaymentData({ ...manualPaymentData, transaction_reference: e.target.value })
}
placeholder="Enter transaction ID or reference number"
- className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[var(--color-brand-500)] focus:border-[var(--color-brand-500)]"
/>
-
- Additional Notes (Optional)
-
- Additional Notes (Optional)
+
- setManualPaymentData({ ...manualPaymentData, notes: e.target.value })
+ onChange={(value) =>
+ setManualPaymentData({ ...manualPaymentData, notes: value })
}
placeholder="Any additional information..."
rows={3}
- className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[var(--color-brand-500)] focus:border-[var(--color-brand-500)]"
/>
diff --git a/frontend/src/pages/settings/ProfileSettingsPage.tsx b/frontend/src/pages/settings/ProfileSettingsPage.tsx
index b65a4aa2..a3d6121b 100644
--- a/frontend/src/pages/settings/ProfileSettingsPage.tsx
+++ b/frontend/src/pages/settings/ProfileSettingsPage.tsx
@@ -6,6 +6,10 @@
import { useState } from 'react';
import { SaveIcon, UserIcon, MailIcon, LockIcon, Loader2Icon } from '../../icons';
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 Checkbox from '../../components/form/input/Checkbox';
export default function ProfileSettingsPage() {
const [saving, setSaving] = useState(false);
@@ -38,14 +42,15 @@ export default function ProfileSettingsPage() {
Update your personal settings - Your name, preferences, and notification choices
-
{saving ? : }
{saving ? 'Saving...' : '✓ Save My Settings'}
-
+
@@ -53,47 +58,35 @@ export default function ProfileSettingsPage() {
About You
@@ -103,34 +96,30 @@ export default function ProfileSettingsPage() {
How You Like It
-
- Your Timezone
-
- setProfile({ ...profile, timezone: e.target.value })}
- className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-brand-500 dark:bg-gray-800"
- >
-
-
-
-
-
-
+ setProfile({ ...profile, timezone: val })}
+ />
-
- Language
-
- setProfile({ ...profile, language: e.target.value })}
- className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-brand-500 dark:bg-gray-800"
- >
-
-
-
-
+ setProfile({ ...profile, language: val })}
+ />
@@ -148,11 +137,9 @@ export default function ProfileSettingsPage() {
Get notified about important changes to your account
- setProfile({ ...profile, emailNotifications: e.target.checked })}
- className="w-5 h-5 text-brand-600 rounded focus:ring-brand-500"
+ onChange={(checked) => setProfile({ ...profile, emailNotifications: checked })}
/>
@@ -162,11 +149,9 @@ export default function ProfileSettingsPage() {
Hear about new features and content tips
- setProfile({ ...profile, marketingEmails: e.target.checked })}
- className="w-5 h-5 text-brand-600 rounded focus:ring-brand-500"
+ onChange={(checked) => setProfile({ ...profile, marketingEmails: checked })}
/>
@@ -177,9 +162,9 @@ export default function ProfileSettingsPage() {
Security
-
+
Change Password
-
+
diff --git a/frontend/src/templates/ContentViewTemplate.tsx b/frontend/src/templates/ContentViewTemplate.tsx
index a100c089..0ec23dca 100644
--- a/frontend/src/templates/ContentViewTemplate.tsx
+++ b/frontend/src/templates/ContentViewTemplate.tsx
@@ -19,6 +19,7 @@ import React, { useEffect, useMemo, useState } from 'react';
import { Content, fetchImages, ImageRecord } from '../services/api';
import { ArrowLeftIcon, CalendarIcon, TagIcon, FileTextIcon, CheckCircleIcon, XCircleIcon, ClockIcon, PencilIcon, ImageIcon, BoltIcon } from '../icons';
import { useNavigate } from 'react-router-dom';
+import Button from '../components/ui/button/Button';
interface ContentViewTemplateProps {
content: Content | null;
@@ -759,13 +760,13 @@ export default function ContentViewTemplate({ content, loading, onBack }: Conten
Content Not Found
The content you're looking for doesn't exist or has been deleted.
{onBack && (
-
Back to Content List
-
+
)}
@@ -824,13 +825,14 @@ export default function ContentViewTemplate({ content, loading, onBack }: Conten
{/* Back Button */}
{onBack && (
-
-
- Back to Content
-
+
+ Back to Content List
+
)}
{/* Main Content Card */}
@@ -1025,40 +1027,42 @@ export default function ContentViewTemplate({ content, loading, onBack }: Conten
{/* Draft status: Show Edit Content + Generate Images */}
{content.status.toLowerCase() === 'draft' && (
<>
-
navigate(`/sites/${content.site_id}/posts/${content.id}/edit`)}
- className="inline-flex items-center gap-2 px-4 py-2 bg-brand-500 hover:bg-brand-600 text-white rounded-lg font-medium transition-colors"
>
Edit Content
-
-
+ navigate(`/writer/images?contentId=${content.id}`)}
- className="inline-flex items-center gap-2 px-4 py-2 bg-purple-500 hover:bg-purple-600 text-white rounded-lg font-medium transition-colors"
>
Generate Images
-
+
>
)}
{/* Review status: Show Edit Content + Publish */}
{content.status.toLowerCase() === 'review' && (
<>
-
navigate(`/sites/${content.site_id}/posts/${content.id}/edit`)}
- className="inline-flex items-center gap-2 px-4 py-2 bg-brand-500 hover:bg-brand-600 text-white rounded-lg font-medium transition-colors"
>
Edit Content
-
-
+ navigate(`/writer/published?contentId=${content.id}&action=publish`)}
- className="inline-flex items-center gap-2 px-4 py-2 bg-success-500 hover:bg-success-600 text-white rounded-lg font-medium transition-colors"
>
Publish
-
+
>
)}
diff --git a/frontend/src/templates/FormPageTemplate.tsx b/frontend/src/templates/FormPageTemplate.tsx
index e541a920..51f970bb 100644
--- a/frontend/src/templates/FormPageTemplate.tsx
+++ b/frontend/src/templates/FormPageTemplate.tsx
@@ -12,6 +12,7 @@
*/
import React, { ReactNode, useState } from 'react';
+import Button from '../components/ui/button/Button';
interface FormSection {
title: string;
@@ -89,21 +90,21 @@ export default function FormPageTemplate({
{(onSave || onCancel) && (
{onCancel && (
-
{cancelLabel}
-
+
)}
{onSave && (
-
{loading ? 'Saving...' : saveLabel}
-
+
)}
)}