+
{showCategoryHub ? : children}
diff --git a/client/src/components/ui/SmartAlexDesignSystem.tsx b/client/src/components/ui/SmartAlexDesignSystem.tsx
new file mode 100644
index 00000000..258f74ea
--- /dev/null
+++ b/client/src/components/ui/SmartAlexDesignSystem.tsx
@@ -0,0 +1,561 @@
+/**
+ * SmartAlex Design System
+ *
+ * Shared components implementing the SmartAlex visual language:
+ * - Teal gradient heroes and buttons
+ * - Consistent KPI cards with trend indicators
+ * - Unified page headers with icons
+ * - Data tables with consistent styling
+ * - Form dialogs with standard layout
+ * - Empty states with illustrations
+ */
+import React, { ReactNode, useState } from 'react';
+
+// ============================================================================
+// DESIGN TOKENS
+// ============================================================================
+
+export const tokens = {
+ colors: {
+ primary: '#0d9488', // teal-600
+ primaryDark: '#0f766e', // teal-700
+ primaryLight: '#14b8a6', // teal-500
+ accent: '#f59e0b', // amber-500
+ success: '#10b981', // emerald-500
+ danger: '#ef4444', // red-500
+ warning: '#f59e0b', // amber-500
+ info: '#3b82f6', // blue-500
+ muted: '#6b7280', // gray-500
+ background: '#f9fafb', // gray-50
+ surface: '#ffffff',
+ text: '#111827', // gray-900
+ textSecondary: '#6b7280', // gray-500
+ },
+ gradients: {
+ hero: 'linear-gradient(135deg, #0d9488 0%, #14b8a6 50%, #2dd4bf 100%)',
+ button: 'linear-gradient(135deg, #0d9488 0%, #0f766e 100%)',
+ accent: 'linear-gradient(135deg, #f59e0b 0%, #d97706 100%)',
+ card: 'linear-gradient(135deg, #f0fdfa 0%, #ccfbf1 100%)',
+ },
+} as const;
+
+// ============================================================================
+// PAGE HEADER (replaces inconsistent h1/h2 patterns across 157 pages)
+// ============================================================================
+
+interface PageHeaderProps {
+ icon: ReactNode;
+ title: string;
+ subtitle?: string;
+ actions?: ReactNode;
+ variant?: 'hero' | 'compact';
+}
+
+export function PageHeader({ icon, title, subtitle, actions, variant = 'compact' }: PageHeaderProps) {
+ if (variant === 'hero') {
+ return (
+
+
+
+
+ {icon}
+
+
+
{title}
+ {subtitle &&
{subtitle}
}
+
+
+ {actions &&
{actions}
}
+
+
+ );
+ }
+
+ return (
+
+
+
+ {icon}
+
+
+
{title}
+ {subtitle &&
{subtitle}
}
+
+
+ {actions &&
{actions}
}
+
+ );
+}
+
+// ============================================================================
+// KPI CARD (replaces 50+ different stat card implementations)
+// ============================================================================
+
+interface KpiCardProps {
+ label: string;
+ value: string | number;
+ icon?: ReactNode;
+ trend?: { value: number; label?: string };
+ target?: string;
+ color?: 'teal' | 'amber' | 'emerald' | 'red' | 'blue' | 'purple';
+}
+
+const kpiColorMap = {
+ teal: 'bg-teal-50 text-teal-600',
+ amber: 'bg-amber-50 text-amber-600',
+ emerald: 'bg-emerald-50 text-emerald-600',
+ red: 'bg-red-50 text-red-600',
+ blue: 'bg-blue-50 text-blue-600',
+ purple: 'bg-purple-50 text-purple-600',
+};
+
+export function KpiCard({ label, value, icon, trend, target, color = 'teal' }: KpiCardProps) {
+ return (
+
+
+
+
{label}
+
{value}
+ {target &&
Target: {target}
}
+
+ {icon && (
+
+ {icon}
+
+ )}
+
+ {trend && (
+
+ = 0 ? 'text-emerald-600' : 'text-red-600'}`}>
+ {trend.value >= 0 ? '↑' : '↓'} {Math.abs(trend.value)}%
+
+ {trend.label && {trend.label}}
+
+ )}
+
+ );
+}
+
+// ============================================================================
+// ACTION BUTTON (replaces inconsistent button patterns)
+// ============================================================================
+
+interface ActionButtonProps {
+ children: ReactNode;
+ onClick?: () => void;
+ variant?: 'primary' | 'secondary' | 'outline' | 'danger';
+ size?: 'sm' | 'md' | 'lg';
+ icon?: ReactNode;
+ disabled?: boolean;
+ type?: 'button' | 'submit';
+}
+
+export function ActionButton({ children, onClick, variant = 'primary', size = 'md', icon, disabled, type = 'button' }: ActionButtonProps) {
+ const baseClasses = 'inline-flex items-center justify-center font-medium rounded-lg transition-all focus:outline-none focus:ring-2 focus:ring-offset-2';
+
+ const sizeClasses = {
+ sm: 'px-3 py-1.5 text-sm gap-1.5',
+ md: 'px-4 py-2.5 text-sm gap-2',
+ lg: 'px-6 py-3 text-base gap-2',
+ };
+
+ const variantClasses = {
+ primary: 'sa-btn-primary text-white focus:ring-teal-500 disabled:opacity-50',
+ secondary: 'bg-gray-100 text-gray-700 hover:bg-gray-200 focus:ring-gray-500',
+ outline: 'border-2 border-teal-600 text-teal-600 hover:bg-teal-50 focus:ring-teal-500',
+ danger: 'bg-red-600 text-white hover:bg-red-700 focus:ring-red-500',
+ };
+
+ return (
+
+ );
+}
+
+// ============================================================================
+// DATA TABLE (replaces 50+ different table patterns)
+// ============================================================================
+
+interface Column
{
+ key: string;
+ header: string;
+ render?: (item: T) => ReactNode;
+ align?: 'left' | 'center' | 'right';
+}
+
+interface DataTableProps {
+ columns: Column[];
+ data: T[];
+ emptyMessage?: string;
+ emptyIcon?: ReactNode;
+ isLoading?: boolean;
+}
+
+export function DataTable>({ columns, data, emptyMessage = 'No records found.', emptyIcon, isLoading }: DataTableProps) {
+ if (isLoading) {
+ return (
+
+ );
+ }
+
+ return (
+
+
+
+
+
+ {columns.map((col) => (
+ |
+ {col.header}
+ |
+ ))}
+
+
+
+ {data.length === 0 ? (
+
+ |
+ {emptyIcon && {emptyIcon} }
+ {emptyMessage}
+ |
+
+ ) : (
+ data.map((item, idx) => (
+
+ {columns.map((col) => (
+ |
+ {col.render ? col.render(item) : String(item[col.key] ?? '—')}
+ |
+ ))}
+
+ ))
+ )}
+
+
+
+
+ );
+}
+
+// ============================================================================
+// PERIOD SELECTOR (replaces many date range selectors)
+// ============================================================================
+
+interface PeriodSelectorProps {
+ periods: { key: string; label: string }[];
+ selected: string;
+ onSelect: (key: string) => void;
+}
+
+export function PeriodSelector({ periods, selected, onSelect }: PeriodSelectorProps) {
+ return (
+
+ {periods.map((p) => (
+
+ ))}
+
+ );
+}
+
+// ============================================================================
+// STATUS BADGE (replaces many status rendering patterns)
+// ============================================================================
+
+interface StatusBadgeProps {
+ status: string;
+ variant?: 'dot' | 'pill';
+}
+
+const statusColors: Record = {
+ active: 'bg-emerald-100 text-emerald-700',
+ completed: 'bg-emerald-100 text-emerald-700',
+ approved: 'bg-emerald-100 text-emerald-700',
+ delivered: 'bg-emerald-100 text-emerald-700',
+ paid: 'bg-emerald-100 text-emerald-700',
+ pending: 'bg-amber-100 text-amber-700',
+ draft: 'bg-gray-100 text-gray-700',
+ review: 'bg-blue-100 text-blue-700',
+ in_transit: 'bg-blue-100 text-blue-700',
+ processing: 'bg-blue-100 text-blue-700',
+ failed: 'bg-red-100 text-red-700',
+ rejected: 'bg-red-100 text-red-700',
+ cancelled: 'bg-red-100 text-red-700',
+ defaulted: 'bg-red-100 text-red-700',
+ expired: 'bg-gray-100 text-gray-500',
+};
+
+export function StatusBadge({ status, variant = 'pill' }: StatusBadgeProps) {
+ const colorClass = statusColors[status.toLowerCase()] || 'bg-gray-100 text-gray-700';
+ const label = status.replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
+
+ if (variant === 'dot') {
+ const dotColor = colorClass.includes('emerald') ? 'bg-emerald-500' : colorClass.includes('amber') ? 'bg-amber-500' : colorClass.includes('red') ? 'bg-red-500' : colorClass.includes('blue') ? 'bg-blue-500' : 'bg-gray-400';
+ return (
+
+
+ {label}
+
+ );
+ }
+
+ return (
+
+ {label}
+
+ );
+}
+
+// ============================================================================
+// DIALOG (replaces many inconsistent modal implementations)
+// ============================================================================
+
+interface DialogProps {
+ open: boolean;
+ onClose: () => void;
+ title: string;
+ children: ReactNode;
+ actions?: ReactNode;
+ size?: 'sm' | 'md' | 'lg';
+}
+
+export function Dialog({ open, onClose, title, children, actions, size = 'md' }: DialogProps) {
+ if (!open) return null;
+
+ const sizeClasses = {
+ sm: 'max-w-md',
+ md: 'max-w-lg',
+ lg: 'max-w-2xl',
+ };
+
+ return (
+
+
+
+
+
+
{children}
+ {actions && (
+
+ {actions}
+
+ )}
+
+
+
+ );
+}
+
+// ============================================================================
+// FORM FIELD (replaces many inconsistent input patterns)
+// ============================================================================
+
+interface FormFieldProps {
+ label: string;
+ name: string;
+ type?: 'text' | 'number' | 'email' | 'date' | 'textarea' | 'select';
+ value: string | number;
+ onChange: (value: string) => void;
+ placeholder?: string;
+ required?: boolean;
+ options?: { value: string; label: string }[];
+ error?: string;
+}
+
+export function FormField({ label, name, type = 'text', value, onChange, placeholder, required, options, error }: FormFieldProps) {
+ const inputClasses = `w-full px-3 py-2 border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-teal-500 focus:border-transparent transition-all ${error ? 'border-red-300' : 'border-gray-300'}`;
+
+ return (
+
+
+ {type === 'select' && options ? (
+
+ ) : type === 'textarea' ? (
+
+ );
+}
+
+// ============================================================================
+// TAB NAVIGATION (replaces many inconsistent tab implementations)
+// ============================================================================
+
+interface Tab {
+ key: string;
+ label: string;
+ icon?: ReactNode;
+ badge?: string | number;
+}
+
+interface TabNavProps {
+ tabs: Tab[];
+ activeTab: string;
+ onTabChange: (key: string) => void;
+}
+
+export function TabNav({ tabs, activeTab, onTabChange }: TabNavProps) {
+ return (
+
+
+ {tabs.map((tab) => (
+
+ ))}
+
+
+ );
+}
+
+// ============================================================================
+// SECTION CARD (replaces many wrapper patterns)
+// ============================================================================
+
+interface SectionCardProps {
+ title?: string;
+ subtitle?: string;
+ icon?: ReactNode;
+ children: ReactNode;
+ actions?: ReactNode;
+ className?: string;
+}
+
+export function SectionCard({ title, subtitle, icon, children, actions, className = '' }: SectionCardProps) {
+ return (
+
+ {(title || actions) && (
+
+
+ {icon &&
{icon}}
+
+ {title &&
{title}
}
+ {subtitle &&
{subtitle}
}
+
+
+ {actions}
+
+ )}
+
{children}
+
+ );
+}
+
+// ============================================================================
+// FILTER BAR (replaces many filter implementations)
+// ============================================================================
+
+interface FilterOption {
+ key: string;
+ label: string;
+ count?: number;
+}
+
+interface FilterBarProps {
+ filters: FilterOption[];
+ selected: string;
+ onSelect: (key: string) => void;
+}
+
+export function FilterBar({ filters, selected, onSelect }: FilterBarProps) {
+ return (
+
+ {filters.map((f) => (
+
+ ))}
+
+ );
+}
diff --git a/client/src/index.css b/client/src/index.css
index 23a640ff..1277ed7d 100644
--- a/client/src/index.css
+++ b/client/src/index.css
@@ -64,9 +64,9 @@
}
:root {
- /* Primary: Deep Forest Green - Trust, Growth, Agriculture */
- --primary: oklch(0.45 0.12 145);
- --primary-foreground: oklch(0.98 0.01 145);
+ /* Primary: SmartAlex Teal - Trust, Innovation, Agriculture */
+ --primary: oklch(0.55 0.11 175);
+ --primary-foreground: oklch(0.98 0.01 175);
/* Accent: Warm Gold/Amber - Harvest, Prosperity, Finance */
--accent: oklch(0.75 0.15 75);
@@ -111,7 +111,7 @@
/* Sidebar */
--sidebar: oklch(0.18 0.03 250);
--sidebar-foreground: oklch(0.92 0.01 250);
- --sidebar-primary: oklch(0.55 0.12 145);
+ --sidebar-primary: oklch(0.55 0.11 175);
--sidebar-primary-foreground: oklch(0.98 0.01 145);
--sidebar-accent: oklch(0.25 0.03 250);
--sidebar-accent-foreground: oklch(0.92 0.01 250);
@@ -133,13 +133,13 @@
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.05), 0 2px 4px -2px rgb(0 0 0 / 0.05);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.05), 0 4px 6px -4px rgb(0 0 0 / 0.05);
--shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.05), 0 8px 10px -6px rgb(0 0 0 / 0.05);
- --shadow-glow: 0 0 20px -5px oklch(0.45 0.12 145 / 0.3);
+ --shadow-glow: 0 0 20px -5px oklch(0.55 0.11 175 / 0.3);
}
.dark {
- /* Primary: Lighter green for dark mode */
- --primary: oklch(0.6 0.12 145);
- --primary-foreground: oklch(0.15 0.02 145);
+ /* Primary: SmartAlex Teal for dark mode */
+ --primary: oklch(0.65 0.11 175);
+ --primary-foreground: oklch(0.15 0.02 175);
/* Accent: Warm Gold */
--accent: oklch(0.7 0.12 75);
@@ -191,7 +191,7 @@
/* Sidebar */
--sidebar: oklch(0.12 0.02 250);
--sidebar-foreground: oklch(0.88 0.01 250);
- --sidebar-primary: oklch(0.6 0.12 145);
+ --sidebar-primary: oklch(0.6 0.11 175);
--sidebar-primary-foreground: oklch(0.15 0.02 145);
--sidebar-accent: oklch(0.2 0.02 250);
--sidebar-accent-foreground: oklch(0.92 0.01 250);
@@ -438,7 +438,7 @@
============================================ */
.gradient-primary {
- background: linear-gradient(135deg, oklch(0.45 0.12 145) 0%, oklch(0.35 0.1 145) 100%);
+ background: linear-gradient(135deg, #0d9488 0%, #0f766e 100%);
}
.gradient-accent {
@@ -842,6 +842,112 @@
}
}
+/* ============================================
+ SMARTALEX UNIFIED DESIGN SYSTEM
+ Applied platform-wide for visual consistency.
+ Teal gradients, warm illustrative style,
+ circular iconography, consistent spacing.
+ ============================================ */
+
+/* SmartAlex Hero — used across ALL feature pages */
+.sa-hero {
+ background: linear-gradient(135deg, #0d9488 0%, #14b8a6 50%, #2dd4bf 100%);
+ border-radius: 16px;
+ position: relative;
+ overflow: hidden;
+}
+.sa-hero::after {
+ content: '';
+ position: absolute;
+ top: -50%;
+ right: -30%;
+ width: 60%;
+ height: 200%;
+ background: radial-gradient(ellipse, rgba(255,255,255,0.08) 0%, transparent 70%);
+ pointer-events: none;
+}
+.dark .sa-hero {
+ background: linear-gradient(135deg, #134e4a 0%, #0f4c47 50%, #0c3e3a 100%);
+}
+
+/* SmartAlex Card — consistent card styling */
+.sa-card {
+ border-radius: 12px;
+ border: 1px solid #e5e7eb;
+ transition: box-shadow 0.2s ease, transform 0.15s ease;
+}
+.sa-card:hover {
+ box-shadow: 0 4px 16px rgba(13, 148, 136, 0.1);
+}
+.dark .sa-card {
+ border-color: #374151;
+ background: #1f2937;
+}
+.dark .sa-card:hover {
+ box-shadow: 0 4px 16px rgba(13, 148, 136, 0.2);
+}
+
+/* SmartAlex Primary Button */
+.sa-btn-primary {
+ background: linear-gradient(135deg, #0d9488 0%, #0f766e 100%);
+ color: white;
+ border: none;
+ font-weight: 600;
+ border-radius: 8px;
+ transition: opacity 0.15s ease, transform 0.1s ease;
+ cursor: pointer;
+}
+.sa-btn-primary:hover {
+ opacity: 0.92;
+ transform: translateY(-1px);
+}
+.sa-btn-primary:active {
+ transform: translateY(0);
+}
+.sa-btn-primary:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+ transform: none;
+}
+
+/* SmartAlex Tab */
+.sa-tab {
+ transition: all 0.15s ease;
+}
+
+/* SmartAlex Icon Container */
+.sa-icon {
+ width: 40px;
+ height: 40px;
+ border-radius: 10px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+}
+.sa-icon-teal {
+ background: #f0fdfa;
+ color: #0d9488;
+}
+.sa-icon-amber {
+ background: #fffbeb;
+ color: #f59e0b;
+}
+.sa-icon-emerald {
+ background: #ecfdf5;
+ color: #10b981;
+}
+.sa-icon-red {
+ background: #fef2f2;
+ color: #ef4444;
+}
+.dark .sa-icon-teal {
+ background: rgba(13, 148, 136, 0.15);
+}
+.dark .sa-icon-amber {
+ background: rgba(245, 158, 11, 0.15);
+}
+
/* ============================================
SMARTALEX DAIRY DESIGN SYSTEM
Warm illustrative style — sandy-brown / sky-blue
diff --git a/client/src/pages/Login.tsx b/client/src/pages/Login.tsx
index 67ad5f6a..722f4e5a 100644
--- a/client/src/pages/Login.tsx
+++ b/client/src/pages/Login.tsx
@@ -3,9 +3,9 @@ import { useLocation } from "wouter";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
-import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
+import { Card, CardContent, CardFooter } from "@/components/ui/card";
import { Alert, AlertDescription } from "@/components/ui/alert";
-import { Loader2, AlertCircle } from "lucide-react";
+import { Loader2, AlertCircle, Sprout } from "lucide-react";
import { APP_TITLE } from "@/const";
import { trpc } from "@/lib/trpc";
import { useAuth } from "@/contexts/AuthContext";
@@ -34,16 +34,22 @@ export default function Login() {
};
return (
-
-
-
- {APP_TITLE}
-
- Sign in to your account to continue
-
-
+
+
+ {/* SmartAlex Teal Gradient Header */}
+
+
+
+
+
{APP_TITLE}
+
Agricultural Finance Platform
+
+
@@ -72,13 +79,14 @@ export default function Login() {
onChange={(e) => setPassword(e.target.value)}
required
disabled={loginMutation.isPending}
+ className="border-teal-200 focus:border-teal-500 focus:ring-teal-500"
/>
-
+