diff --git a/BUTTON_STANDARDIZATION_COMPLETE.md b/BUTTON_STANDARDIZATION_COMPLETE.md new file mode 100644 index 00000000..b6ad734c --- /dev/null +++ b/BUTTON_STANDARDIZATION_COMPLETE.md @@ -0,0 +1,225 @@ +# Button Component Standardization - Complete + +## Overview +Fixed Button component and all Sites pages to match IGNY8 global design standard: +- Secondary button color now uses `text-dim` (#64748b) +- Removed ALL `Link` component usage from buttons +- All buttons now use `onClick` navigation with `useNavigate()` + +## Changes Made + +### 1. Button Component (`/frontend/src/components/ui/button/Button.tsx`) + +#### Removed Link/Anchor Support +- **Removed imports**: `Link` from react-router-dom +- **Removed props**: `as`, `href`, `to`, `target`, `rel` +- **Simplified component**: Always renders ` +``` + +#### List.tsx (`/frontend/src/pages/Sites/List.tsx`) +- **Removed import**: `Link` from react-router-dom +- **Changed**: Site card buttons from `Button as={Link} to={...}` to `Button onClick={() => navigate(...)}` +- **Buttons updated**: + - Dashboard (primary variant) + - Content (secondary variant) + - Pages (secondary variant) + - Settings (secondary variant) + +**Before:** +```tsx + +``` + +**After:** +```tsx + +``` + +#### Content.tsx (`/frontend/src/pages/Sites/Content.tsx`) +- **Removed import**: `Link` from react-router-dom +- **Changed**: 2 buttons from `Button as={Link}` to `Button onClick` +- **Buttons updated**: + - "New Post" button (top of page) + - "Create Your First Post" button (empty state) + +#### Editor.tsx (`/frontend/src/pages/Sites/Editor.tsx`) +- **Removed import**: `Link` from react-router-dom (was imported but not yet used) + +## Verification + +### TypeScript Compilation +✅ No errors found in: +- `/frontend/src/components/ui/button/Button.tsx` +- `/frontend/src/pages/Sites/Dashboard.tsx` +- `/frontend/src/pages/Sites/List.tsx` +- `/frontend/src/pages/Sites/Content.tsx` +- `/frontend/src/pages/Sites/Editor.tsx` + +### Code Search +✅ No remaining instances of: +- ` + +// Button with icon + + +// Multiple buttons (primary + secondary) +
+ + +
+``` + +### Variants +- **primary**: Brand color background (#0693e3), white text +- **secondary**: Text-dim color (#64748b), transparent background, gray hover +- **outline**: Colored text with ring border +- **ghost**: Colored text with subtle hover +- **gradient**: Gradient background with shadow + +### Tones +- **brand**: Primary blue (#0693e3) +- **success**: Teal-green (#0bbf87) +- **warning**: Vivid orange (#ff7a00) +- **danger**: Red (#ef4444) +- **neutral**: Gray + +### Sizes +- **xs**: h-7 px-2.5 text-xs +- **sm**: h-9 px-3 text-sm (most common) +- **md**: h-10 px-4 text-sm +- **lg**: h-12 px-5 text-base + +## Color Reference + +### Text-Dim Color +```css +/* tokens.css */ +--color-text-dim: #64748b; /* Light mode */ + +.dark { + --color-text-dim: #9ca3af; /* Dark mode */ +} +``` + +This is Tailwind's `slate-500` equivalent, used for: +- Secondary button text +- Subdued/helper text +- Less prominent UI elements + +## What Was Wrong Before + +1. **Secondary color mismatch**: Used tone-specific colors (brand-600, success-600, etc.) instead of universal text-dim +2. **Link component usage**: Button component had `as={Link}` prop support (accessibility anti-pattern for navigation buttons) +3. **Mixed patterns**: Some pages used ``, others used `Button as={Link}`, creating inconsistency +4. **Wrong variants**: Some buttons still used old "solid"/"soft" names + +## Result + +✅ **Consistent design**: All secondary buttons now have same text-dim color across all tones +✅ **Single pattern**: ALL buttons use `onClick` with `useNavigate()` - no Link components +✅ **Proper semantics**: Navigation uses button elements with click handlers (matches HTML standard) +✅ **Type safe**: Removed unnecessary props from Button component +✅ **Maintainable**: Single source of truth for button styles in Button.tsx + +--- + +**Date**: 2024 +**Status**: ✅ Complete - All Sites pages standardized diff --git a/IGNY8_DESIGN_STANDARD.md b/IGNY8_DESIGN_STANDARD.md new file mode 100644 index 00000000..d3934f60 --- /dev/null +++ b/IGNY8_DESIGN_STANDARD.md @@ -0,0 +1,515 @@ +# IGNY8 Design Standard Reference +**Standardized UI patterns used across Planner, Writer, and Dashboard modules** + +This document defines the locked design patterns and component usage standards for the IGNY8 application. All modules (including Sites) should follow these patterns to maintain visual consistency. + +--- + +## Core Component Library + +### 1. Button Component +**Location:** `frontend/src/components/ui/button/Button.tsx` +**Status:** 🔒 STYLE LOCKED - See `DESIGN_SYSTEM.md` + +#### Variants (5 total) +- `solid` - Filled background (primary action) +- `soft` - Light background (secondary action) +- `outline` - Border only (tertiary action) +- `ghost` - No border or background (minimal action) +- `gradient` - Gradient background with shadow (premium/highlight action) + +#### Sizes (4 total) +- `xs` - Extra small +- `sm` - Small +- `md` - Medium (default) +- `lg` - Large + +#### Tones (5 total) +- `brand` - Primary brand color (blue) +- `success` - Green +- `warning` - Orange +- `danger` - Red/Error +- `neutral` - Gray + +#### Usage Example +```tsx +import Button from '../../components/ui/button/Button'; + + +``` + +#### ⚠️ Anti-Pattern +```tsx +// ❌ DON'T: Raw HTML buttons with inline Tailwind + + +// ✅ DO: Use Button component + +``` + +--- + +### 2. ComponentCard +**Location:** `frontend/src/components/common/ComponentCard.tsx` +**Purpose:** Standard card wrapper for sections with title and description + +#### Props +- `title` (required) - Section title (string or ReactNode) +- `desc` (optional) - Description text below title +- `children` (required) - Card content +- `className` (optional) - Additional styling + +#### Usage Example +```tsx +import ComponentCard from '../../components/common/ComponentCard'; + + +
+ {/* Content */} +
+
+``` + +#### Structure +- **Header:** Title + optional description (gray text) +- **Body:** Border-top separated content area with padding +- **Dark mode:** Automatic theme support + +#### ⚠️ Anti-Pattern +```tsx +// ❌ DON'T: Raw Card component with manual header + +
+

