# UI Component Specifications

## Document Overview

This document provides detailed specifications for all UI components required for multi-tenant, SSO, and MCP Token management features in ColaFlow.

**Version:** 1.0
**Last Updated:** 2025-11-03
**Tech Stack:** Next.js 16 + React 19 + shadcn/ui + Tailwind CSS 4
**Owner:** UX/UI Team

---

## Table of Contents

1. [Design System Foundation](#design-system-foundation)
2. [Registration Components](#registration-components)
3. [Login Components](#login-components)
4. [SSO Configuration Components](#sso-configuration-components)
5. [MCP Token Components](#mcp-token-components)
6. [Shared Components](#shared-components)
7. [Component Props Reference](#component-props-reference)

---

## Design System Foundation

### Color System

Based on ColaFlow's existing design system:

```typescript
// tailwind.config.ts
const colors = {
  primary: {
    50: '#e3f2fd',
    100: '#bbdefb',
    200: '#90caf9',
    300: '#64b5f6',
    400: '#42a5f5',
    500: '#2196F3', // Main brand color
    600: '#1e88e5',
    700: '#1976D2', // Darker shade
    800: '#1565c0',
    900: '#0d47a1',
  },
  secondary: {
    success: '#4CAF50',
    warning: '#FF9800',
    error: '#F44336',
    info: '#2196F3',
  },
  priority: {
    urgent: '#F44336',
    high: '#FF9800',
    medium: '#2196F3',
    low: '#9E9E9E',
  },
  status: {
    active: '#4CAF50',
    suspended: '#FF9800',
    cancelled: '#F44336',
    pending: '#FFC107',
  },
  gray: {
    50: '#fafafa',
    100: '#f5f5f5',
    200: '#eeeeee',
    300: '#e0e0e0',
    400: '#bdbdbd',
    500: '#9e9e9e',
    600: '#757575',
    700: '#616161',
    800: '#424242',
    900: '#212121',
  },
}
```

### Typography Scale

```typescript
// Font families
const fontFamily = {
  sans: ['Inter', 'Roboto', 'PingFang SC', 'Microsoft YaHei', 'sans-serif'],
  mono: ['JetBrains Mono', 'Consolas', 'monospace'],
}

// Font sizes
const fontSize = {
  'xs': ['12px', { lineHeight: '16px' }],
  'sm': ['14px', { lineHeight: '20px' }],
  'base': ['16px', { lineHeight: '24px' }],
  'lg': ['18px', { lineHeight: '28px' }],
  'xl': ['20px', { lineHeight: '28px' }],
  '2xl': ['24px', { lineHeight: '32px' }],
  '3xl': ['30px', { lineHeight: '36px' }],
  '4xl': ['36px', { lineHeight: '40px' }],
}

// Font weights
const fontWeight = {
  normal: '400',
  medium: '500',
  semibold: '600',
  bold: '700',
}
```

### Spacing System (8px base)

```typescript
const spacing = {
  0: '0px',
  1: '4px',    // 0.5 * 8
  2: '8px',    // 1 * 8
  3: '12px',   // 1.5 * 8
  4: '16px',   // 2 * 8
  5: '20px',   // 2.5 * 8
  6: '24px',   // 3 * 8
  8: '32px',   // 4 * 8
  10: '40px',  // 5 * 8
  12: '48px',  // 6 * 8
  16: '64px',  // 8 * 8
  20: '80px',  // 10 * 8
  24: '96px',  // 12 * 8
}
```

### Shadows

```typescript
const boxShadow = {
  sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
  DEFAULT: '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)',
  md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
  lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
  xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)',
  '2xl': '0 25px 50px -12px rgba(0, 0, 0, 0.25)',
}
```

### Border Radius

```typescript
const borderRadius = {
  none: '0',
  sm: '4px',
  DEFAULT: '6px',
  md: '8px',
  lg: '12px',
  xl: '16px',
  '2xl': '24px',
  full: '9999px',
}
```

---

## Registration Components

### 1. TenantSlugInput

**Purpose:** Input field with real-time availability check and subdomain preview.

**Visual:**

```
Company Slug *                              [ℹ️]
[acme                          ] [⟳]
✓ acme.colaflow.com is available
```

**Component Structure:**

```typescript
interface TenantSlugInputProps {
  value: string;
  onChange: (value: string) => void;
  onValidationChange: (isValid: boolean) => void;
  disabled?: boolean;
}

interface ValidationState {
  status: 'idle' | 'checking' | 'valid' | 'invalid' | 'error';
  message?: string;
  suggestions?: string[];
}
```

**States:**

1. **Idle:** No input yet
   - Border: gray-300
   - No icon

2. **Checking:** Validating in real-time
   - Border: primary-500
   - Icon: Spinner (animated)
   - Message: "Checking availability..."

3. **Valid:** Slug is available
   - Border: green-500
   - Icon: Checkmark (green)
   - Message: "✓ acme.colaflow.com is available"

4. **Invalid:** Slug is taken or has errors
   - Border: red-500
   - Icon: X (red)
   - Message: "This URL is already taken. Try: acme-corp, acme-team"
   - Show suggestion chips (clickable)

5. **Error:** Network or validation error
   - Border: red-500
   - Icon: Alert (red)
   - Message: "Unable to check availability. Please try again."

**Behavior:**
- Debounce input: 500ms
- Auto-lowercase
- Strip invalid characters
- Show preview domain below input
- Validate format: ^[a-z0-9]+(?:-[a-z0-9]+)*$
- API call: `GET /api/tenants/check-slug?slug=acme`

**Accessibility:**
- aria-label: "Company URL slug"
- aria-describedby: Points to preview text
- aria-invalid: true when status is invalid
- aria-live: "polite" for validation messages

---

### 2. PasswordStrengthInput

**Purpose:** Password input with strength indicator and requirements checklist.

**Visual:**

```
Password *
[******************] [👁️]

Strength: [███████░░░] Medium

Requirements:
☑ At least 8 characters
☑ One uppercase letter (A-Z)
☑ One lowercase letter (a-z)
☐ One number (0-9)
☐ One special character (!@#$%^&*)
```

**Component Structure:**

```typescript
interface PasswordStrengthInputProps {
  value: string;
  onChange: (value: string) => void;
  showRequirements?: boolean;
  minStrength?: 'weak' | 'medium' | 'strong';
}

interface PasswordStrength {
  level: 'weak' | 'medium' | 'strong';
  score: number; // 0-100
  requirements: PasswordRequirement[];
}

interface PasswordRequirement {
  label: string;
  met: boolean;
  regex: RegExp;
}
```

**Strength Calculation:**

```typescript
const calculateStrength = (password: string): PasswordStrength => {
  let score = 0;
  const requirements: PasswordRequirement[] = [
    { label: 'At least 8 characters', met: password.length >= 8, regex: /.{8,}/ },
    { label: 'One uppercase letter (A-Z)', met: /[A-Z]/.test(password), regex: /[A-Z]/ },
    { label: 'One lowercase letter (a-z)', met: /[a-z]/.test(password), regex: /[a-z]/ },
    { label: 'One number (0-9)', met: /\d/.test(password), regex: /\d/ },
    { label: 'One special character (!@#$%^&*)', met: /[!@#$%^&*]/.test(password), regex: /[!@#$%^&*]/ },
  ];

  requirements.forEach(req => {
    if (req.met) score += 20;
  });

  let level: 'weak' | 'medium' | 'strong' = 'weak';
  if (score >= 80) level = 'strong';
  else if (score >= 60) level = 'medium';

  return { level, score, requirements };
};
```

**Strength Bar Colors:**

- **Weak (0-40%):** bg-red-500
- **Medium (40-80%):** bg-yellow-500
- **Strong (80-100%):** bg-green-500

**Behavior:**
- Show/hide toggle button
- Update strength on every keystroke
- Prevent paste (optional, for security)
- Animated strength bar transition

**Accessibility:**
- aria-label: "Password, show requirements below"
- aria-describedby: Points to requirements list
- Toggle button: aria-label "Show password" / "Hide password"
- Requirements list: role="list"

---

### 3. SubscriptionPlanCard

**Purpose:** Selectable subscription plan card with feature comparison.

**Visual:**

```
┌─────────────────────────────────┐
│  MOST POPULAR                   │  <- Badge (optional)
├─────────────────────────────────┤
│  Professional                   │
│  $49 /month                     │
│                                 │
│  Everything in Starter, plus:   │
│  ✓ 50 users                     │
│  ✓ 100 projects                 │
│  ✓ 100 GB storage               │
│  ✓ SSO integration              │
│  ✓ Priority support             │
│  ✓ Advanced analytics           │
│                                 │
│  [Start with Professional]      │
│                                 │
│  14-day free trial              │
└─────────────────────────────────┘
```

**Component Structure:**

```typescript
interface SubscriptionPlanCardProps {
  plan: SubscriptionPlan;
  selected: boolean;
  onSelect: (plan: SubscriptionPlan) => void;
  showBadge?: boolean;
  badgeText?: string;
}

interface SubscriptionPlan {
  id: string;
  name: string;
  price: number;
  billingPeriod: 'month' | 'year';
  features: PlanFeature[];
  limits: PlanLimits;
}

interface PlanFeature {
  label: string;
  included: boolean;
  tooltip?: string;
}

interface PlanLimits {
  users: number | 'unlimited';
  projects: number | 'unlimited';
  storageGB: number;
  mcpTokens: number | 'unlimited';
}
```

**States:**

1. **Unselected:**
   - Border: gray-300
   - Background: white
   - Shadow: sm

2. **Hover:**
   - Border: primary-500
   - Shadow: md
   - Transform: scale(1.02)
   - Transition: 200ms ease-out

3. **Selected:**
   - Border: primary-500 (2px)
   - Background: primary-50
   - Shadow: lg
   - Checkmark icon in top-right corner

**Badge (optional):**
- Position: absolute top-0 right-0
- Background: primary-500
- Text: white, text-xs, uppercase
- Padding: 4px 12px
- Border-radius: bottom-left only

**Button:**
- Full width
- Size: large
- Variant: selected ? primary : secondary

**Accessibility:**
- role: "radio"
- aria-checked: selected
- Keyboard: Space/Enter to select
- Focus ring: 2px offset

---

## Login Components

### 4. SsoButton

**Purpose:** Branded SSO login button for different identity providers.

**Visual Examples:**

```
Azure AD:
[🟦 Continue with Microsoft]

Google:
[🔴 Continue with Google]

Okta:
[🔵 Continue with Okta]

Generic SAML:
[🔐 Continue with SSO]
```

**Component Structure:**

```typescript
interface SsoButtonProps {
  provider: 'AzureAD' | 'Google' | 'Okta' | 'GenericSaml';
  onClick: () => void;
  loading?: boolean;
  disabled?: boolean;
  fullWidth?: boolean;
}

interface ProviderConfig {
  name: string;
  icon: React.ReactNode;
  bgColor: string;
  textColor: string;
  hoverBgColor: string;
}
```

**Provider Configurations:**

```typescript
const providerConfigs: Record<string, ProviderConfig> = {
  AzureAD: {
    name: 'Microsoft',
    icon: <MicrosoftIcon />,
    bgColor: '#00A4EF', // Microsoft blue
    textColor: '#FFFFFF',
    hoverBgColor: '#0078D4',
  },
  Google: {
    name: 'Google',
    icon: <GoogleIcon />,
    bgColor: '#FFFFFF',
    textColor: '#3C4043',
    hoverBgColor: '#F8F9FA',
  },
  Okta: {
    name: 'Okta',
    icon: <OktaIcon />,
    bgColor: '#007DC1',
    textColor: '#FFFFFF',
    hoverBgColor: '#005F96',
  },
  GenericSaml: {
    name: 'SSO',
    icon: <LockIcon />,
    bgColor: '#6B7280',
    textColor: '#FFFFFF',
    hoverBgColor: '#4B5563',
  },
};
```

**States:**

1. **Default:**
   - Provider-specific colors
   - Border: none
   - Height: 48px (large)
   - Padding: 12px 24px
   - Font: medium weight

2. **Hover:**
   - Background: hoverBgColor
   - Shadow: md
   - Cursor: pointer

3. **Loading:**
   - Spinner animation (white for dark bg, gray for light bg)
   - Text: "Signing in..."
   - Disabled cursor

4. **Disabled:**
   - Opacity: 50%
   - Cursor: not-allowed

**Layout:**
```
[Icon] Continue with {Provider}
```
- Icon: 20x20px, margin-right 12px
- Text: align center, font-size 16px

**Accessibility:**
- aria-label: "Sign in with {provider}"
- role: "button"
- Keyboard: Enter/Space to activate

---

### 5. LoginDivider

**Purpose:** Visual separator between SSO and local login options.

**Visual:**

```
──────────── OR ────────────
```

**Component Structure:**

```typescript
interface LoginDividerProps {
  text?: string; // Default: "OR"
  className?: string;
}
```

**Implementation:**

```tsx
<div className="relative my-6">
  <div className="absolute inset-0 flex items-center">
    <div className="w-full border-t border-gray-300"></div>
  </div>
  <div className="relative flex justify-center text-sm">
    <span className="px-4 bg-white text-gray-500 uppercase tracking-wider">
      {text || 'OR'}
    </span>
  </div>
</div>
```

---

## SSO Configuration Components

### 6. SsoProviderSelect

**Purpose:** Dropdown to select SSO provider type.

**Visual:**

```
SSO Provider *
[Azure AD / Microsoft Entra    ▼]

Options:
  - Azure AD / Microsoft Entra
  - Google Workspace
  - Okta
  - Generic SAML 2.0
```

**Component Structure:**

```typescript
interface SsoProviderSelectProps {
  value: SsoProvider;
  onChange: (provider: SsoProvider) => void;
  disabled?: boolean;
}

type SsoProvider = 'AzureAD' | 'Google' | 'Okta' | 'GenericSaml';

interface ProviderOption {
  value: SsoProvider;
  label: string;
  description: string;
  icon: React.ReactNode;
  docsLink: string;
}
```

**Provider Options:**

```typescript
const providerOptions: ProviderOption[] = [
  {
    value: 'AzureAD',
    label: 'Azure AD / Microsoft Entra',
    description: 'For Microsoft 365 and Azure Active Directory',
    icon: <MicrosoftIcon />,
    docsLink: '/docs/sso/azure-ad',
  },
  {
    value: 'Google',
    label: 'Google Workspace',
    description: 'For Google Workspace (formerly G Suite)',
    icon: <GoogleIcon />,
    docsLink: '/docs/sso/google',
  },
  {
    value: 'Okta',
    label: 'Okta',
    description: 'For Okta Identity Cloud',
    icon: <OktaIcon />,
    docsLink: '/docs/sso/okta',
  },
  {
    value: 'GenericSaml',
    label: 'Generic SAML 2.0',
    description: 'For any SAML 2.0 compliant IdP',
    icon: <ShieldIcon />,
    docsLink: '/docs/sso/saml',
  },
];
```

**Dropdown Item Layout:**

```
[Icon]  Provider Name
        Description
```

- Icon: 24x24px
- Name: font-medium, text-gray-900
- Description: text-sm, text-gray-500

**Behavior:**
- Show help icon with link to docs
- Highlight selected option
- Keyboard navigation (arrow keys)

---

### 7. DomainListInput

**Purpose:** Add/remove allowed email domains for SSO.

**Visual:**

```
Allowed Email Domains (Optional)

[@acme.com              ] [+ Add Domain]

Added Domains:
┌──────────────────────────────────┐
│ @acme.com                   [🗑️] │
│ @acme.co.uk                 [🗑️] │
│ @acme-corp.com              [🗑️] │
└──────────────────────────────────┘

ℹ️ Only users with these email domains can log in via SSO.
   Leave empty to allow all domains.
```

**Component Structure:**

```typescript
interface DomainListInputProps {
  domains: string[];
  onChange: (domains: string[]) => void;
  placeholder?: string;
  helperText?: string;
}

interface ValidationResult {
  isValid: boolean;
  error?: string;
}
```

**Validation:**

```typescript
const validateDomain = (domain: string): ValidationResult => {
  // Remove @ if present
  const cleanDomain = domain.replace('@', '').trim().toLowerCase();

  // Check format: valid domain
  const domainRegex = /^[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,}$/;

  if (!cleanDomain) {
    return { isValid: false, error: 'Domain cannot be empty' };
  }

  if (!domainRegex.test(cleanDomain)) {
    return { isValid: false, error: 'Invalid domain format' };
  }

  return { isValid: true };
};
```

**Behavior:**
- Input always shows "@" prefix
- Add domain on Enter or button click
- Prevent duplicate domains
- Show validation error below input
- Animate domain chips on add/remove

**Domain Chip:**
- Background: gray-100
- Border: gray-300
- Padding: 6px 12px
- Border-radius: full
- Delete icon: hover shows red

**Accessibility:**
- Input: aria-label "Add email domain"
- Chip list: role="list"
- Delete buttons: aria-label "Remove {domain}"

---

### 8. SsoTestButton

**Purpose:** Test SSO configuration before saving.

**Visual:**

```
[Test Connection]

Test Results:
┌─────────────────────────────────────────┐
│ ✓ Metadata endpoint reachable           │
│ ✓ OIDC discovery document valid         │
│ ⏳ Testing authentication...             │
└─────────────────────────────────────────┘
```

**Component Structure:**

```typescript
interface SsoTestButtonProps {
  onTest: () => Promise<SsoTestResult>;
  disabled?: boolean;
}

interface SsoTestResult {
  success: boolean;
  checks: TestCheck[];
  error?: string;
}

interface TestCheck {
  label: string;
  status: 'pending' | 'success' | 'error';
  message?: string;
  duration?: number; // ms
}
```

**Test Checks:**

1. **Metadata Endpoint Reachable**
   - Request: GET metadata URL
   - Success: 200 status
   - Time: <500ms

2. **OIDC Discovery Valid** (OIDC only)
   - Parse JSON
   - Validate required fields
   - Time: <100ms

3. **Certificate Valid** (SAML only)
   - Parse X.509 certificate
   - Check expiration
   - Time: <100ms

4. **Callback URL Whitelisted**
   - Attempt authorization request
   - Check for redirect_uri_mismatch
   - Time: <1000ms

**States:**

1. **Idle:**
   - Button: secondary variant
   - Text: "Test Connection"
   - Icon: none

2. **Testing:**
   - Button: loading spinner
   - Text: "Testing..."
   - Show progress: checks updating in real-time

3. **Success:**
   - Button: disabled
   - Results: all checks green
   - Final message: "All checks passed ✓"

4. **Failure:**
   - Button: enabled (can retry)
   - Results: failed checks red
   - Show error details
   - Link to troubleshooting docs

**Results Display:**

```tsx
<div className="mt-4 space-y-2">
  {checks.map((check, idx) => (
    <div key={idx} className="flex items-center gap-2">
      {check.status === 'pending' && <Spinner className="h-4 w-4" />}
      {check.status === 'success' && <CheckIcon className="h-4 w-4 text-green-500" />}
      {check.status === 'error' && <XIcon className="h-4 w-4 text-red-500" />}
      <span className={check.status === 'error' ? 'text-red-700' : 'text-gray-700'}>
        {check.label}
      </span>
      {check.duration && (
        <span className="text-xs text-gray-500">({check.duration}ms)</span>
      )}
    </div>
  ))}
</div>
```

---

## MCP Token Components

### 9. McpTokenCard

**Purpose:** Display token information in list view.

**Visual:**

```
┌──────────────────────────────────────────────────┐
│ Claude Desktop                          [Active] │
│ Permissions: Projects (R), Issues (R/W)          │
│ Created: 2025-11-01 • Last Used: 2 hours ago     │
│ Expires: In 30 days                              │
│                                                  │
│ [View Details]  [Revoke]                         │
└──────────────────────────────────────────────────┘
```

**Component Structure:**

```typescript
interface McpTokenCardProps {
  token: McpToken;
  onViewDetails: (tokenId: string) => void;
  onRevoke: (tokenId: string) => void;
}

interface McpToken {
  id: string;
  name: string;
  permissions: TokenPermissions;
  createdAt: string;
  lastUsedAt?: string;
  expiresAt?: string;
  status: TokenStatus;
  usageCount: number;
}

type TokenStatus = 'Active' | 'Expired' | 'Revoked';

interface TokenPermissions {
  [resource: string]: string[]; // e.g., { projects: ['read'], issues: ['read', 'create'] }
}
```

**Status Badge:**

```typescript
const statusConfig: Record<TokenStatus, { color: string; text: string }> = {
  Active: { color: 'green', text: 'Active' },
  Expired: { color: 'gray', text: 'Expired' },
  Revoked: { color: 'red', text: 'Revoked' },
};
```

**Permissions Display:**

Format: `Resource (Operations)`
- R = Read
- C = Create
- U = Update
- D = Delete
- S = Search

Example: `Projects (R), Issues (R/C/U), Documents (R/C)`

Truncate if more than 3 resources, show "+X more"

**Time Display:**

```typescript
const formatRelativeTime = (date: string): string => {
  const now = new Date();
  const then = new Date(date);
  const diffMs = now.getTime() - then.getTime();

  const seconds = Math.floor(diffMs / 1000);
  const minutes = Math.floor(seconds / 60);
  const hours = Math.floor(minutes / 60);
  const days = Math.floor(hours / 24);

  if (minutes < 1) return 'Just now';
  if (minutes < 60) return `${minutes} minute${minutes > 1 ? 's' : ''} ago`;
  if (hours < 24) return `${hours} hour${hours > 1 ? 's' : ''} ago`;
  if (days < 30) return `${days} day${days > 1 ? 's' : ''} ago`;

  return then.toLocaleDateString();
};
```

**Hover State:**
- Border: primary-500
- Shadow: md
- Background: gray-50

**Actions:**
- "View Details" button: secondary variant
- "Revoke" button: danger text button

---

### 10. PermissionMatrix

**Purpose:** Interactive matrix for selecting token permissions.

**Visual:**

```
┌─────────────────────────────────────────────────┐
│ Resource   │ Read │Create│Update│Delete│Search │
│────────────┼──────┼──────┼──────┼──────┼────── │
│ Projects   │  ✓   │      │      │      │   ✓   │
│ Issues     │  ✓   │  ✓   │  ✓   │      │   ✓   │
│ Documents  │  ✓   │  ✓   │      │      │   ✓   │
│ Reports    │  ✓   │      │      │      │       │
│ Sprints    │  ✓   │      │      │      │   ✓   │
│ Comments   │  ✓   │  ✓   │      │      │       │
└─────────────────────────────────────────────────┘

[Select All] [Clear All] [Templates ▼]
```

**Component Structure:**

```typescript
interface PermissionMatrixProps {
  value: TokenPermissions;
  onChange: (permissions: TokenPermissions) => void;
  restrictions?: PermissionRestrictions;
}

interface PermissionRestrictions {
  [resource: string]: {
    allowedOperations: string[];
    disabledMessage?: string;
  };
}

interface PermissionCell {
  resource: string;
  operation: string;
  enabled: boolean;
  disabled?: boolean;
  disabledReason?: string;
}
```

**Resources and Operations:**

```typescript
const resources = ['projects', 'issues', 'documents', 'reports', 'sprints', 'comments'];
const operations = ['read', 'create', 'update', 'delete', 'search'];
```

**Restrictions (Business Rules):**

```typescript
const defaultRestrictions: PermissionRestrictions = {
  issues: {
    allowedOperations: ['read', 'create', 'update', 'search'], // No delete
    disabledMessage: 'Deleting issues is not allowed for safety',
  },
  users: {
    allowedOperations: [], // No user management via MCP
    disabledMessage: 'User management is not available via MCP tokens',
  },
};
```

**Behavior:**

1. **Click checkbox:** Toggle permission
2. **Click row header:** Select all operations for that resource
3. **Click column header:** Select that operation for all resources
4. **Disabled cells:** Show tooltip with reason
5. **Templates:** Quick presets (Read Only, Read+Write, Custom)

**Templates:**

```typescript
const templates = {
  'Read Only': {
    projects: ['read', 'search'],
    issues: ['read', 'search'],
    documents: ['read', 'search'],
    reports: ['read'],
  },
  'Read + Write': {
    projects: ['read', 'search'],
    issues: ['read', 'create', 'update', 'search'],
    documents: ['read', 'create', 'search'],
    reports: ['read'],
  },
};
```

**Visual States:**

- **Enabled checkbox:**
  - Unchecked: border-gray-300
  - Checked: bg-primary-500, checkmark visible

- **Disabled checkbox:**
  - Unchecked: border-gray-200, bg-gray-100
  - Tooltip on hover

**Accessibility:**
- Table: role="table"
- Row headers: scope="row"
- Column headers: scope="col"
- Checkboxes: aria-label "{operation} permission for {resource}"
- Disabled cells: aria-disabled="true"

---

### 11. TokenDisplayModal

**Purpose:** One-time display of generated MCP token with copy functionality.

**Visual:**

```
┌────────────────────────────────────────────────┐
│ ✓ Token Created Successfully!           [× ] │
├────────────────────────────────────────────────┤
│                                                │
│ ⚠️ IMPORTANT: Save this token now!             │
│                                                │
│ This is the ONLY time you'll see this token.   │
│ If you lose it, you'll need to generate a new  │
│ one.                                           │
│                                                │
│ Your Token:                                    │
│ ┌──────────────────────────────────────────┐   │
│ │ mcp_acme_7f3d8a9c4e1b2f5a6d8c9e0f1a2b3  │   │
│ └──────────────────────────────────────────┘   │
│ [📋 Copy to Clipboard]                         │
│                                                │
│ [Download as .env file]                        │
│                                                │
│ [☐] I have saved this token securely           │
│                                                │
│ [Close]                                        │
└────────────────────────────────────────────────┘
```

**Component Structure:**

```typescript
interface TokenDisplayModalProps {
  token: string;
  tokenName: string;
  onClose: () => void;
  open: boolean;
}

interface TokenDisplayState {
  copied: boolean;
  downloaded: boolean;
  acknowledged: boolean;
}
```

**Token Display Box:**

```tsx
<div className="bg-gray-50 rounded-lg border border-gray-300 p-4 my-4">
  <code className="text-sm font-mono break-all select-all">
    {token}
  </code>
</div>
```

**Copy Button Behavior:**

```typescript
const handleCopy = async () => {
  await navigator.clipboard.writeText(token);
  setCopied(true);
  toast.success('Token copied to clipboard!');

  // Reset after 3 seconds
  setTimeout(() => setCopied(false), 3000);
};
```

**Download .env File:**

```typescript
const handleDownload = () => {
  const content = `# ColaFlow MCP Token
# Token Name: ${tokenName}
# Created: ${new Date().toISOString()}
# Expires: ${expiresAt || 'Never'}

COLAFLOW_MCP_TOKEN=${token}
`;

  const blob = new Blob([content], { type: 'text/plain' });
  const url = URL.createObjectURL(blob);
  const link = document.createElement('a');
  link.href = url;
  link.download = 'colaflow-mcp-token.env';
  link.click();
  URL.revokeObjectURL(url);

  setDownloaded(true);
};
```

**Close Button State:**

- Disabled until checkbox is checked
- Shows tooltip: "Please confirm you've saved the token"

**Warning on Close (if not acknowledged):**

```
Are you sure you want to close?

You won't be able to see this token again.

[Go Back]  [Close Anyway]
```

---

### 12. AuditLogTable

**Purpose:** Display MCP token usage audit log.

**Visual:**

```
┌──────────────────────────────────────────────────────────┐
│ Timestamp        Action   Resource    Result    IP       │
│──────────────────────────────────────────────────────────│
│ Nov 3, 08:15 AM  Read     Issue #123  Success  192.168… │
│ Nov 3, 08:10 AM  Create   Issue #456  Success  192.168… │
│ Nov 3, 08:05 AM  Update   Issue #456  Success  192.168… │
│ Nov 3, 08:00 AM  Read     Project #1  Success  192.168… │
│ Nov 3, 07:55 AM  Delete   Issue #789  Failed   192.168… │
└──────────────────────────────────────────────────────────┘

Showing 1-50 of 1,247 operations
[Load More]
```

**Component Structure:**

```typescript
interface AuditLogTableProps {
  logs: AuditLog[];
  loading?: boolean;
  onLoadMore?: () => void;
  hasMore?: boolean;
}

interface AuditLog {
  id: string;
  timestamp: string;
  action: string; // read, create, update, delete, search
  resource: string; // Issue #123, Project #1
  result: 'Success' | 'Failed';
  ipAddress: string;
  userAgent?: string;
  errorMessage?: string;
  durationMs: number;
}
```

**Result Badge:**

```typescript
const resultConfig = {
  Success: { color: 'green', icon: CheckCircleIcon },
  Failed: { color: 'red', icon: XCircleIcon },
};
```

**Row Hover:**
- Background: gray-50
- Cursor: pointer
- Show details on click (expandable row)

**Expandable Row Details:**

```
┌──────────────────────────────────────────────────────┐
│ Request Details                                      │
│ Method: POST /api/mcp/issues                         │
│ Duration: 234ms                                      │
│ User Agent: Claude Desktop 1.2.3                     │
│ IP Address: 192.168.1.100                            │
│                                                      │
│ Error: Permission denied                             │
│ Message: Token does not have delete permission for   │
│          issues.                                     │
└──────────────────────────────────────────────────────┘
```

**Pagination:**
- Load more button (not traditional pagination)
- Infinite scroll (optional)
- Show "X of Y operations" count

**Filters (future):**
- Action type
- Result (success/failed)
- Date range
- Resource type

---

## Shared Components

### 13. ProgressIndicator

**Purpose:** Show progress through multi-step flows.

**Visual:**

```
● ○ ○     Step 1 of 3
✓ ● ○     Step 2 of 3
✓ ✓ ●     Step 3 of 3
```

**Component Structure:**

```typescript
interface ProgressIndicatorProps {
  steps: number;
  currentStep: number;
  labels?: string[];
  variant?: 'dots' | 'line' | 'numbers';
}
```

**Variants:**

1. **Dots (default):**
   - Completed: filled circle with checkmark
   - Current: filled circle (primary color)
   - Upcoming: outlined circle (gray)
   - Size: 32px
   - Gap: 16px

2. **Line:**
   - Horizontal line connecting steps
   - Progress fills from left to right

3. **Numbers:**
   - Numbered steps instead of dots
   - Better for >5 steps

**Implementation:**

```tsx
<div className="flex items-center justify-center gap-4">
  {Array.from({ length: steps }, (_, i) => {
    const stepNumber = i + 1;
    const isCompleted = stepNumber < currentStep;
    const isCurrent = stepNumber === currentStep;

    return (
      <div key={i} className="flex items-center gap-2">
        <div className={cn(
          "w-8 h-8 rounded-full flex items-center justify-center",
          isCompleted && "bg-green-500 text-white",
          isCurrent && "bg-primary-500 text-white",
          !isCompleted && !isCurrent && "border-2 border-gray-300 text-gray-500"
        )}>
          {isCompleted ? <CheckIcon /> : stepNumber}
        </div>
        {i < steps - 1 && (
          <div className="w-12 h-0.5 bg-gray-300" />
        )}
      </div>
    );
  })}
</div>
```

---

### 14. InfoTooltip

**Purpose:** Contextual help icon with tooltip.

**Visual:**

```
Company Slug * [ℹ️]
              ↑
         Hover shows tooltip
```

**Component Structure:**

```typescript
interface InfoTooltipProps {
  content: React.ReactNode | string;
  placement?: 'top' | 'right' | 'bottom' | 'left';
  maxWidth?: number;
}
```

**Icon:**
- Component: Information Circle Icon
- Size: 16px
- Color: gray-400
- Hover: gray-600

**Tooltip:**
- Background: gray-900
- Text: white, text-sm
- Padding: 8px 12px
- Border-radius: 6px
- Max-width: 200px (default)
- Arrow: pointing to icon
- Animation: fade-in 200ms
- Delay: 300ms (on hover)

**Accessibility:**
- Icon button: role="button", aria-label="Show help"
- Tooltip: role="tooltip"
- Keyboard: Focus with Tab, show tooltip on Enter

---

### 15. EmptyState

**Purpose:** Placeholder when no data exists.

**Visual:**

```
┌────────────────────────────────────┐
│          [Icon/Illustration]       │
│                                    │
│         No tokens yet              │
│                                    │
│  Generate your first MCP token to  │
│  connect AI agents to ColaFlow.    │
│                                    │
│  [Generate Token]                  │
└────────────────────────────────────┘
```

**Component Structure:**

```typescript
interface EmptyStateProps {
  icon?: React.ReactNode;
  title: string;
  description: string;
  action?: {
    label: string;
    onClick: () => void;
  };
  secondaryAction?: {
    label: string;
    href: string;
  };
}
```

**Layout:**
- Center-aligned
- Icon: 64x64px, gray-400
- Title: text-xl, font-semibold, gray-900
- Description: text-sm, gray-600, max-w-md
- Spacing: 16px between elements
- Action button: primary, large

**Common Variations:**

1. **No MCP Tokens:**
   - Icon: Robot icon
   - Action: "Generate Token"

2. **No SSO Config:**
   - Icon: Shield icon
   - Action: "Configure SSO"

3. **No Users:**
   - Icon: Users icon
   - Action: "Invite Team"

---

### 16. ConfirmDialog

**Purpose:** Confirmation modal for destructive actions.

**Visual:**

```
┌────────────────────────────────────┐
│ Revoke Token?                      │
├────────────────────────────────────┤
│                                    │
│ Are you sure you want to revoke    │
│ this token?                        │
│                                    │
│ Token: Claude Desktop              │
│ Last used: 2 hours ago             │
│                                    │
│ This will:                         │
│ • Immediately stop all API access  │
│ • Cannot be undone                 │
│ • Require generating a new token   │
│                                    │
│ [Cancel]  [Revoke Token]           │
└────────────────────────────────────┘
```

**Component Structure:**

```typescript
interface ConfirmDialogProps {
  open: boolean;
  title: string;
  description: string;
  details?: React.ReactNode;
  consequences?: string[];
  confirmLabel?: string;
  cancelLabel?: string;
  variant?: 'danger' | 'warning' | 'info';
  onConfirm: () => void | Promise<void>;
  onCancel: () => void;
  loading?: boolean;
}
```

**Variants:**

1. **Danger (red):**
   - Confirm button: bg-red-500
   - Icon: Alert triangle (red)
   - Used for: delete, revoke, suspend

2. **Warning (yellow):**
   - Confirm button: bg-yellow-500
   - Icon: Warning icon (yellow)
   - Used for: archival, downgrade

3. **Info (blue):**
   - Confirm button: bg-primary-500
   - Icon: Info icon (blue)
   - Used for: non-destructive confirmations

**Behavior:**
- Backdrop: semi-transparent black
- Click outside: calls onCancel
- Escape key: calls onCancel
- Enter key: calls onConfirm (if confirm button focused)
- Loading state: disable buttons, show spinner

**Animation:**
- Enter: fade-in 200ms + scale from 0.95
- Exit: fade-out 200ms + scale to 0.95

---

## Component Props Reference

### Common Props

All components accept these base props:

```typescript
interface BaseComponentProps {
  className?: string;
  style?: React.CSSProperties;
  testId?: string; // for e2e testing
  'aria-label'?: string;
  'aria-describedby'?: string;
}
```

### Form Field Props

All form fields accept:

```typescript
interface FormFieldProps extends BaseComponentProps {
  label?: string;
  helperText?: string;
  error?: string;
  required?: boolean;
  disabled?: boolean;
  autoFocus?: boolean;
}
```

### Button Props

```typescript
interface ButtonProps extends BaseComponentProps {
  variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger';
  size?: 'sm' | 'md' | 'lg';
  loading?: boolean;
  disabled?: boolean;
  fullWidth?: boolean;
  icon?: React.ReactNode;
  iconPosition?: 'left' | 'right';
  onClick?: () => void;
  type?: 'button' | 'submit' | 'reset';
}
```

### Modal Props

```typescript
interface ModalProps extends BaseComponentProps {
  open: boolean;
  onClose: () => void;
  title?: string;
  description?: string;
  size?: 'sm' | 'md' | 'lg' | 'xl' | 'full';
  closeOnBackdrop?: boolean;
  closeOnEscape?: boolean;
  showCloseButton?: boolean;
  footer?: React.ReactNode;
}
```

---

## Implementation Guidelines

### Component File Structure

```
src/components/
├── registration/
│   ├── TenantSlugInput.tsx
│   ├── PasswordStrengthInput.tsx
│   └── SubscriptionPlanCard.tsx
├── auth/
│   ├── SsoButton.tsx
│   ├── LoginDivider.tsx
│   └── LoginForm.tsx
├── sso/
│   ├── SsoProviderSelect.tsx
│   ├── DomainListInput.tsx
│   └── SsoTestButton.tsx
├── mcp-tokens/
│   ├── McpTokenCard.tsx
│   ├── PermissionMatrix.tsx
│   ├── TokenDisplayModal.tsx
│   └── AuditLogTable.tsx
└── shared/
    ├── ProgressIndicator.tsx
    ├── InfoTooltip.tsx
    ├── EmptyState.tsx
    └── ConfirmDialog.tsx
```

### Naming Conventions

- **Components:** PascalCase (e.g., `McpTokenCard`)
- **Files:** Match component name (e.g., `McpTokenCard.tsx`)
- **Props interfaces:** `{ComponentName}Props`
- **CSS classes:** kebab-case with BEM (if needed)
- **Test files:** `{ComponentName}.test.tsx`
- **Story files:** `{ComponentName}.stories.tsx` (Storybook)

### State Management

- **Local state:** Use `useState` for component-specific state
- **Form state:** Use `react-hook-form` for complex forms
- **Server state:** Use `TanStack Query` for API data
- **Global state:** Use Zustand for app-wide state

### Testing Requirements

Each component must have:

1. **Unit tests:** Component rendering and interaction
2. **Accessibility tests:** ARIA, keyboard navigation
3. **Visual tests:** Storybook stories for all variants
4. **Integration tests:** E2E flows with Playwright

---

## Conclusion

This component specification provides a complete blueprint for implementing the UI for multi-tenant, SSO, and MCP Token features. All components follow ColaFlow's design system and adhere to accessibility best practices.

**Next Steps:**
1. Review with development team
2. Create Storybook stories for each component
3. Begin implementation in priority order:
   - Registration flow (M1)
   - Login + SSO (M1)
   - MCP Tokens (M2)
4. Conduct accessibility audit
5. User testing with prototypes

**Questions:** Contact UX/UI team at ux@colaflow.com