Quick Actions

+
+
+ {/* Content */} +
+
+ +// ✅ DO: Use ComponentCard + + {/* Content */} + +``` + +--- + +### 3. EnhancedMetricCard +**Location:** `frontend/src/components/dashboard/EnhancedMetricCard.tsx` +**Purpose:** Display metrics with optional trends, tooltips, and navigation + +#### Key Props +- `title` (required) - Metric label +- `value` (required) - Main metric value (string | number) +- `subtitle` (optional) - Additional context below value +- `icon` (optional) - Icon component +- `accentColor` (required) - Border accent color +- `trend` (optional) - { direction: 'up' | 'down', value: string } +- `href` (optional) - React Router navigation path +- `onClick` (optional) - Click handler (alternative to href) +- `tooltip` (optional) - Tooltip text on hover +- `details` (optional) - Array of tooltip detail breakdowns + +#### Accent Colors (6 total) +- `blue` - Primary/default metrics +- `green` - Success/positive metrics +- `orange` - Warning/attention metrics +- `purple` - Special/premium metrics +- `red` - Error/critical metrics +- `success` - Alternative green (var(--color-success)) + +#### Usage Example +```tsx +import EnhancedMetricCard from '../../components/dashboard/EnhancedMetricCard'; + +} + accentColor="blue" + trend={{ direction: 'up', value: '+12%' }} + href="/planner/keywords" + tooltip="View all active keywords" + details={[ + { label: 'Tracked', value: '800' }, + { label: 'Untracked', value: '434' }, + ]} +/> +``` + +#### Features +- Automatic Link wrapping when `href` provided +- Hover effects and transitions +- Dark mode support +- Tooltip with optional details breakdown +- Trend indicators with arrows + +#### ⚠️ Anti-Pattern +```tsx +// ❌ DON'T: Custom metric cards with inline styles +
+
+
+

Active Keywords

+

1,234

+
+ +
+
+ +// ✅ DO: Use EnhancedMetricCard +} + accentColor="blue" +/> +``` + +--- + +### 4. PageHeader +**Location:** `frontend/src/components/common/PageHeader.tsx` +**Purpose:** Standardized page header with title, site/sector info, and selectors + +#### Key Props +- `title` (required) - Page title +- `lastUpdated` (optional) - Last refresh timestamp +- `badge` (optional) - { icon: ReactNode, color: 'blue' | 'green' | ... } +- `showRefresh` (optional) - Show refresh button +- `onRefresh` (optional) - Refresh button handler +- `hideSiteSector` (optional) - Hide site/sector info for global pages +- `className` (optional) - Additional styling + +#### Badge Colors (6 total) +Same as Button/Metric: `blue`, `green`, `purple`, `orange`, `red`, `indigo` + +#### Usage Example +```tsx +import PageHeader from '../../components/common/PageHeader'; +import { ListIcon } from '../../icons'; + +, color: 'blue' }} + showRefresh={true} + onRefresh={handleRefresh} +/> +``` + +#### Features +- Automatic site/sector display from stores +- SiteAndSectorSelector integration +- Responsive layout (stack on mobile) +- Badge with icon support +- Last updated timestamp + +#### ⚠️ Anti-Pattern +```tsx +// ❌ DON'T: Custom page headers with manual layout +
+
+

Keyword Dashboard

+

+ Site: {site.name} • Sector: {sector.name} +

+
+ +
+ +// ✅ DO: Use PageHeader + +``` + +--- + +### 5. Link Component (React Router) +**Location:** `react-router-dom` +**Purpose:** Standard navigation with automatic prefetching and accessibility + +#### Usage Example +```tsx +import { Link } from 'react-router-dom'; + + +
Navigate to Keywords
+ +``` + +#### Benefits Over Button + Navigate +- ✅ Proper semantic HTML (`` tag) +- ✅ Keyboard navigation (Tab + Enter) +- ✅ Right-click "Open in new tab" support +- ✅ Screen reader accessibility +- ✅ Browser history support +- ✅ Automatic prefetching + +#### ⚠️ Anti-Pattern +```tsx +// ❌ DON'T: Button with onClick navigate + + +// ✅ DO: Use Link component + + Go to Keywords + +``` + +--- + +## Design Patterns + +### Quick Actions Grid +**Standard pattern used in:** Planner Dashboard, Writer Dashboard + +#### Structure +```tsx + +
+ + {/* Gradient Icon */} +
+ +
+ + {/* Content */} +
+

Action Title

+

Action description

+
+ + {/* Arrow Icon */} + + +
+
+``` + +#### Key Elements +1. **ComponentCard wrapper** - Title + description +2. **Responsive grid** - 1 col mobile, 2 col tablet, 4 col desktop +3. **Link component** - Not button +4. **Gradient icon box** - 48px (size-12), gradient from primary to primary-dark +5. **Content area** - Title (font-semibold) + description (text-sm) +6. **Arrow icon** - Right-pointing, changes color on hover +7. **Hover effects** - Border color + shadow on hover + +#### Gradient Color Variants +```tsx +// Primary (Blue) +from-[var(--color-primary)] to-[var(--color-primary-dark)] + +// Success (Green) +from-[var(--color-success)] to-[var(--color-success-dark)] + +// Warning (Orange) +from-[var(--color-warning)] to-[var(--color-warning-dark)] + +// Purple +from-[var(--color-purple)] to-[var(--color-purple-dark)] +``` + +#### ⚠️ Anti-Pattern - Sites Dashboard Current Implementation +```tsx +// ❌ DON'T: Button with navigate + manual styling + + +// ✅ DO: Link component + + {/* ... */} + +``` + +--- + +### Metrics Dashboard Grid +**Standard pattern used in:** Planner Dashboard, Writer Dashboard + +#### Structure +```tsx +
+ } + accentColor="blue" + href="/path" + /> +
+``` + +#### Grid Breakpoints +- **Mobile (< 768px):** 1 column +- **Tablet (768px - 1024px):** 2 columns +- **Desktop (> 1024px):** 3 columns + +#### Best Practices +- Use `href` prop for navigation (not `onClick`) +- Consistent icon sizing: `h-5 w-5` +- Map accent colors to metric meaning (blue = neutral, green = success, orange = warning, red = error) +- Include tooltips for complex metrics +- Add trend indicators when comparing periods + +--- + +## Color System + +### CSS Variables +```css +--color-primary: #0693e3; /* Brand Blue */ +--color-primary-dark: #0570b8; +--color-success: #0bbf87; /* Green */ +--color-success-dark: #089968; +--color-warning: #ff7a00; /* Orange */ +--color-warning-dark: #cc6200; +--color-purple: #5d4ae3; +--color-purple-dark: #4a3bb5; +--color-error: #f44336; /* Red */ +``` + +### Tailwind Color Classes +- `brand-*` - Primary blue (50-900) +- `success-*` - Green (50-900) +- `warning-*` - Orange (50-900) +- `error-*` - Red (50-900) +- `purple-*` - Purple (50-900) +- `gray-*` - Neutral (50-900) + +--- + +## Sites Module Refactor Checklist + +### Current Inconsistencies (Sites Dashboard Example) +- ❌ Uses ` +``` + +**Target (✅):** +```tsx + + {/* ... */} + +``` + +**Reason:** Link provides better accessibility, keyboard navigation, and browser features (right-click open in new tab) + +--- + +#### 2. Missing ComponentCard Wrapper +**Files affected:** Dashboard.tsx, List.tsx, Settings.tsx + +**Current (❌):** +```tsx +
+

+ Quick Actions +

+
+ {/* actions */} +
+
+``` + +**Target (✅):** +```tsx + +
+ {/* actions */} +
+
+``` + +**Reason:** Consistent section styling, automatic dark mode support, less boilerplate + +--- + +#### 3. Button + Navigate Anti-Pattern +**Files affected:** Content.tsx (2 instances), Editor.tsx (2 instances), List.tsx (1 instance) + +**Current (❌):** +```tsx + +``` + +**Target (✅):** +```tsx + +``` + +**Reason:** Button component supports `as` prop for Link rendering while maintaining Button styles + +--- + +#### 4. Inline Tailwind Duplication +**Files affected:** Settings.tsx (30+ instances), Content.tsx, List.tsx + +**Example:** +```tsx +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" +``` + +**Solution:** Extract to reusable Input component or use existing form components + +--- + +#### 5. Raw Button Elements +**Files affected:** Multiple (50+ instances found) + +**Pattern:** Some ` + + +``` + +**After:** +```tsx + +
+ + {/* ... */} + +
+
+``` + +**Estimated Time:** 30 minutes + +--- + +### Phase 2: Content.tsx (Medium Priority) +**Lines affected:** 133, 214 (Button with navigate) + +**Changes:** +1. Replace ` +``` + +**After:** +```tsx + +``` + +**Estimated Time:** 15 minutes + +--- + +### Phase 3: List.tsx (Medium Priority) +**Lines affected:** 670 (Button with navigate), filter/tab sections + +**Changes:** +1. Replace ` - )} + + {/* Right side: Navigation bar stacked above site/sector selector */} +
+ {navigation &&
{navigation}
} +
+ {!hideSiteSector && } + {showRefresh && onRefresh && ( + + )} +
); diff --git a/frontend/src/components/integration/SiteIntegrationsSection.tsx b/frontend/src/components/integration/SiteIntegrationsSection.tsx index 7da5a944..0c7a3715 100644 --- a/frontend/src/components/integration/SiteIntegrationsSection.tsx +++ b/frontend/src/components/integration/SiteIntegrationsSection.tsx @@ -228,8 +228,7 @@ export default function SiteIntegrationsSection({ siteId }: SiteIntegrationsSect Connect your sites to external platforms (WordPress, Shopify, Custom APIs)

- diff --git a/frontend/src/components/navigation/ModuleNavigationTabs.tsx b/frontend/src/components/navigation/ModuleNavigationTabs.tsx index d39ffa30..e2587e96 100644 --- a/frontend/src/components/navigation/ModuleNavigationTabs.tsx +++ b/frontend/src/components/navigation/ModuleNavigationTabs.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { Link, useLocation } from 'react-router'; -import { Tabs, TabList, Tab } from '../ui/tabs/Tabs'; +import Button from '../ui/button/Button'; export interface NavigationTab { label: string; @@ -32,34 +32,22 @@ export default function ModuleNavigationTabs({ tabs, className = '' }: ModuleNav const activeTabId = activeTab?.path || tabs[0]?.path || ''; return ( -
- - {(activeTabId, setActiveTab) => ( - - {tabs.map((tab) => { - const isActive = activeTabId === tab.path || location.pathname.startsWith(tab.path + '/'); - - return ( - setActiveTab(tab.path)} - className="flex-1" - > - - {tab.icon && {tab.icon}} - {tab.label} - - - ); - })} - - )} - +
+ {tabs.map((tab) => { + const isActive = activeTabId === tab.path || location.pathname.startsWith(tab.path + '/'); + + return ( + + + + ); + })}
); } diff --git a/frontend/src/components/onboarding/WorkflowGuide.tsx b/frontend/src/components/onboarding/WorkflowGuide.tsx index 5efa51c3..a308872a 100644 --- a/frontend/src/components/onboarding/WorkflowGuide.tsx +++ b/frontend/src/components/onboarding/WorkflowGuide.tsx @@ -411,14 +411,8 @@ export default function WorkflowGuide({ onSiteAdded }: WorkflowGuideProps) { ); }, ); diff --git a/frontend/src/layout/AppHeader.tsx b/frontend/src/layout/AppHeader.tsx index 61c87dad..3a0bc5d1 100644 --- a/frontend/src/layout/AppHeader.tsx +++ b/frontend/src/layout/AppHeader.tsx @@ -7,25 +7,6 @@ import NotificationDropdown from "../components/header/NotificationDropdown"; import UserDropdown from "../components/header/UserDropdown"; import { HeaderMetrics } from "../components/header/HeaderMetrics"; import ResourceDebugToggle from "../components/debug/ResourceDebugToggle"; -import { useOnboardingStore } from "../store/onboardingStore"; -import Button from "../components/ui/button/Button"; -import { BoltIcon } from "../icons"; - -const ShowGuideButton: React.FC = () => { - const { toggleGuide, isGuideVisible } = useOnboardingStore(); - - return ( - - ); -}; const AppHeader: React.FC = () => { const [isApplicationMenuOpen, setApplicationMenuOpen] = useState(false); @@ -180,8 +161,6 @@ const AppHeader: React.FC = () => {
{/* */} - {/* */} - {/* */} {/* */} diff --git a/frontend/src/layout/AppLayout.tsx b/frontend/src/layout/AppLayout.tsx index 3bde3b2c..2811644c 100644 --- a/frontend/src/layout/AppLayout.tsx +++ b/frontend/src/layout/AppLayout.tsx @@ -194,13 +194,29 @@ const LayoutContent: React.FC = () => { refreshUserData(); }; - // Periodic refresh every 2 minutes + // Proactive token refresh - refresh token every 12 minutes (before 15-minute expiry) + // This prevents 401 errors and ensures seamless user experience + const tokenRefreshInterval = setInterval(async () => { + const authState = useAuthStore.getState(); + const refreshToken = authState?.refreshToken; + if (refreshToken && authState?.isAuthenticated) { + try { + await authState.refreshToken(); + console.debug('Token proactively refreshed'); + } catch (error) { + console.debug('Proactive token refresh failed (will retry on next API call):', error); + } + } + }, 720000); // 12 minutes = 720000ms + + // Periodic user data refresh every 2 minutes const intervalId = setInterval(() => refreshUserData(), 120000); document.addEventListener('visibilitychange', handleVisibilityChange); window.addEventListener('focus', handleFocus); return () => { + clearInterval(tokenRefreshInterval); clearInterval(intervalId); document.removeEventListener('visibilitychange', handleVisibilityChange); window.removeEventListener('focus', handleFocus); diff --git a/frontend/src/pages/Automation/Rules.tsx b/frontend/src/pages/Automation/Rules.tsx index 9483cf40..35cc79ac 100644 --- a/frontend/src/pages/Automation/Rules.tsx +++ b/frontend/src/pages/Automation/Rules.tsx @@ -127,6 +127,7 @@ export default function AutomationRules() { icon: , color: 'purple', }} + navigation={} />
diff --git a/frontend/src/pages/Automation/Tasks.tsx b/frontend/src/pages/Automation/Tasks.tsx index dc207af3..ae6cded5 100644 --- a/frontend/src/pages/Automation/Tasks.tsx +++ b/frontend/src/pages/Automation/Tasks.tsx @@ -113,8 +113,8 @@ export default function AutomationTasks() { icon: , color: 'blue', }} + navigation={} /> -
diff --git a/frontend/src/pages/Linker/ContentList.tsx b/frontend/src/pages/Linker/ContentList.tsx index 099a9993..a80ff3b1 100644 --- a/frontend/src/pages/Linker/ContentList.tsx +++ b/frontend/src/pages/Linker/ContentList.tsx @@ -104,10 +104,10 @@ export default function LinkerContentList() { }, + ]} />} /> - }, - ]} /> {loading ? (
diff --git a/frontend/src/pages/Optimizer/ContentSelector.tsx b/frontend/src/pages/Optimizer/ContentSelector.tsx index 4a47a6c2..b88d75b1 100644 --- a/frontend/src/pages/Optimizer/ContentSelector.tsx +++ b/frontend/src/pages/Optimizer/ContentSelector.tsx @@ -148,10 +148,10 @@ export default function OptimizerContentSelector() { icon: , color: 'orange', }} + navigation={ }, + ]} />} /> - }, - ]} />